index.tsx 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import { useContext } from 'use-context-selector'
  6. import { usePathname } from 'next/navigation'
  7. import produce from 'immer'
  8. import { useBoolean, useGetState } from 'ahooks'
  9. import { clone, isEqual } from 'lodash-es'
  10. import { CodeBracketIcon } from '@heroicons/react/20/solid'
  11. import { useShallow } from 'zustand/react/shallow'
  12. import AgentSettingButton from '@/app/components/app/configuration/config/agent-setting-button'
  13. import useAdvancedPromptConfig from '@/app/components/app/configuration/hooks/use-advanced-prompt-config'
  14. import EditHistoryModal from '@/app/components/app/configuration/config-prompt/conversation-history/edit-modal'
  15. import {
  16. useDebugWithSingleOrMultipleModel,
  17. useFormattingChangedDispatcher,
  18. } from '@/app/components/app/configuration/debug/hooks'
  19. import type { ModelAndParameter } from '@/app/components/app/configuration/debug/types'
  20. import Button from '@/app/components/base/button'
  21. import Loading from '@/app/components/base/loading'
  22. import AppPublisher from '@/app/components/app/app-publisher/features-wrapper'
  23. import type {
  24. AnnotationReplyConfig,
  25. DatasetConfigs,
  26. Inputs,
  27. ModelConfig,
  28. ModerationConfig,
  29. MoreLikeThisConfig,
  30. PromptConfig,
  31. PromptVariable,
  32. TextToSpeechConfig,
  33. } from '@/models/debug'
  34. import type { ExternalDataTool } from '@/models/common'
  35. import type { DataSet } from '@/models/datasets'
  36. import type { ModelConfig as BackendModelConfig, VisionSettings } from '@/types/app'
  37. import ConfigContext from '@/context/debug-configuration'
  38. import Config from '@/app/components/app/configuration/config'
  39. import Debug from '@/app/components/app/configuration/debug'
  40. import Confirm from '@/app/components/base/confirm'
  41. import { ModelFeatureEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
  42. import { ToastContext } from '@/app/components/base/toast'
  43. import { fetchAppDetail, updateAppModelConfig } from '@/service/apps'
  44. import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from '@/utils/model-config'
  45. import { fetchDatasets } from '@/service/datasets'
  46. import { useProviderContext } from '@/context/provider-context'
  47. import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app'
  48. import { PromptMode } from '@/models/debug'
  49. import { ANNOTATION_DEFAULT, DATASET_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
  50. import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset'
  51. import { useModalContext } from '@/context/modal-context'
  52. import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
  53. import Drawer from '@/app/components/base/drawer'
  54. import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
  55. import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
  56. import {
  57. useModelListAndDefaultModelAndCurrentProviderAndModel,
  58. useTextGenerationCurrentProviderAndModelAndModelList,
  59. } from '@/app/components/header/account-setting/model-provider-page/hooks'
  60. import { fetchCollectionList } from '@/service/tools'
  61. import { type Collection } from '@/app/components/tools/types'
  62. import { useStore as useAppStore } from '@/app/components/app/store'
  63. import {
  64. getMultipleRetrievalConfig,
  65. getSelectedDatasetsMode,
  66. } from '@/app/components/workflow/nodes/knowledge-retrieval/utils'
  67. import { FeaturesProvider } from '@/app/components/base/features'
  68. import type { Features as FeaturesData, FileUpload } from '@/app/components/base/features/types'
  69. import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
  70. import { SupportUploadFileTypes } from '@/app/components/workflow/types'
  71. import NewFeaturePanel from '@/app/components/base/features/new-feature-panel'
  72. type PublishConfig = {
  73. modelConfig: ModelConfig
  74. completionParams: FormValue
  75. }
  76. const Configuration: FC = () => {
  77. const { t } = useTranslation()
  78. const { notify } = useContext(ToastContext)
  79. const { appDetail, showAppConfigureFeaturesModal, setAppSiderbarExpand, setShowAppConfigureFeaturesModal } = useAppStore(useShallow(state => ({
  80. appDetail: state.appDetail,
  81. setAppSiderbarExpand: state.setAppSiderbarExpand,
  82. showAppConfigureFeaturesModal: state.showAppConfigureFeaturesModal,
  83. setShowAppConfigureFeaturesModal: state.setShowAppConfigureFeaturesModal,
  84. })))
  85. const latestPublishedAt = useMemo(() => appDetail?.model_config.updated_at, [appDetail])
  86. const [formattingChanged, setFormattingChanged] = useState(false)
  87. const { setShowAccountSettingModal } = useModalContext()
  88. const [hasFetchedDetail, setHasFetchedDetail] = useState(false)
  89. const isLoading = !hasFetchedDetail
  90. const pathname = usePathname()
  91. const matched = pathname.match(/\/app\/([^/]+)/)
  92. const appId = (matched?.length && matched[1]) ? matched[1] : ''
  93. const [mode, setMode] = useState('')
  94. const [publishedConfig, setPublishedConfig] = useState<PublishConfig | null>(null)
  95. const [conversationId, setConversationId] = useState<string | null>('')
  96. const media = useBreakpoints()
  97. const isMobile = media === MediaType.mobile
  98. const [isShowDebugPanel, { setTrue: showDebugPanel, setFalse: hideDebugPanel }] = useBoolean(false)
  99. const [introduction, setIntroduction] = useState<string>('')
  100. const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([])
  101. const [controlClearChatMessage, setControlClearChatMessage] = useState(0)
  102. const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({
  103. prompt_template: '',
  104. prompt_variables: [],
  105. })
  106. const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig>({
  107. enabled: false,
  108. })
  109. const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<MoreLikeThisConfig>({
  110. enabled: false,
  111. })
  112. const [speechToTextConfig, setSpeechToTextConfig] = useState<MoreLikeThisConfig>({
  113. enabled: false,
  114. })
  115. const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig>({
  116. enabled: false,
  117. voice: '',
  118. language: '',
  119. })
  120. const [citationConfig, setCitationConfig] = useState<MoreLikeThisConfig>({
  121. enabled: false,
  122. })
  123. const [annotationConfig, doSetAnnotationConfig] = useState<AnnotationReplyConfig>({
  124. id: '',
  125. enabled: false,
  126. score_threshold: ANNOTATION_DEFAULT.score_threshold,
  127. embedding_model: {
  128. embedding_provider_name: '',
  129. embedding_model_name: '',
  130. },
  131. })
  132. const formattingChangedDispatcher = useFormattingChangedDispatcher()
  133. const setAnnotationConfig = (config: AnnotationReplyConfig, notSetFormatChanged?: boolean) => {
  134. doSetAnnotationConfig(config)
  135. if (!notSetFormatChanged)
  136. formattingChangedDispatcher()
  137. }
  138. const [moderationConfig, setModerationConfig] = useState<ModerationConfig>({
  139. enabled: false,
  140. })
  141. const [externalDataToolsConfig, setExternalDataToolsConfig] = useState<ExternalDataTool[]>([])
  142. const [inputs, setInputs] = useState<Inputs>({})
  143. const [query, setQuery] = useState('')
  144. const [completionParams, doSetCompletionParams] = useState<FormValue>({})
  145. const [_, setTempStop, getTempStop] = useGetState<string[]>([])
  146. const setCompletionParams = (value: FormValue) => {
  147. const params = { ...value }
  148. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  149. if ((!params.stop || params.stop.length === 0) && (modeModeTypeRef.current === ModelModeType.completion)) {
  150. params.stop = getTempStop()
  151. setTempStop([])
  152. }
  153. doSetCompletionParams(params)
  154. }
  155. const [modelConfig, doSetModelConfig] = useState<ModelConfig>({
  156. provider: 'openai',
  157. model_id: 'gpt-3.5-turbo',
  158. mode: ModelModeType.unset,
  159. configs: {
  160. prompt_template: '',
  161. prompt_variables: [] as PromptVariable[],
  162. },
  163. more_like_this: null,
  164. opening_statement: '',
  165. suggested_questions: [],
  166. sensitive_word_avoidance: null,
  167. speech_to_text: null,
  168. text_to_speech: null,
  169. file_upload: null,
  170. suggested_questions_after_answer: null,
  171. retriever_resource: null,
  172. annotation_reply: null,
  173. dataSets: [],
  174. agentConfig: DEFAULT_AGENT_SETTING,
  175. })
  176. const isAgent = mode === 'agent-chat'
  177. const isOpenAI = modelConfig.provider === 'openai'
  178. const [collectionList, setCollectionList] = useState<Collection[]>([])
  179. useEffect(() => {
  180. }, [])
  181. const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>({
  182. retrieval_model: RETRIEVE_TYPE.multiWay,
  183. reranking_model: {
  184. reranking_provider_name: '',
  185. reranking_model_name: '',
  186. },
  187. top_k: DATASET_DEFAULT.top_k,
  188. score_threshold_enabled: false,
  189. score_threshold: DATASET_DEFAULT.score_threshold,
  190. datasets: {
  191. datasets: [],
  192. },
  193. })
  194. const setModelConfig = (newModelConfig: ModelConfig) => {
  195. doSetModelConfig(newModelConfig)
  196. }
  197. const modelModeType = modelConfig.mode
  198. const modeModeTypeRef = useRef(modelModeType)
  199. useEffect(() => {
  200. modeModeTypeRef.current = modelModeType
  201. }, [modelModeType])
  202. const [dataSets, setDataSets] = useState<DataSet[]>([])
  203. const contextVar = modelConfig.configs.prompt_variables.find((item: any) => item.is_context_var)?.key
  204. const hasSetContextVar = !!contextVar
  205. const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false)
  206. const selectedIds = dataSets.map(item => item.id)
  207. const [rerankSettingModalOpen, setRerankSettingModalOpen] = useState(false)
  208. const {
  209. currentModel: currentRerankModel,
  210. } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
  211. const handleSelect = (data: DataSet[]) => {
  212. if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) {
  213. hideSelectDataSet()
  214. return
  215. }
  216. formattingChangedDispatcher()
  217. let newDatasets = data
  218. if (data.find(item => !item.name)) { // has not loaded selected dataset
  219. const newSelected = produce(data, (draft: any) => {
  220. data.forEach((item, index) => {
  221. if (!item.name) { // not fetched database
  222. const newItem = dataSets.find(i => i.id === item.id)
  223. if (newItem)
  224. draft[index] = newItem
  225. }
  226. })
  227. })
  228. setDataSets(newSelected)
  229. newDatasets = newSelected
  230. }
  231. else {
  232. setDataSets(data)
  233. }
  234. hideSelectDataSet()
  235. const {
  236. allEconomic,
  237. mixtureHighQualityAndEconomic,
  238. inconsistentEmbeddingModel,
  239. } = getSelectedDatasetsMode(newDatasets)
  240. if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel)
  241. setRerankSettingModalOpen(true)
  242. const { datasets, retrieval_model, score_threshold_enabled, ...restConfigs } = datasetConfigs
  243. const retrievalConfig = getMultipleRetrievalConfig({
  244. top_k: restConfigs.top_k,
  245. score_threshold: restConfigs.score_threshold,
  246. reranking_model: restConfigs.reranking_model && {
  247. provider: restConfigs.reranking_model.reranking_provider_name,
  248. model: restConfigs.reranking_model.reranking_model_name,
  249. },
  250. reranking_mode: restConfigs.reranking_mode,
  251. weights: restConfigs.weights,
  252. reranking_enable: restConfigs.reranking_enable,
  253. }, newDatasets, dataSets, !!currentRerankModel)
  254. setDatasetConfigs({
  255. ...retrievalConfig,
  256. reranking_model: restConfigs.reranking_model && {
  257. reranking_provider_name: restConfigs.reranking_model.reranking_provider_name,
  258. reranking_model_name: restConfigs.reranking_model.reranking_model_name,
  259. },
  260. retrieval_model,
  261. score_threshold_enabled,
  262. datasets,
  263. })
  264. }
  265. const [isShowHistoryModal, { setTrue: showHistoryModal, setFalse: hideHistoryModal }] = useBoolean(false)
  266. const syncToPublishedConfig = (_publishedConfig: PublishConfig) => {
  267. const modelConfig = _publishedConfig.modelConfig
  268. setModelConfig(_publishedConfig.modelConfig)
  269. setCompletionParams(_publishedConfig.completionParams)
  270. setDataSets(modelConfig.dataSets || [])
  271. // reset feature
  272. setIntroduction(modelConfig.opening_statement!)
  273. setMoreLikeThisConfig(modelConfig.more_like_this || {
  274. enabled: false,
  275. })
  276. setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer || {
  277. enabled: false,
  278. })
  279. setSpeechToTextConfig(modelConfig.speech_to_text || {
  280. enabled: false,
  281. })
  282. setTextToSpeechConfig(modelConfig.text_to_speech || {
  283. enabled: false,
  284. voice: '',
  285. language: '',
  286. })
  287. setCitationConfig(modelConfig.retriever_resource || {
  288. enabled: false,
  289. })
  290. }
  291. const { isAPIKeySet } = useProviderContext()
  292. const {
  293. currentModel: currModel,
  294. textGenerationModelList,
  295. } = useTextGenerationCurrentProviderAndModelAndModelList(
  296. {
  297. provider: modelConfig.provider,
  298. model: modelConfig.model_id,
  299. },
  300. )
  301. const isFunctionCall = (() => {
  302. const features = currModel?.features
  303. if (!features)
  304. return false
  305. return features.includes(ModelFeatureEnum.toolCall) || features.includes(ModelFeatureEnum.multiToolCall)
  306. })()
  307. // Fill old app data missing model mode.
  308. useEffect(() => {
  309. if (hasFetchedDetail && !modelModeType) {
  310. const mode = currModel?.model_properties.mode as (ModelModeType | undefined)
  311. if (mode) {
  312. const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
  313. draft.mode = mode
  314. })
  315. setModelConfig(newModelConfig)
  316. }
  317. }
  318. }, [textGenerationModelList, hasFetchedDetail, modelModeType, currModel, modelConfig])
  319. const [promptMode, doSetPromptMode] = useState(PromptMode.simple)
  320. const isAdvancedMode = promptMode === PromptMode.advanced
  321. const [canReturnToSimpleMode, setCanReturnToSimpleMode] = useState(true)
  322. const setPromptMode = async (mode: PromptMode) => {
  323. if (mode === PromptMode.advanced) {
  324. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  325. await migrateToDefaultPrompt()
  326. setCanReturnToSimpleMode(true)
  327. }
  328. doSetPromptMode(mode)
  329. }
  330. const [visionConfig, doSetVisionConfig] = useState({
  331. enabled: false,
  332. number_limits: 2,
  333. detail: Resolution.low,
  334. transfer_methods: [TransferMethod.local_file],
  335. })
  336. const handleSetVisionConfig = (config: VisionSettings, notNoticeFormattingChanged?: boolean) => {
  337. doSetVisionConfig({
  338. enabled: config.enabled || false,
  339. number_limits: config.number_limits || 2,
  340. detail: config.detail || Resolution.low,
  341. transfer_methods: config.transfer_methods || [TransferMethod.local_file],
  342. })
  343. if (!notNoticeFormattingChanged)
  344. formattingChangedDispatcher()
  345. }
  346. const {
  347. chatPromptConfig,
  348. setChatPromptConfig,
  349. completionPromptConfig,
  350. setCompletionPromptConfig,
  351. currentAdvancedPrompt,
  352. setCurrentAdvancedPrompt,
  353. hasSetBlockStatus,
  354. setConversationHistoriesRole,
  355. migrateToDefaultPrompt,
  356. } = useAdvancedPromptConfig({
  357. appMode: mode,
  358. modelName: modelConfig.model_id,
  359. promptMode,
  360. modelModeType,
  361. prePrompt: modelConfig.configs.prompt_template,
  362. hasSetDataSet: dataSets.length > 0,
  363. onUserChangedPrompt: () => {
  364. setCanReturnToSimpleMode(false)
  365. },
  366. completionParams,
  367. setCompletionParams,
  368. setStop: setTempStop,
  369. })
  370. const setModel = async ({
  371. modelId,
  372. provider,
  373. mode: modeMode,
  374. features,
  375. }: { modelId: string; provider: string; mode: string; features: string[] }) => {
  376. if (isAdvancedMode) {
  377. const appMode = mode
  378. if (modeMode === ModelModeType.completion) {
  379. if (appMode !== AppType.completion) {
  380. if (!completionPromptConfig.prompt?.text || !completionPromptConfig.conversation_histories_role.assistant_prefix || !completionPromptConfig.conversation_histories_role.user_prefix)
  381. await migrateToDefaultPrompt(true, ModelModeType.completion)
  382. }
  383. else {
  384. if (!completionPromptConfig.prompt?.text)
  385. await migrateToDefaultPrompt(true, ModelModeType.completion)
  386. }
  387. }
  388. if (modeMode === ModelModeType.chat) {
  389. if (chatPromptConfig.prompt.length === 0)
  390. await migrateToDefaultPrompt(true, ModelModeType.chat)
  391. }
  392. }
  393. const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
  394. draft.provider = provider
  395. draft.model_id = modelId
  396. draft.mode = modeMode as ModelModeType
  397. })
  398. setModelConfig(newModelConfig)
  399. const supportVision = features && features.includes(ModelFeatureEnum.vision)
  400. handleSetVisionConfig({
  401. ...visionConfig,
  402. enabled: supportVision,
  403. }, true)
  404. setCompletionParams({})
  405. }
  406. const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
  407. // *** web app features ***
  408. const featuresData: FeaturesData = useMemo(() => {
  409. return {
  410. moreLikeThis: modelConfig.more_like_this || { enabled: false },
  411. opening: {
  412. enabled: !!modelConfig.opening_statement,
  413. opening_statement: modelConfig.opening_statement || '',
  414. suggested_questions: modelConfig.suggested_questions || [],
  415. },
  416. moderation: modelConfig.sensitive_word_avoidance || { enabled: false },
  417. speech2text: modelConfig.speech_to_text || { enabled: false },
  418. text2speech: modelConfig.text_to_speech || { enabled: false },
  419. file: {
  420. image: {
  421. detail: modelConfig.file_upload?.image?.detail || Resolution.high,
  422. enabled: !!modelConfig.file_upload?.image?.enabled,
  423. number_limits: modelConfig.file_upload?.image?.number_limits || 3,
  424. transfer_methods: modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
  425. },
  426. enabled: !!(modelConfig.file_upload?.enabled || modelConfig.file_upload?.image?.enabled),
  427. allowed_file_types: modelConfig.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
  428. allowed_file_extensions: modelConfig.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
  429. allowed_file_upload_methods: modelConfig.file_upload?.allowed_file_upload_methods || modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
  430. number_limits: modelConfig.file_upload?.number_limits || modelConfig.file_upload?.image?.number_limits || 3,
  431. } as FileUpload,
  432. suggested: modelConfig.suggested_questions_after_answer || { enabled: false },
  433. citation: modelConfig.retriever_resource || { enabled: false },
  434. annotationReply: modelConfig.annotation_reply || { enabled: false },
  435. }
  436. }, [modelConfig])
  437. const handleFeaturesChange = useCallback((flag: any) => {
  438. setShowAppConfigureFeaturesModal(true)
  439. if (flag)
  440. formattingChangedDispatcher()
  441. }, [formattingChangedDispatcher, setShowAppConfigureFeaturesModal])
  442. const handleAddPromptVariable = useCallback((variable: PromptVariable[]) => {
  443. const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
  444. draft.configs.prompt_variables = variable
  445. })
  446. setModelConfig(newModelConfig)
  447. }, [modelConfig])
  448. useEffect(() => {
  449. (async () => {
  450. const collectionList = await fetchCollectionList()
  451. setCollectionList(collectionList)
  452. fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => {
  453. setMode(res.mode)
  454. const modelConfig = res.model_config
  455. const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple
  456. doSetPromptMode(promptMode)
  457. if (promptMode === PromptMode.advanced) {
  458. if (modelConfig.chat_prompt_config && modelConfig.chat_prompt_config.prompt.length > 0)
  459. setChatPromptConfig(modelConfig.chat_prompt_config)
  460. else
  461. setChatPromptConfig(clone(DEFAULT_CHAT_PROMPT_CONFIG) as any)
  462. setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any)
  463. setCanReturnToSimpleMode(false)
  464. }
  465. const model = res.model_config.model
  466. let datasets: any = null
  467. // old dataset struct
  468. if (modelConfig.agent_mode?.tools?.find(({ dataset }: any) => dataset?.enabled))
  469. datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled)
  470. // new dataset struct
  471. else if (modelConfig.dataset_configs.datasets?.datasets?.length > 0)
  472. datasets = modelConfig.dataset_configs?.datasets?.datasets
  473. if (dataSets && datasets?.length && datasets?.length > 0) {
  474. const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } })
  475. datasets = dataSetsWithDetail
  476. setDataSets(datasets)
  477. }
  478. setIntroduction(modelConfig.opening_statement)
  479. setSuggestedQuestions(modelConfig.suggested_questions || [])
  480. if (modelConfig.more_like_this)
  481. setMoreLikeThisConfig(modelConfig.more_like_this)
  482. if (modelConfig.suggested_questions_after_answer)
  483. setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer)
  484. if (modelConfig.speech_to_text)
  485. setSpeechToTextConfig(modelConfig.speech_to_text)
  486. if (modelConfig.text_to_speech)
  487. setTextToSpeechConfig(modelConfig.text_to_speech)
  488. if (modelConfig.retriever_resource)
  489. setCitationConfig(modelConfig.retriever_resource)
  490. if (modelConfig.annotation_reply)
  491. setAnnotationConfig(modelConfig.annotation_reply, true)
  492. if (modelConfig.sensitive_word_avoidance)
  493. setModerationConfig(modelConfig.sensitive_word_avoidance)
  494. if (modelConfig.external_data_tools)
  495. setExternalDataToolsConfig(modelConfig.external_data_tools)
  496. const config = {
  497. modelConfig: {
  498. provider: model.provider,
  499. model_id: model.name,
  500. mode: model.mode,
  501. configs: {
  502. prompt_template: modelConfig.pre_prompt || '',
  503. prompt_variables: userInputsFormToPromptVariables(
  504. [
  505. ...modelConfig.user_input_form,
  506. ...(
  507. modelConfig.external_data_tools?.length
  508. ? modelConfig.external_data_tools.map((item: any) => {
  509. return {
  510. external_data_tool: {
  511. variable: item.variable as string,
  512. label: item.label as string,
  513. enabled: item.enabled,
  514. type: item.type as string,
  515. config: item.config,
  516. required: true,
  517. icon: item.icon,
  518. icon_background: item.icon_background,
  519. },
  520. }
  521. })
  522. : []
  523. ),
  524. ],
  525. modelConfig.dataset_query_variable,
  526. ),
  527. },
  528. more_like_this: modelConfig.more_like_this,
  529. opening_statement: modelConfig.opening_statement,
  530. suggested_questions: modelConfig.suggested_questions,
  531. sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
  532. speech_to_text: modelConfig.speech_to_text,
  533. text_to_speech: modelConfig.text_to_speech,
  534. file_upload: modelConfig.file_upload,
  535. suggested_questions_after_answer: modelConfig.suggested_questions_after_answer,
  536. retriever_resource: modelConfig.retriever_resource,
  537. annotation_reply: modelConfig.annotation_reply,
  538. external_data_tools: modelConfig.external_data_tools,
  539. dataSets: datasets || [],
  540. // eslint-disable-next-line multiline-ternary
  541. agentConfig: res.mode === 'agent-chat' ? {
  542. max_iteration: DEFAULT_AGENT_SETTING.max_iteration,
  543. ...modelConfig.agent_mode,
  544. // remove dataset
  545. enabled: true, // modelConfig.agent_mode?.enabled is not correct. old app: the value of app with dataset's is always true
  546. tools: modelConfig.agent_mode?.tools.filter((tool: any) => {
  547. return !tool.dataset
  548. }).map((tool: any) => {
  549. return {
  550. ...tool,
  551. isDeleted: res.deleted_tools?.includes(tool.tool_name),
  552. notAuthor: collectionList.find(c => tool.provider_id === c.id)?.is_team_authorization === false,
  553. }
  554. }),
  555. } : DEFAULT_AGENT_SETTING,
  556. },
  557. completionParams: model.completion_params,
  558. }
  559. if (modelConfig.file_upload)
  560. handleSetVisionConfig(modelConfig.file_upload.image, true)
  561. syncToPublishedConfig(config)
  562. setPublishedConfig(config)
  563. const retrievalConfig = getMultipleRetrievalConfig(modelConfig.dataset_configs, datasets, datasets, !!currentRerankModel)
  564. setDatasetConfigs({
  565. retrieval_model: RETRIEVE_TYPE.multiWay,
  566. ...modelConfig.dataset_configs,
  567. ...retrievalConfig,
  568. })
  569. setHasFetchedDetail(true)
  570. })
  571. })()
  572. // eslint-disable-next-line react-hooks/exhaustive-deps
  573. }, [appId])
  574. const promptEmpty = (() => {
  575. if (mode !== AppType.completion)
  576. return false
  577. if (isAdvancedMode) {
  578. if (modelModeType === ModelModeType.chat)
  579. return chatPromptConfig.prompt.every(({ text }: any) => !text)
  580. else
  581. return !completionPromptConfig.prompt?.text
  582. }
  583. else { return !modelConfig.configs.prompt_template }
  584. })()
  585. const cannotPublish = (() => {
  586. if (mode !== AppType.completion) {
  587. if (!isAdvancedMode)
  588. return false
  589. if (modelModeType === ModelModeType.completion) {
  590. if (!hasSetBlockStatus.history || !hasSetBlockStatus.query)
  591. return true
  592. return false
  593. }
  594. return false
  595. }
  596. else { return promptEmpty }
  597. })()
  598. const contextVarEmpty = mode === AppType.completion && dataSets.length > 0 && !hasSetContextVar
  599. const onPublish = async (modelAndParameter?: ModelAndParameter, features?: FeaturesData) => {
  600. const modelId = modelAndParameter?.model || modelConfig.model_id
  601. const promptTemplate = modelConfig.configs.prompt_template
  602. const promptVariables = modelConfig.configs.prompt_variables
  603. if (promptEmpty) {
  604. notify({ type: 'error', message: t('appDebug.otherError.promptNoBeEmpty') })
  605. return
  606. }
  607. if (isAdvancedMode && mode !== AppType.completion) {
  608. if (modelModeType === ModelModeType.completion) {
  609. if (!hasSetBlockStatus.history) {
  610. notify({ type: 'error', message: t('appDebug.otherError.historyNoBeEmpty') })
  611. return
  612. }
  613. if (!hasSetBlockStatus.query) {
  614. notify({ type: 'error', message: t('appDebug.otherError.queryNoBeEmpty') })
  615. return
  616. }
  617. }
  618. }
  619. if (contextVarEmpty) {
  620. notify({ type: 'error', message: t('appDebug.feature.dataSet.queryVariable.contextVarNotEmpty') })
  621. return
  622. }
  623. const postDatasets = dataSets.map(({ id }) => ({
  624. dataset: {
  625. enabled: true,
  626. id,
  627. },
  628. }))
  629. // new model config data struct
  630. const data: BackendModelConfig = {
  631. // Simple Mode prompt
  632. pre_prompt: !isAdvancedMode ? promptTemplate : '',
  633. prompt_type: promptMode,
  634. chat_prompt_config: {},
  635. completion_prompt_config: {},
  636. user_input_form: promptVariablesToUserInputsForm(promptVariables),
  637. dataset_query_variable: contextVar || '',
  638. // features
  639. more_like_this: features?.moreLikeThis as any,
  640. opening_statement: features?.opening?.enabled ? (features.opening?.opening_statement || '') : '',
  641. suggested_questions: features?.opening?.enabled ? (features.opening?.suggested_questions || []) : [],
  642. sensitive_word_avoidance: features?.moderation as any,
  643. speech_to_text: features?.speech2text as any,
  644. text_to_speech: features?.text2speech as any,
  645. file_upload: features?.file as any,
  646. suggested_questions_after_answer: features?.suggested as any,
  647. retriever_resource: features?.citation as any,
  648. agent_mode: {
  649. ...modelConfig.agentConfig,
  650. strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
  651. },
  652. model: {
  653. provider: modelAndParameter?.provider || modelConfig.provider,
  654. name: modelId,
  655. mode: modelConfig.mode,
  656. completion_params: modelAndParameter?.parameters || completionParams as any,
  657. },
  658. dataset_configs: {
  659. ...datasetConfigs,
  660. datasets: {
  661. datasets: [...postDatasets],
  662. } as any,
  663. },
  664. }
  665. if (isAdvancedMode) {
  666. data.chat_prompt_config = chatPromptConfig
  667. data.completion_prompt_config = completionPromptConfig
  668. }
  669. await updateAppModelConfig({ url: `/apps/${appId}/model-config`, body: data })
  670. const newModelConfig = produce(modelConfig, (draft: any) => {
  671. draft.opening_statement = introduction
  672. draft.more_like_this = moreLikeThisConfig
  673. draft.suggested_questions_after_answer = suggestedQuestionsAfterAnswerConfig
  674. draft.speech_to_text = speechToTextConfig
  675. draft.text_to_speech = textToSpeechConfig
  676. draft.retriever_resource = citationConfig
  677. draft.dataSets = dataSets
  678. })
  679. setPublishedConfig({
  680. modelConfig: newModelConfig,
  681. completionParams,
  682. })
  683. notify({ type: 'success', message: t('common.api.success') })
  684. setCanReturnToSimpleMode(false)
  685. return true
  686. }
  687. const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false)
  688. const {
  689. debugWithMultipleModel,
  690. multipleModelConfigs,
  691. handleMultipleModelConfigsChange,
  692. } = useDebugWithSingleOrMultipleModel(appId)
  693. const handleDebugWithMultipleModelChange = () => {
  694. handleMultipleModelConfigsChange(
  695. true,
  696. [
  697. { id: `${Date.now()}`, model: modelConfig.model_id, provider: modelConfig.provider, parameters: completionParams },
  698. { id: `${Date.now()}-no-repeat`, model: '', provider: '', parameters: {} },
  699. ],
  700. )
  701. setAppSiderbarExpand('collapse')
  702. }
  703. if (isLoading) {
  704. return <div className='flex items-center justify-center h-full'>
  705. <Loading type='area' />
  706. </div>
  707. }
  708. return (
  709. <ConfigContext.Provider value={{
  710. appId,
  711. isAPIKeySet,
  712. isTrailFinished: false,
  713. mode,
  714. modelModeType,
  715. promptMode,
  716. isAdvancedMode,
  717. isAgent,
  718. isOpenAI,
  719. isFunctionCall,
  720. collectionList,
  721. setPromptMode,
  722. canReturnToSimpleMode,
  723. setCanReturnToSimpleMode,
  724. chatPromptConfig,
  725. completionPromptConfig,
  726. currentAdvancedPrompt,
  727. setCurrentAdvancedPrompt,
  728. conversationHistoriesRole: completionPromptConfig.conversation_histories_role,
  729. showHistoryModal,
  730. setConversationHistoriesRole,
  731. hasSetBlockStatus,
  732. conversationId,
  733. introduction,
  734. setIntroduction,
  735. suggestedQuestions,
  736. setSuggestedQuestions,
  737. setConversationId,
  738. controlClearChatMessage,
  739. setControlClearChatMessage,
  740. prevPromptConfig,
  741. setPrevPromptConfig,
  742. moreLikeThisConfig,
  743. setMoreLikeThisConfig,
  744. suggestedQuestionsAfterAnswerConfig,
  745. setSuggestedQuestionsAfterAnswerConfig,
  746. speechToTextConfig,
  747. setSpeechToTextConfig,
  748. textToSpeechConfig,
  749. setTextToSpeechConfig,
  750. citationConfig,
  751. setCitationConfig,
  752. annotationConfig,
  753. setAnnotationConfig,
  754. moderationConfig,
  755. setModerationConfig,
  756. externalDataToolsConfig,
  757. setExternalDataToolsConfig,
  758. formattingChanged,
  759. setFormattingChanged,
  760. inputs,
  761. setInputs,
  762. query,
  763. setQuery,
  764. completionParams,
  765. setCompletionParams,
  766. modelConfig,
  767. setModelConfig,
  768. showSelectDataSet,
  769. dataSets,
  770. setDataSets,
  771. datasetConfigs,
  772. setDatasetConfigs,
  773. hasSetContextVar,
  774. isShowVisionConfig,
  775. visionConfig,
  776. setVisionConfig: handleSetVisionConfig,
  777. rerankSettingModalOpen,
  778. setRerankSettingModalOpen,
  779. }}
  780. >
  781. <FeaturesProvider features={featuresData}>
  782. <>
  783. <div className="flex flex-col h-full">
  784. <div className='relative flex grow h-[200px] pt-14'>
  785. {/* Header */}
  786. <div className='absolute top-0 left-0 w-full bg-white h-14'>
  787. <div className='flex items-center justify-between px-6 h-14'>
  788. <div className='flex items-center'>
  789. <div className='text-base font-semibold leading-6 text-gray-900'>{t('appDebug.orchestrate')}</div>
  790. <div className='flex items-center h-[14px] space-x-1 text-xs'>
  791. {isAdvancedMode && (
  792. <div className='ml-1 flex items-center h-5 px-1.5 border border-gray-100 rounded-md text-[11px] font-medium text-gray-500 uppercase'>{t('appDebug.promptMode.advanced')}</div>
  793. )}
  794. </div>
  795. </div>
  796. <div className='flex items-center'>
  797. {/* Agent Setting */}
  798. {isAgent && (
  799. <AgentSettingButton
  800. isChatModel={modelConfig.mode === ModelModeType.chat}
  801. agentConfig={modelConfig.agentConfig}
  802. isFunctionCall={isFunctionCall}
  803. onAgentSettingChange={(config) => {
  804. const nextConfig = produce(modelConfig, (draft: ModelConfig) => {
  805. draft.agentConfig = config
  806. })
  807. setModelConfig(nextConfig)
  808. }}
  809. />
  810. )}
  811. {/* Model and Parameters */}
  812. {!debugWithMultipleModel && (
  813. <>
  814. <ModelParameterModal
  815. isAdvancedMode={isAdvancedMode}
  816. mode={mode}
  817. provider={modelConfig.provider}
  818. completionParams={completionParams}
  819. modelId={modelConfig.model_id}
  820. setModel={setModel as any}
  821. onCompletionParamsChange={(newParams: FormValue) => {
  822. setCompletionParams(newParams)
  823. }}
  824. debugWithMultipleModel={debugWithMultipleModel}
  825. onDebugWithMultipleModelChange={handleDebugWithMultipleModelChange}
  826. />
  827. <div className='mx-2 w-[1px] h-[14px] bg-gray-200'></div>
  828. </>
  829. )}
  830. {isMobile && (
  831. <Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
  832. <span className='mr-1'>{t('appDebug.operation.debugConfig')}</span>
  833. <CodeBracketIcon className="w-4 h-4 text-gray-500" />
  834. </Button>
  835. )}
  836. <AppPublisher {...{
  837. publishDisabled: cannotPublish,
  838. publishedAt: (latestPublishedAt || 0) * 1000,
  839. debugWithMultipleModel,
  840. multipleModelConfigs,
  841. onPublish,
  842. publishedConfig: publishedConfig!,
  843. resetAppConfig: () => syncToPublishedConfig(publishedConfig!),
  844. }} />
  845. </div>
  846. </div>
  847. </div>
  848. <div className={`w-full sm:w-1/2 shrink-0 flex flex-col h-full ${debugWithMultipleModel && 'max-w-[560px]'}`}>
  849. <Config />
  850. </div>
  851. {!isMobile && <div className="relative flex flex-col w-1/2 h-full overflow-y-auto grow " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
  852. <div className='grow flex flex-col border-t-[0.5px] border-l-[0.5px] rounded-tl-2xl border-components-panel-border bg-chatbot-bg '>
  853. <Debug
  854. isAPIKeySet={isAPIKeySet}
  855. onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
  856. inputs={inputs}
  857. modelParameterParams={{
  858. setModel: setModel as any,
  859. onCompletionParamsChange: setCompletionParams,
  860. }}
  861. debugWithMultipleModel={debugWithMultipleModel}
  862. multipleModelConfigs={multipleModelConfigs}
  863. onMultipleModelConfigsChange={handleMultipleModelConfigsChange}
  864. />
  865. </div>
  866. </div>}
  867. </div>
  868. </div>
  869. {showUseGPT4Confirm && (
  870. <Confirm
  871. title={t('appDebug.trailUseGPT4Info.title')}
  872. content={t('appDebug.trailUseGPT4Info.description')}
  873. isShow={showUseGPT4Confirm}
  874. onConfirm={() => {
  875. setShowAccountSettingModal({ payload: 'provider' })
  876. setShowUseGPT4Confirm(false)
  877. }}
  878. onCancel={() => setShowUseGPT4Confirm(false)}
  879. />
  880. )}
  881. {isShowSelectDataSet && (
  882. <SelectDataSet
  883. isShow={isShowSelectDataSet}
  884. onClose={hideSelectDataSet}
  885. selectedIds={selectedIds}
  886. onSelect={handleSelect}
  887. />
  888. )}
  889. {isShowHistoryModal && (
  890. <EditHistoryModal
  891. isShow={isShowHistoryModal}
  892. saveLoading={false}
  893. onClose={hideHistoryModal}
  894. data={completionPromptConfig.conversation_histories_role}
  895. onSave={(data) => {
  896. setConversationHistoriesRole(data)
  897. hideHistoryModal()
  898. }}
  899. />
  900. )}
  901. {isMobile && (
  902. <Drawer showClose isOpen={isShowDebugPanel} onClose={hideDebugPanel} mask footer={null} panelClassname='!bg-gray-50'>
  903. <Debug
  904. isAPIKeySet={isAPIKeySet}
  905. onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
  906. inputs={inputs}
  907. modelParameterParams={{
  908. setModel: setModel as any,
  909. onCompletionParamsChange: setCompletionParams,
  910. }}
  911. debugWithMultipleModel={debugWithMultipleModel}
  912. multipleModelConfigs={multipleModelConfigs}
  913. onMultipleModelConfigsChange={handleMultipleModelConfigsChange}
  914. />
  915. </Drawer>
  916. )}
  917. {showAppConfigureFeaturesModal && (
  918. <NewFeaturePanel
  919. show
  920. inWorkflow={false}
  921. showFileUpload={false}
  922. isChatMode={mode !== 'completion'}
  923. disabled={false}
  924. onChange={handleFeaturesChange}
  925. onClose={() => setShowAppConfigureFeaturesModal(false)}
  926. promptVariables={modelConfig.configs.prompt_variables}
  927. onAutoAddPromptVariable={handleAddPromptVariable}
  928. />
  929. )}
  930. </>
  931. </FeaturesProvider>
  932. </ConfigContext.Provider>
  933. )
  934. }
  935. export default React.memo(Configuration)