'use client'
|
import type { FC } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import useSWR from 'swr'
|
import { basePath } from '@/utils/var'
|
import { useTranslation } from 'react-i18next'
|
import { useContext } from 'use-context-selector'
|
import { usePathname } from 'next/navigation'
|
import produce from 'immer'
|
import { useBoolean, useGetState } from 'ahooks'
|
import { clone, isEqual } from 'lodash-es'
|
import { CodeBracketIcon } from '@heroicons/react/20/solid'
|
import { useShallow } from 'zustand/react/shallow'
|
import AgentSettingButton from '@/app/components/app/configuration/config/agent-setting-button'
|
import useAdvancedPromptConfig from '@/app/components/app/configuration/hooks/use-advanced-prompt-config'
|
import EditHistoryModal from '@/app/components/app/configuration/config-prompt/conversation-history/edit-modal'
|
import {
|
useDebugWithSingleOrMultipleModel,
|
useFormattingChangedDispatcher,
|
} from '@/app/components/app/configuration/debug/hooks'
|
import type { ModelAndParameter } from '@/app/components/app/configuration/debug/types'
|
import Button from '@/app/components/base/button'
|
import Divider from '@/app/components/base/divider'
|
import Loading from '@/app/components/base/loading'
|
import AppPublisher from '@/app/components/app/app-publisher/features-wrapper'
|
import type {
|
AnnotationReplyConfig,
|
DatasetConfigs,
|
Inputs,
|
ModelConfig,
|
ModerationConfig,
|
MoreLikeThisConfig,
|
PromptConfig,
|
PromptVariable,
|
TextToSpeechConfig,
|
} from '@/models/debug'
|
import type { ExternalDataTool } from '@/models/common'
|
import type { DataSet } from '@/models/datasets'
|
import type { ModelConfig as BackendModelConfig, VisionSettings } from '@/types/app'
|
import ConfigContext from '@/context/debug-configuration'
|
import Config from '@/app/components/app/configuration/config'
|
import Debug from '@/app/components/app/configuration/debug'
|
import Confirm from '@/app/components/base/confirm'
|
import { ModelFeatureEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
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 { useProviderContext } from '@/context/provider-context'
|
import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app'
|
import { PromptMode } from '@/models/debug'
|
import { ANNOTATION_DEFAULT, DATASET_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
|
import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset'
|
import { useModalContext } from '@/context/modal-context'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import Drawer from '@/app/components/base/drawer'
|
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import {
|
useModelListAndDefaultModelAndCurrentProviderAndModel,
|
useTextGenerationCurrentProviderAndModelAndModelList,
|
} from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { fetchCollectionList } from '@/service/tools'
|
import type { Collection } from '@/app/components/tools/types'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
import {
|
getMultipleRetrievalConfig,
|
getSelectedDatasetsMode,
|
} from '@/app/components/workflow/nodes/knowledge-retrieval/utils'
|
import { FeaturesProvider } from '@/app/components/base/features'
|
import type { Features as FeaturesData, FileUpload } from '@/app/components/base/features/types'
|
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
import NewFeaturePanel from '@/app/components/base/features/new-feature-panel'
|
import { fetchFileUploadConfig } from '@/service/common'
|
import {
|
correctModelProvider,
|
correctToolProvider,
|
} from '@/utils'
|
import PluginDependency from '@/app/components/workflow/plugin-dependency'
|
import { supportFunctionCall } from '@/utils/tool-call'
|
|
type PublishConfig = {
|
modelConfig: ModelConfig
|
completionParams: FormValue
|
}
|
|
const Configuration: FC = () => {
|
const { t } = useTranslation()
|
const { notify } = useContext(ToastContext)
|
const { appDetail, showAppConfigureFeaturesModal, setAppSiderbarExpand, setShowAppConfigureFeaturesModal } = useAppStore(useShallow(state => ({
|
appDetail: state.appDetail,
|
setAppSiderbarExpand: state.setAppSiderbarExpand,
|
showAppConfigureFeaturesModal: state.showAppConfigureFeaturesModal,
|
setShowAppConfigureFeaturesModal: state.setShowAppConfigureFeaturesModal,
|
})))
|
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
|
|
const latestPublishedAt = useMemo(() => appDetail?.model_config?.updated_at, [appDetail])
|
const [formattingChanged, setFormattingChanged] = useState(false)
|
const { setShowAccountSettingModal } = useModalContext()
|
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<PublishConfig | null>(null)
|
|
const [conversationId, setConversationId] = useState<string | null>('')
|
|
const media = useBreakpoints()
|
const isMobile = media === MediaType.mobile
|
const [isShowDebugPanel, { setTrue: showDebugPanel, setFalse: hideDebugPanel }] = useBoolean(false)
|
|
const [introduction, setIntroduction] = useState<string>('')
|
const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([])
|
const [controlClearChatMessage, setControlClearChatMessage] = useState(0)
|
const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({
|
prompt_template: '',
|
prompt_variables: [],
|
})
|
const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig>({
|
enabled: false,
|
})
|
const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<MoreLikeThisConfig>({
|
enabled: false,
|
})
|
const [speechToTextConfig, setSpeechToTextConfig] = useState<MoreLikeThisConfig>({
|
enabled: false,
|
})
|
const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig>({
|
enabled: false,
|
voice: '',
|
language: '',
|
})
|
const [citationConfig, setCitationConfig] = useState<MoreLikeThisConfig>({
|
enabled: false,
|
})
|
const [annotationConfig, doSetAnnotationConfig] = useState<AnnotationReplyConfig>({
|
id: '',
|
enabled: false,
|
score_threshold: ANNOTATION_DEFAULT.score_threshold,
|
embedding_model: {
|
embedding_provider_name: '',
|
embedding_model_name: '',
|
},
|
})
|
const formattingChangedDispatcher = useFormattingChangedDispatcher()
|
const setAnnotationConfig = (config: AnnotationReplyConfig, notSetFormatChanged?: boolean) => {
|
doSetAnnotationConfig(config)
|
if (!notSetFormatChanged)
|
formattingChangedDispatcher()
|
}
|
|
const [moderationConfig, setModerationConfig] = useState<ModerationConfig>({
|
enabled: false,
|
})
|
const [externalDataToolsConfig, setExternalDataToolsConfig] = useState<ExternalDataTool[]>([])
|
const [inputs, setInputs] = useState<Inputs>({})
|
const [query, setQuery] = useState('')
|
const [completionParams, doSetCompletionParams] = useState<FormValue>({})
|
const [_, setTempStop, getTempStop] = useGetState<string[]>([])
|
const setCompletionParams = (value: FormValue) => {
|
const params = { ...value }
|
|
// eslint-disable-next-line ts/no-use-before-define
|
if ((!params.stop || params.stop.length === 0) && (modeModeTypeRef.current === ModelModeType.completion)) {
|
params.stop = getTempStop()
|
setTempStop([])
|
}
|
doSetCompletionParams(params)
|
}
|
|
const [modelConfig, doSetModelConfig] = useState<ModelConfig>({
|
provider: 'langgenius/openai/openai',
|
model_id: 'gpt-3.5-turbo',
|
mode: ModelModeType.unset,
|
configs: {
|
prompt_template: '',
|
prompt_variables: [] as PromptVariable[],
|
},
|
more_like_this: null,
|
opening_statement: '',
|
suggested_questions: [],
|
sensitive_word_avoidance: null,
|
speech_to_text: null,
|
text_to_speech: null,
|
file_upload: null,
|
suggested_questions_after_answer: null,
|
retriever_resource: null,
|
annotation_reply: null,
|
dataSets: [],
|
agentConfig: DEFAULT_AGENT_SETTING,
|
})
|
const isAgent = mode === 'agent-chat'
|
|
const isOpenAI = modelConfig.provider === 'langgenius/openai/openai'
|
|
const [collectionList, setCollectionList] = useState<Collection[]>([])
|
const [datasetConfigs, doSetDatasetConfigs] = useState<DatasetConfigs>({
|
retrieval_model: RETRIEVE_TYPE.multiWay,
|
reranking_model: {
|
reranking_provider_name: '',
|
reranking_model_name: '',
|
},
|
top_k: DATASET_DEFAULT.top_k,
|
score_threshold_enabled: false,
|
score_threshold: DATASET_DEFAULT.score_threshold,
|
datasets: {
|
datasets: [],
|
},
|
})
|
const datasetConfigsRef = useRef(datasetConfigs)
|
const setDatasetConfigs = useCallback((newDatasetConfigs: DatasetConfigs) => {
|
doSetDatasetConfigs(newDatasetConfigs)
|
datasetConfigsRef.current = newDatasetConfigs
|
}, [])
|
|
const setModelConfig = (newModelConfig: ModelConfig) => {
|
doSetModelConfig(newModelConfig)
|
}
|
|
const modelModeType = modelConfig.mode
|
const modeModeTypeRef = useRef(modelModeType)
|
useEffect(() => {
|
modeModeTypeRef.current = modelModeType
|
}, [modelModeType])
|
|
const [dataSets, setDataSets] = useState<DataSet[]>([])
|
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
|
const hasSetContextVar = !!contextVar
|
const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false)
|
const selectedIds = dataSets.map(item => item.id)
|
const [rerankSettingModalOpen, setRerankSettingModalOpen] = useState(false)
|
const {
|
currentModel: currentRerankModel,
|
currentProvider: currentRerankProvider,
|
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
|
const handleSelect = (data: DataSet[]) => {
|
if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) {
|
hideSelectDataSet()
|
return
|
}
|
|
formattingChangedDispatcher()
|
let newDatasets = data
|
if (data.find(item => !item.name)) { // has not loaded selected dataset
|
const newSelected = produce(data, (draft) => {
|
data.forEach((item, index) => {
|
if (!item.name) { // not fetched database
|
const newItem = dataSets.find(i => i.id === item.id)
|
if (newItem)
|
draft[index] = newItem
|
}
|
})
|
})
|
setDataSets(newSelected)
|
newDatasets = newSelected
|
}
|
else {
|
setDataSets(data)
|
}
|
hideSelectDataSet()
|
const {
|
allExternal,
|
allInternal,
|
mixtureInternalAndExternal,
|
mixtureHighQualityAndEconomic,
|
inconsistentEmbeddingModel,
|
} = getSelectedDatasetsMode(newDatasets)
|
|
if (
|
(allInternal && (mixtureHighQualityAndEconomic || inconsistentEmbeddingModel))
|
|| mixtureInternalAndExternal
|
|| allExternal
|
)
|
setRerankSettingModalOpen(true)
|
|
const { datasets, retrieval_model, score_threshold_enabled, ...restConfigs } = datasetConfigs
|
|
const retrievalConfig = getMultipleRetrievalConfig({
|
top_k: restConfigs.top_k,
|
score_threshold: restConfigs.score_threshold,
|
reranking_model: restConfigs.reranking_model && {
|
provider: restConfigs.reranking_model.reranking_provider_name,
|
model: restConfigs.reranking_model.reranking_model_name,
|
},
|
reranking_mode: restConfigs.reranking_mode,
|
weights: restConfigs.weights,
|
reranking_enable: restConfigs.reranking_enable,
|
}, newDatasets, dataSets, {
|
provider: currentRerankProvider?.provider,
|
model: currentRerankModel?.model,
|
})
|
|
setDatasetConfigs({
|
...datasetConfigsRef.current,
|
...retrievalConfig,
|
reranking_model: {
|
reranking_provider_name: retrievalConfig?.reranking_model?.provider || '',
|
reranking_model_name: retrievalConfig?.reranking_model?.model || '',
|
},
|
retrieval_model,
|
score_threshold_enabled,
|
datasets,
|
})
|
}
|
|
const [isShowHistoryModal, { setTrue: showHistoryModal, setFalse: hideHistoryModal }] = useBoolean(false)
|
|
const syncToPublishedConfig = (_publishedConfig: PublishConfig) => {
|
const modelConfig = _publishedConfig.modelConfig
|
setModelConfig(_publishedConfig.modelConfig)
|
setCompletionParams(_publishedConfig.completionParams)
|
setDataSets(modelConfig.dataSets || [])
|
// reset 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,
|
})
|
setTextToSpeechConfig(modelConfig.text_to_speech || {
|
enabled: false,
|
voice: '',
|
language: '',
|
})
|
setCitationConfig(modelConfig.retriever_resource || {
|
enabled: false,
|
})
|
}
|
|
const { isAPIKeySet } = useProviderContext()
|
const {
|
currentModel: currModel,
|
textGenerationModelList,
|
} = useTextGenerationCurrentProviderAndModelAndModelList(
|
{
|
provider: modelConfig.provider,
|
model: modelConfig.model_id,
|
},
|
)
|
|
const isFunctionCall = supportFunctionCall(currModel?.features)
|
|
// Fill old app data missing model mode.
|
useEffect(() => {
|
if (hasFetchedDetail && !modelModeType) {
|
const mode = currModel?.model_properties.mode as (ModelModeType | undefined)
|
if (mode) {
|
const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
|
draft.mode = mode
|
})
|
setModelConfig(newModelConfig)
|
}
|
}
|
}, [textGenerationModelList, hasFetchedDetail, modelModeType, currModel, modelConfig])
|
|
const [promptMode, doSetPromptMode] = useState(PromptMode.simple)
|
const isAdvancedMode = promptMode === PromptMode.advanced
|
const [canReturnToSimpleMode, setCanReturnToSimpleMode] = useState(true)
|
const setPromptMode = async (mode: PromptMode) => {
|
if (mode === PromptMode.advanced) {
|
// eslint-disable-next-line ts/no-use-before-define
|
await migrateToDefaultPrompt()
|
setCanReturnToSimpleMode(true)
|
}
|
|
doSetPromptMode(mode)
|
}
|
const [visionConfig, doSetVisionConfig] = useState({
|
enabled: false,
|
number_limits: 2,
|
detail: Resolution.low,
|
transfer_methods: [TransferMethod.local_file],
|
})
|
|
const handleSetVisionConfig = (config: VisionSettings, notNoticeFormattingChanged?: boolean) => {
|
doSetVisionConfig({
|
enabled: config.enabled || false,
|
number_limits: config.number_limits || 2,
|
detail: config.detail || Resolution.low,
|
transfer_methods: config.transfer_methods || [TransferMethod.local_file],
|
})
|
if (!notNoticeFormattingChanged)
|
formattingChangedDispatcher()
|
}
|
|
const {
|
chatPromptConfig,
|
setChatPromptConfig,
|
completionPromptConfig,
|
setCompletionPromptConfig,
|
currentAdvancedPrompt,
|
setCurrentAdvancedPrompt,
|
hasSetBlockStatus,
|
setConversationHistoriesRole,
|
migrateToDefaultPrompt,
|
} = useAdvancedPromptConfig({
|
appMode: mode,
|
modelName: modelConfig.model_id,
|
promptMode,
|
modelModeType,
|
prePrompt: modelConfig.configs.prompt_template,
|
hasSetDataSet: dataSets.length > 0,
|
onUserChangedPrompt: () => {
|
setCanReturnToSimpleMode(false)
|
},
|
completionParams,
|
setCompletionParams,
|
setStop: setTempStop,
|
})
|
const setModel = async ({
|
modelId,
|
provider,
|
mode: modeMode,
|
features,
|
}: { modelId: string; provider: string; mode: string; features: string[] }) => {
|
if (isAdvancedMode) {
|
const appMode = mode
|
|
if (modeMode === ModelModeType.completion) {
|
if (appMode !== AppType.completion) {
|
if (!completionPromptConfig.prompt?.text || !completionPromptConfig.conversation_histories_role.assistant_prefix || !completionPromptConfig.conversation_histories_role.user_prefix)
|
await migrateToDefaultPrompt(true, ModelModeType.completion)
|
}
|
else {
|
if (!completionPromptConfig.prompt?.text)
|
await migrateToDefaultPrompt(true, ModelModeType.completion)
|
}
|
}
|
if (modeMode === ModelModeType.chat) {
|
if (chatPromptConfig.prompt.length === 0)
|
await migrateToDefaultPrompt(true, ModelModeType.chat)
|
}
|
}
|
const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
|
draft.provider = provider
|
draft.model_id = modelId
|
draft.mode = modeMode as ModelModeType
|
})
|
|
setModelConfig(newModelConfig)
|
const supportVision = features && features.includes(ModelFeatureEnum.vision)
|
|
handleSetVisionConfig({
|
...visionConfig,
|
enabled: supportVision,
|
}, true)
|
setCompletionParams({})
|
}
|
|
const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
|
const isShowDocumentConfig = !!currModel?.features?.includes(ModelFeatureEnum.document)
|
const isAllowVideoUpload = !!currModel?.features?.includes(ModelFeatureEnum.video)
|
// *** web app features ***
|
const featuresData: FeaturesData = useMemo(() => {
|
return {
|
moreLikeThis: modelConfig.more_like_this || { enabled: false },
|
opening: {
|
enabled: !!modelConfig.opening_statement,
|
opening_statement: modelConfig.opening_statement || '',
|
suggested_questions: modelConfig.suggested_questions || [],
|
},
|
moderation: modelConfig.sensitive_word_avoidance || { enabled: false },
|
speech2text: modelConfig.speech_to_text || { enabled: false },
|
text2speech: modelConfig.text_to_speech || { enabled: false },
|
file: {
|
image: {
|
detail: modelConfig.file_upload?.image?.detail || Resolution.high,
|
enabled: !!modelConfig.file_upload?.image?.enabled,
|
number_limits: modelConfig.file_upload?.image?.number_limits || 3,
|
transfer_methods: modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
|
},
|
enabled: !!(modelConfig.file_upload?.enabled || modelConfig.file_upload?.image?.enabled),
|
allowed_file_types: modelConfig.file_upload?.allowed_file_types || [],
|
allowed_file_extensions: modelConfig.file_upload?.allowed_file_extensions || [...FILE_EXTS[SupportUploadFileTypes.image], ...FILE_EXTS[SupportUploadFileTypes.video]].map(ext => `.${ext}`),
|
allowed_file_upload_methods: modelConfig.file_upload?.allowed_file_upload_methods || modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
|
number_limits: modelConfig.file_upload?.number_limits || modelConfig.file_upload?.image?.number_limits || 3,
|
fileUploadConfig: fileUploadConfigResponse,
|
} as FileUpload,
|
suggested: modelConfig.suggested_questions_after_answer || { enabled: false },
|
citation: modelConfig.retriever_resource || { enabled: false },
|
annotationReply: modelConfig.annotation_reply || { enabled: false },
|
}
|
}, [fileUploadConfigResponse, modelConfig])
|
const handleFeaturesChange = useCallback((flag: any) => {
|
setShowAppConfigureFeaturesModal(true)
|
if (flag)
|
formattingChangedDispatcher()
|
}, [formattingChangedDispatcher, setShowAppConfigureFeaturesModal])
|
const handleAddPromptVariable = useCallback((variable: PromptVariable[]) => {
|
const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
|
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...variable]
|
})
|
setModelConfig(newModelConfig)
|
}, [modelConfig])
|
|
useEffect(() => {
|
(async () => {
|
const collectionList = await fetchCollectionList()
|
if (basePath) {
|
collectionList.forEach((item) => {
|
if (typeof item.icon == 'string' && !item.icon.includes(basePath))
|
item.icon = `${basePath}${item.icon}`
|
})
|
}
|
setCollectionList(collectionList)
|
fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => {
|
setMode(res.mode)
|
const modelConfig = res.model_config
|
const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple
|
doSetPromptMode(promptMode)
|
if (promptMode === PromptMode.advanced) {
|
if (modelConfig.chat_prompt_config && modelConfig.chat_prompt_config.prompt.length > 0)
|
setChatPromptConfig(modelConfig.chat_prompt_config)
|
else
|
setChatPromptConfig(clone(DEFAULT_CHAT_PROMPT_CONFIG))
|
setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any)
|
setCanReturnToSimpleMode(false)
|
}
|
|
const model = res.model_config.model
|
|
let datasets: any = null
|
// old dataset struct
|
if (modelConfig.agent_mode?.tools?.find(({ dataset }: any) => dataset?.enabled))
|
datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled)
|
// new dataset struct
|
else if (modelConfig.dataset_configs.datasets?.datasets?.length > 0)
|
datasets = modelConfig.dataset_configs?.datasets?.datasets
|
|
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)
|
setSuggestedQuestions(modelConfig.suggested_questions || [])
|
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.text_to_speech)
|
setTextToSpeechConfig(modelConfig.text_to_speech)
|
|
if (modelConfig.retriever_resource)
|
setCitationConfig(modelConfig.retriever_resource)
|
|
if (modelConfig.annotation_reply) {
|
let annotationConfig = modelConfig.annotation_reply
|
if (modelConfig.annotation_reply.enabled) {
|
annotationConfig = {
|
...modelConfig.annotation_reply,
|
embedding_model: {
|
...modelConfig.annotation_reply.embedding_model,
|
embedding_provider_name: correctModelProvider(modelConfig.annotation_reply.embedding_model.embedding_provider_name),
|
},
|
}
|
}
|
setAnnotationConfig(annotationConfig, true)
|
}
|
|
if (modelConfig.sensitive_word_avoidance)
|
setModerationConfig(modelConfig.sensitive_word_avoidance)
|
|
if (modelConfig.external_data_tools)
|
setExternalDataToolsConfig(modelConfig.external_data_tools)
|
|
const config = {
|
modelConfig: {
|
provider: correctModelProvider(model.provider),
|
model_id: model.name,
|
mode: model.mode,
|
configs: {
|
prompt_template: modelConfig.pre_prompt || '',
|
prompt_variables: userInputsFormToPromptVariables(
|
[
|
...modelConfig.user_input_form,
|
...(
|
modelConfig.external_data_tools?.length
|
? modelConfig.external_data_tools.map((item: any) => {
|
return {
|
external_data_tool: {
|
variable: item.variable as string,
|
label: item.label as string,
|
enabled: item.enabled,
|
type: item.type as string,
|
config: item.config,
|
required: true,
|
icon: item.icon,
|
icon_background: item.icon_background,
|
},
|
}
|
})
|
: []
|
),
|
],
|
modelConfig.dataset_query_variable,
|
),
|
},
|
more_like_this: modelConfig.more_like_this,
|
opening_statement: modelConfig.opening_statement,
|
suggested_questions: modelConfig.suggested_questions,
|
sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
|
speech_to_text: modelConfig.speech_to_text,
|
text_to_speech: modelConfig.text_to_speech,
|
file_upload: modelConfig.file_upload,
|
suggested_questions_after_answer: modelConfig.suggested_questions_after_answer,
|
retriever_resource: modelConfig.retriever_resource,
|
annotation_reply: modelConfig.annotation_reply,
|
external_data_tools: modelConfig.external_data_tools,
|
dataSets: datasets || [],
|
agentConfig: res.mode === 'agent-chat' ? {
|
max_iteration: DEFAULT_AGENT_SETTING.max_iteration,
|
...modelConfig.agent_mode,
|
// remove dataset
|
enabled: true, // modelConfig.agent_mode?.enabled is not correct. old app: the value of app with dataset's is always true
|
tools: modelConfig.agent_mode?.tools.filter((tool: any) => {
|
return !tool.dataset
|
}).map((tool: any) => {
|
const toolInCollectionList = collectionList.find(c => tool.provider_id === c.id)
|
return {
|
...tool,
|
isDeleted: res.deleted_tools?.some((deletedTool: any) => deletedTool.id === tool.id && deletedTool.tool_name === tool.tool_name),
|
notAuthor: toolInCollectionList?.is_team_authorization === false,
|
...(tool.provider_type === 'builtin' ? {
|
provider_id: correctToolProvider(tool.provider_name, !!toolInCollectionList),
|
provider_name: correctToolProvider(tool.provider_name, !!toolInCollectionList),
|
} : {}),
|
}
|
}),
|
} : DEFAULT_AGENT_SETTING,
|
},
|
completionParams: model.completion_params,
|
}
|
|
if (modelConfig.file_upload)
|
handleSetVisionConfig(modelConfig.file_upload.image, true)
|
|
syncToPublishedConfig(config)
|
setPublishedConfig(config)
|
const retrievalConfig = getMultipleRetrievalConfig({
|
...modelConfig.dataset_configs,
|
reranking_model: modelConfig.dataset_configs.reranking_model && {
|
provider: modelConfig.dataset_configs.reranking_model.reranking_provider_name,
|
model: modelConfig.dataset_configs.reranking_model.reranking_model_name,
|
},
|
}, datasets, datasets, {
|
provider: currentRerankProvider?.provider,
|
model: currentRerankModel?.model,
|
})
|
setDatasetConfigs({
|
retrieval_model: RETRIEVE_TYPE.multiWay,
|
...modelConfig.dataset_configs,
|
...retrievalConfig,
|
...(retrievalConfig.reranking_model ? {
|
reranking_model: {
|
reranking_model_name: retrievalConfig.reranking_model.model,
|
reranking_provider_name: correctModelProvider(retrievalConfig.reranking_model.provider),
|
},
|
} : {}),
|
})
|
setHasFetchedDetail(true)
|
})
|
})()
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [appId])
|
|
const promptEmpty = (() => {
|
if (mode !== AppType.completion)
|
return false
|
|
if (isAdvancedMode) {
|
if (modelModeType === ModelModeType.chat)
|
return chatPromptConfig.prompt.every(({ text }: any) => !text)
|
|
else
|
return !completionPromptConfig.prompt?.text
|
}
|
|
else { return !modelConfig.configs.prompt_template }
|
})()
|
const cannotPublish = (() => {
|
if (mode !== AppType.completion) {
|
if (!isAdvancedMode)
|
return false
|
|
if (modelModeType === ModelModeType.completion) {
|
if (!hasSetBlockStatus.history || !hasSetBlockStatus.query)
|
return true
|
|
return false
|
}
|
|
return false
|
}
|
else { return promptEmpty }
|
})()
|
const contextVarEmpty = mode === AppType.completion && dataSets.length > 0 && !hasSetContextVar
|
const onPublish = async (modelAndParameter?: ModelAndParameter, features?: FeaturesData) => {
|
const modelId = modelAndParameter?.model || modelConfig.model_id
|
const promptTemplate = modelConfig.configs.prompt_template
|
const promptVariables = modelConfig.configs.prompt_variables
|
|
if (promptEmpty) {
|
notify({ type: 'error', message: t('appDebug.otherError.promptNoBeEmpty') })
|
return
|
}
|
if (isAdvancedMode && mode !== AppType.completion) {
|
if (modelModeType === ModelModeType.completion) {
|
if (!hasSetBlockStatus.history) {
|
notify({ type: 'error', message: t('appDebug.otherError.historyNoBeEmpty') })
|
return
|
}
|
if (!hasSetBlockStatus.query) {
|
notify({ type: 'error', message: t('appDebug.otherError.queryNoBeEmpty') })
|
return
|
}
|
}
|
}
|
if (contextVarEmpty) {
|
notify({ type: 'error', message: t('appDebug.feature.dataSet.queryVariable.contextVarNotEmpty') })
|
return
|
}
|
const postDatasets = dataSets.map(({ id }) => ({
|
dataset: {
|
enabled: true,
|
id,
|
},
|
}))
|
|
const fileUpload = { ...features?.file }
|
delete fileUpload?.fileUploadConfig
|
|
// new model config data struct
|
const data: BackendModelConfig = {
|
// Simple Mode prompt
|
pre_prompt: !isAdvancedMode ? promptTemplate : '',
|
prompt_type: promptMode,
|
chat_prompt_config: {},
|
completion_prompt_config: {},
|
user_input_form: promptVariablesToUserInputsForm(promptVariables),
|
dataset_query_variable: contextVar || '',
|
// features
|
more_like_this: features?.moreLikeThis as any,
|
opening_statement: features?.opening?.enabled ? (features.opening?.opening_statement || '') : '',
|
suggested_questions: features?.opening?.enabled ? (features.opening?.suggested_questions || []) : [],
|
sensitive_word_avoidance: features?.moderation as any,
|
speech_to_text: features?.speech2text as any,
|
text_to_speech: features?.text2speech as any,
|
file_upload: fileUpload as any,
|
suggested_questions_after_answer: features?.suggested as any,
|
retriever_resource: features?.citation as any,
|
agent_mode: {
|
...modelConfig.agentConfig,
|
strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
|
},
|
model: {
|
provider: modelAndParameter?.provider || modelConfig.provider,
|
name: modelId,
|
mode: modelConfig.mode,
|
completion_params: modelAndParameter?.parameters || completionParams as any,
|
},
|
dataset_configs: {
|
...datasetConfigs,
|
datasets: {
|
datasets: [...postDatasets],
|
} as any,
|
},
|
}
|
|
if (isAdvancedMode) {
|
data.chat_prompt_config = chatPromptConfig
|
data.completion_prompt_config = completionPromptConfig
|
}
|
|
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.text_to_speech = textToSpeechConfig
|
draft.retriever_resource = citationConfig
|
draft.dataSets = dataSets
|
})
|
setPublishedConfig({
|
modelConfig: newModelConfig,
|
completionParams,
|
})
|
notify({ type: 'success', message: t('common.api.success') })
|
|
setCanReturnToSimpleMode(false)
|
return true
|
}
|
|
const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false)
|
|
const {
|
debugWithMultipleModel,
|
multipleModelConfigs,
|
handleMultipleModelConfigsChange,
|
} = useDebugWithSingleOrMultipleModel(appId)
|
|
const handleDebugWithMultipleModelChange = () => {
|
handleMultipleModelConfigsChange(
|
true,
|
[
|
{ id: `${Date.now()}`, model: modelConfig.model_id, provider: modelConfig.provider, parameters: completionParams },
|
{ id: `${Date.now()}-no-repeat`, model: '', provider: '', parameters: {} },
|
],
|
)
|
setAppSiderbarExpand('collapse')
|
}
|
|
if (isLoading) {
|
return <div className='flex h-full items-center justify-center'>
|
<Loading type='area' />
|
</div>
|
}
|
|
return (
|
<ConfigContext.Provider value={{
|
appId,
|
isAPIKeySet,
|
isTrailFinished: false,
|
mode,
|
modelModeType,
|
promptMode,
|
isAdvancedMode,
|
isAgent,
|
isOpenAI,
|
isFunctionCall,
|
collectionList,
|
setPromptMode,
|
canReturnToSimpleMode,
|
setCanReturnToSimpleMode,
|
chatPromptConfig,
|
completionPromptConfig,
|
currentAdvancedPrompt,
|
setCurrentAdvancedPrompt,
|
conversationHistoriesRole: completionPromptConfig.conversation_histories_role,
|
showHistoryModal,
|
setConversationHistoriesRole,
|
hasSetBlockStatus,
|
conversationId,
|
introduction,
|
setIntroduction,
|
suggestedQuestions,
|
setSuggestedQuestions,
|
setConversationId,
|
controlClearChatMessage,
|
setControlClearChatMessage,
|
prevPromptConfig,
|
setPrevPromptConfig,
|
moreLikeThisConfig,
|
setMoreLikeThisConfig,
|
suggestedQuestionsAfterAnswerConfig,
|
setSuggestedQuestionsAfterAnswerConfig,
|
speechToTextConfig,
|
setSpeechToTextConfig,
|
textToSpeechConfig,
|
setTextToSpeechConfig,
|
citationConfig,
|
setCitationConfig,
|
annotationConfig,
|
setAnnotationConfig,
|
moderationConfig,
|
setModerationConfig,
|
externalDataToolsConfig,
|
setExternalDataToolsConfig,
|
formattingChanged,
|
setFormattingChanged,
|
inputs,
|
setInputs,
|
query,
|
setQuery,
|
completionParams,
|
setCompletionParams,
|
modelConfig,
|
setModelConfig,
|
showSelectDataSet,
|
dataSets,
|
setDataSets,
|
datasetConfigs,
|
datasetConfigsRef,
|
setDatasetConfigs,
|
hasSetContextVar,
|
isShowVisionConfig,
|
visionConfig,
|
setVisionConfig: handleSetVisionConfig,
|
isAllowVideoUpload,
|
isShowDocumentConfig,
|
rerankSettingModalOpen,
|
setRerankSettingModalOpen,
|
}}
|
>
|
<FeaturesProvider features={featuresData}>
|
<>
|
<div className="flex h-full flex-col">
|
<div className='relative flex h-[200px] grow pt-14'>
|
{/* Header */}
|
<div className='bg-default-subtle absolute left-0 top-0 h-14 w-full'>
|
<div className='flex h-14 items-center justify-between px-6'>
|
<div className='flex items-center'>
|
<div className='system-xl-semibold text-text-primary'>{t('appDebug.orchestrate')}</div>
|
<div className='flex h-[14px] items-center space-x-1 text-xs'>
|
{isAdvancedMode && (
|
<div className='system-xs-medium-uppercase ml-1 flex h-5 items-center rounded-md border border-components-button-secondary-border px-1.5 uppercase text-text-tertiary'>{t('appDebug.promptMode.advanced')}</div>
|
)}
|
</div>
|
</div>
|
<div className='flex items-center'>
|
{/* Agent Setting */}
|
{isAgent && (
|
<AgentSettingButton
|
isChatModel={modelConfig.mode === ModelModeType.chat}
|
agentConfig={modelConfig.agentConfig}
|
|
isFunctionCall={isFunctionCall}
|
onAgentSettingChange={(config) => {
|
const nextConfig = produce(modelConfig, (draft: ModelConfig) => {
|
draft.agentConfig = config
|
})
|
setModelConfig(nextConfig)
|
}}
|
/>
|
)}
|
{/* Model and Parameters */}
|
{!debugWithMultipleModel && (
|
<>
|
<ModelParameterModal
|
isAdvancedMode={isAdvancedMode}
|
mode={mode}
|
provider={modelConfig.provider}
|
completionParams={completionParams}
|
modelId={modelConfig.model_id}
|
setModel={setModel as any}
|
onCompletionParamsChange={(newParams: FormValue) => {
|
setCompletionParams(newParams)
|
}}
|
debugWithMultipleModel={debugWithMultipleModel}
|
onDebugWithMultipleModelChange={handleDebugWithMultipleModelChange}
|
/>
|
<Divider type='vertical' className='mx-2 h-[14px]' />
|
</>
|
)}
|
{isMobile && (
|
<Button className='mr-2 !h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
|
<span className='mr-1'>{t('appDebug.operation.debugConfig')}</span>
|
<CodeBracketIcon className="h-4 w-4 text-text-tertiary" />
|
</Button>
|
)}
|
<AppPublisher {...{
|
publishDisabled: cannotPublish,
|
publishedAt: (latestPublishedAt || 0) * 1000,
|
debugWithMultipleModel,
|
multipleModelConfigs,
|
onPublish,
|
publishedConfig: publishedConfig!,
|
resetAppConfig: () => syncToPublishedConfig(publishedConfig!),
|
}} />
|
</div>
|
</div>
|
</div>
|
<div className={`flex h-full w-full shrink-0 flex-col sm:w-1/2 ${debugWithMultipleModel && 'max-w-[560px]'}`}>
|
<Config />
|
</div>
|
{!isMobile && <div className="relative flex h-full w-1/2 grow flex-col overflow-y-auto " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
|
<div className='flex grow flex-col rounded-tl-2xl border-l-[0.5px] border-t-[0.5px] border-components-panel-border bg-chatbot-bg '>
|
<Debug
|
isAPIKeySet={isAPIKeySet}
|
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
|
inputs={inputs}
|
modelParameterParams={{
|
setModel: setModel as any,
|
onCompletionParamsChange: setCompletionParams,
|
}}
|
debugWithMultipleModel={debugWithMultipleModel}
|
multipleModelConfigs={multipleModelConfigs}
|
onMultipleModelConfigsChange={handleMultipleModelConfigsChange}
|
/>
|
</div>
|
</div>}
|
</div>
|
</div>
|
{showUseGPT4Confirm && (
|
<Confirm
|
title={t('appDebug.trailUseGPT4Info.title')}
|
content={t('appDebug.trailUseGPT4Info.description')}
|
isShow={showUseGPT4Confirm}
|
onConfirm={() => {
|
setShowAccountSettingModal({ payload: 'provider' })
|
setShowUseGPT4Confirm(false)
|
}}
|
onCancel={() => setShowUseGPT4Confirm(false)}
|
/>
|
)}
|
|
{isShowSelectDataSet && (
|
<SelectDataSet
|
isShow={isShowSelectDataSet}
|
onClose={hideSelectDataSet}
|
selectedIds={selectedIds}
|
onSelect={handleSelect}
|
/>
|
)}
|
|
{isShowHistoryModal && (
|
<EditHistoryModal
|
isShow={isShowHistoryModal}
|
saveLoading={false}
|
onClose={hideHistoryModal}
|
data={completionPromptConfig.conversation_histories_role}
|
onSave={(data) => {
|
setConversationHistoriesRole(data)
|
hideHistoryModal()
|
}}
|
/>
|
)}
|
{isMobile && (
|
<Drawer showClose isOpen={isShowDebugPanel} onClose={hideDebugPanel} mask footer={null}>
|
<Debug
|
isAPIKeySet={isAPIKeySet}
|
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
|
inputs={inputs}
|
modelParameterParams={{
|
setModel: setModel as any,
|
onCompletionParamsChange: setCompletionParams,
|
}}
|
debugWithMultipleModel={debugWithMultipleModel}
|
multipleModelConfigs={multipleModelConfigs}
|
onMultipleModelConfigsChange={handleMultipleModelConfigsChange}
|
/>
|
</Drawer>
|
)}
|
{showAppConfigureFeaturesModal && (
|
<NewFeaturePanel
|
show
|
inWorkflow={false}
|
showFileUpload={false}
|
isChatMode={mode !== 'completion'}
|
disabled={false}
|
onChange={handleFeaturesChange}
|
onClose={() => setShowAppConfigureFeaturesModal(false)}
|
promptVariables={modelConfig.configs.prompt_variables}
|
onAutoAddPromptVariable={handleAddPromptVariable}
|
/>
|
)}
|
<PluginDependency />
|
</>
|
</FeaturesProvider>
|
</ConfigContext.Provider>
|
)
|
}
|
export default React.memo(Configuration)
|