'use client' import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { usePathname } from 'next/navigation' import produce from 'immer' import { useBoolean } from 'ahooks' import cn from 'classnames' import Button from '../../base/button' import Loading from '../../base/loading' import type { CompletionParams, Inputs, ModelConfig, MoreLikeThisConfig, PromptConfig, PromptVariable } from '@/models/debug' import type { DataSet } from '@/models/datasets' import type { ModelConfig as BackendModelConfig } from '@/types/app' import ConfigContext from '@/context/debug-configuration' import ConfigModel from '@/app/components/app/configuration/config-model' import Config from '@/app/components/app/configuration/config' import Debug from '@/app/components/app/configuration/debug' import Confirm from '@/app/components/base/confirm' import { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations' import type { AppDetailResponse } from '@/models/app' import { ToastContext } from '@/app/components/base/toast' import { fetchAppDetail, updateAppModelConfig } from '@/service/apps' import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from '@/utils/model-config' import { fetchDatasets } from '@/service/datasets' import AccountSetting from '@/app/components/header/account-setting' import { useProviderContext } from '@/context/provider-context' import { AppType } from '@/types/app' const Configuration: FC = () => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const [hasFetchedDetail, setHasFetchedDetail] = useState(false) const isLoading = !hasFetchedDetail const pathname = usePathname() const matched = pathname.match(/\/app\/([^/]+)/) const appId = (matched?.length && matched[1]) ? matched[1] : '' const [mode, setMode] = useState('') const [publishedConfig, setPublishedConfig] = useState<{ modelConfig: ModelConfig completionParams: CompletionParams } | null>(null) const [conversationId, setConversationId] = useState('') const [introduction, setIntroduction] = useState('') const [controlClearChatMessage, setControlClearChatMessage] = useState(0) const [prevPromptConfig, setPrevPromptConfig] = useState({ prompt_template: '', prompt_variables: [], }) const [moreLikeThisConfig, setMoreLikeThisConfig] = useState({ enabled: false, }) const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState({ enabled: false, }) const [speechToTextConfig, setSpeechToTextConfig] = useState({ enabled: false, }) const [citationConfig, setCitationConfig] = useState({ enabled: false, }) const [formattingChanged, setFormattingChanged] = useState(false) const [inputs, setInputs] = useState({}) const [query, setQuery] = useState('') const [completionParams, setCompletionParams] = useState({ max_tokens: 16, temperature: 1, // 0-2 top_p: 1, presence_penalty: 1, // -2-2 frequency_penalty: 1, // -2-2 }) const [modelConfig, doSetModelConfig] = useState({ provider: ProviderEnum.openai, model_id: 'gpt-3.5-turbo', configs: { prompt_template: '', prompt_variables: [] as PromptVariable[], }, opening_statement: '', more_like_this: null, suggested_questions_after_answer: null, speech_to_text: null, retriever_resource: null, dataSets: [], }) const setModelConfig = (newModelConfig: ModelConfig) => { doSetModelConfig(newModelConfig) } const setModelId = (modelId: string, provider: ProviderEnum) => { const newModelConfig = produce(modelConfig, (draft: any) => { draft.provider = provider draft.model_id = modelId }) setModelConfig(newModelConfig) } const [dataSets, setDataSets] = useState([]) const syncToPublishedConfig = (_publishedConfig: any) => { const modelConfig = _publishedConfig.modelConfig setModelConfig(_publishedConfig.modelConfig) setCompletionParams(_publishedConfig.completionParams) setDataSets(modelConfig.dataSets || []) // feature setIntroduction(modelConfig.opening_statement) setMoreLikeThisConfig(modelConfig.more_like_this || { enabled: false, }) setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer || { enabled: false, }) setSpeechToTextConfig(modelConfig.speech_to_text || { enabled: false, }) setCitationConfig(modelConfig.retriever_resource || { enabled: false, }) } const { textGenerationModelList } = useProviderContext() const hasSetCustomAPIKEY = !!textGenerationModelList?.find(({ model_provider: provider }) => { if (provider.provider_type === 'system' && provider.quota_type === 'paid') return true if (provider.provider_type === 'custom') return true return false }) const isTrailFinished = !hasSetCustomAPIKEY && textGenerationModelList .filter(({ model_provider: provider }) => provider.quota_type === 'trial') .every(({ model_provider: provider }) => { const { quota_used, quota_limit } = provider return quota_used === quota_limit }) const hasSetAPIKEY = hasSetCustomAPIKEY || !isTrailFinished const [isShowSetAPIKey, { setTrue: showSetAPIKey, setFalse: hideSetAPIkey }] = useBoolean() useEffect(() => { (fetchAppDetail({ url: '/apps', id: appId }) as any).then(async (res: AppDetailResponse) => { setMode(res.mode) const modelConfig = res.model_config const model = res.model_config.model let datasets: any = null if (modelConfig.agent_mode?.enabled) datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled) if (dataSets && datasets?.length && datasets?.length > 0) { const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } }) datasets = dataSetsWithDetail setDataSets(datasets) } setIntroduction(modelConfig.opening_statement) if (modelConfig.more_like_this) setMoreLikeThisConfig(modelConfig.more_like_this) if (modelConfig.suggested_questions_after_answer) setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer) if (modelConfig.speech_to_text) setSpeechToTextConfig(modelConfig.speech_to_text) if (modelConfig.retriever_resource) setCitationConfig(modelConfig.retriever_resource) const config = { modelConfig: { provider: model.provider, model_id: model.name, configs: { prompt_template: modelConfig.pre_prompt, prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form), }, opening_statement: modelConfig.opening_statement, more_like_this: modelConfig.more_like_this, suggested_questions_after_answer: modelConfig.suggested_questions_after_answer, speech_to_text: modelConfig.speech_to_text, retriever_resource: modelConfig.retriever_resource, dataSets: datasets || [], }, completionParams: model.completion_params, } syncToPublishedConfig(config) setPublishedConfig(config) setHasFetchedDetail(true) }) }, [appId]) const cannotPublish = mode === AppType.completion && !modelConfig.configs.prompt_template const saveAppConfig = async () => { const modelId = modelConfig.model_id const promptTemplate = modelConfig.configs.prompt_template const promptVariables = modelConfig.configs.prompt_variables if (cannotPublish) { notify({ type: 'error', message: t('appDebug.otherError.promptNoBeEmpty'), duration: 3000 }) return } const postDatasets = dataSets.map(({ id }) => ({ dataset: { enabled: true, id, }, })) // new model config data struct const data: BackendModelConfig = { pre_prompt: promptTemplate, user_input_form: promptVariablesToUserInputsForm(promptVariables), opening_statement: introduction || '', more_like_this: moreLikeThisConfig, suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig, speech_to_text: speechToTextConfig, retriever_resource: citationConfig, agent_mode: { enabled: true, tools: [...postDatasets], }, model: { provider: modelConfig.provider, name: modelId, completion_params: completionParams as any, }, } await updateAppModelConfig({ url: `/apps/${appId}/model-config`, body: data }) const newModelConfig = produce(modelConfig, (draft: any) => { draft.opening_statement = introduction draft.more_like_this = moreLikeThisConfig draft.suggested_questions_after_answer = suggestedQuestionsAfterAnswerConfig draft.speech_to_text = speechToTextConfig draft.retriever_resource = citationConfig draft.dataSets = dataSets }) setPublishedConfig({ modelConfig: newModelConfig, completionParams, }) notify({ type: 'success', message: t('common.api.success'), duration: 3000 }) } const [showConfirm, setShowConfirm] = useState(false) const resetAppConfig = () => { syncToPublishedConfig(publishedConfig) setShowConfirm(false) } const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false) const [showSetAPIKeyModal, setShowSetAPIKeyModal] = useState(false) if (isLoading) { return
} return ( <>
{t('appDebug.pageTitle')}
{/* Model and Parameters */} { setCompletionParams(newParams) }} disabled={!hasSetAPIKEY} />
{showConfirm && ( setShowConfirm(false)} onConfirm={resetAppConfig} onCancel={() => setShowConfirm(false)} /> )} {showUseGPT4Confirm && ( setShowUseGPT4Confirm(false)} onConfirm={() => { setShowSetAPIKeyModal(true) setShowUseGPT4Confirm(false) }} onCancel={() => setShowUseGPT4Confirm(false)} /> )} { showSetAPIKeyModal && ( { setShowSetAPIKeyModal(false) }} /> ) } {isShowSetAPIKey && { hideSetAPIkey() }} />}
) } export default React.memo(Configuration)