You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useEffect, 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 } from 'ahooks'
  9. import cn from 'classnames'
  10. import Button from '../../base/button'
  11. import Loading from '../../base/loading'
  12. import type { CompletionParams, Inputs, ModelConfig, MoreLikeThisConfig, PromptConfig, PromptVariable } from '@/models/debug'
  13. import type { DataSet } from '@/models/datasets'
  14. import type { ModelConfig as BackendModelConfig } from '@/types/app'
  15. import ConfigContext from '@/context/debug-configuration'
  16. import ConfigModel from '@/app/components/app/configuration/config-model'
  17. import Config from '@/app/components/app/configuration/config'
  18. import Debug from '@/app/components/app/configuration/debug'
  19. import Confirm from '@/app/components/base/confirm'
  20. import { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
  21. import type { AppDetailResponse } from '@/models/app'
  22. import { ToastContext } from '@/app/components/base/toast'
  23. import { fetchAppDetail, updateAppModelConfig } from '@/service/apps'
  24. import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from '@/utils/model-config'
  25. import { fetchDatasets } from '@/service/datasets'
  26. import AccountSetting from '@/app/components/header/account-setting'
  27. import { useProviderContext } from '@/context/provider-context'
  28. import { AppType } from '@/types/app'
  29. const Configuration: FC = () => {
  30. const { t } = useTranslation()
  31. const { notify } = useContext(ToastContext)
  32. const [hasFetchedDetail, setHasFetchedDetail] = useState(false)
  33. const isLoading = !hasFetchedDetail
  34. const pathname = usePathname()
  35. const matched = pathname.match(/\/app\/([^/]+)/)
  36. const appId = (matched?.length && matched[1]) ? matched[1] : ''
  37. const [mode, setMode] = useState('')
  38. const [publishedConfig, setPublishedConfig] = useState<{
  39. modelConfig: ModelConfig
  40. completionParams: CompletionParams
  41. } | null>(null)
  42. const [conversationId, setConversationId] = useState<string | null>('')
  43. const [introduction, setIntroduction] = useState<string>('')
  44. const [controlClearChatMessage, setControlClearChatMessage] = useState(0)
  45. const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({
  46. prompt_template: '',
  47. prompt_variables: [],
  48. })
  49. const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig>({
  50. enabled: false,
  51. })
  52. const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<MoreLikeThisConfig>({
  53. enabled: false,
  54. })
  55. const [speechToTextConfig, setSpeechToTextConfig] = useState<MoreLikeThisConfig>({
  56. enabled: false,
  57. })
  58. const [citationConfig, setCitationConfig] = useState<MoreLikeThisConfig>({
  59. enabled: false,
  60. })
  61. const [formattingChanged, setFormattingChanged] = useState(false)
  62. const [inputs, setInputs] = useState<Inputs>({})
  63. const [query, setQuery] = useState('')
  64. const [completionParams, setCompletionParams] = useState<CompletionParams>({
  65. max_tokens: 16,
  66. temperature: 1, // 0-2
  67. top_p: 1,
  68. presence_penalty: 1, // -2-2
  69. frequency_penalty: 1, // -2-2
  70. })
  71. const [modelConfig, doSetModelConfig] = useState<ModelConfig>({
  72. provider: ProviderEnum.openai,
  73. model_id: 'gpt-3.5-turbo',
  74. configs: {
  75. prompt_template: '',
  76. prompt_variables: [] as PromptVariable[],
  77. },
  78. opening_statement: '',
  79. more_like_this: null,
  80. suggested_questions_after_answer: null,
  81. speech_to_text: null,
  82. retriever_resource: null,
  83. dataSets: [],
  84. })
  85. const setModelConfig = (newModelConfig: ModelConfig) => {
  86. doSetModelConfig(newModelConfig)
  87. }
  88. const setModelId = (modelId: string, provider: ProviderEnum) => {
  89. const newModelConfig = produce(modelConfig, (draft: any) => {
  90. draft.provider = provider
  91. draft.model_id = modelId
  92. })
  93. setModelConfig(newModelConfig)
  94. }
  95. const [dataSets, setDataSets] = useState<DataSet[]>([])
  96. const syncToPublishedConfig = (_publishedConfig: any) => {
  97. const modelConfig = _publishedConfig.modelConfig
  98. setModelConfig(_publishedConfig.modelConfig)
  99. setCompletionParams(_publishedConfig.completionParams)
  100. setDataSets(modelConfig.dataSets || [])
  101. // feature
  102. setIntroduction(modelConfig.opening_statement)
  103. setMoreLikeThisConfig(modelConfig.more_like_this || {
  104. enabled: false,
  105. })
  106. setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer || {
  107. enabled: false,
  108. })
  109. setSpeechToTextConfig(modelConfig.speech_to_text || {
  110. enabled: false,
  111. })
  112. setCitationConfig(modelConfig.retriever_resource || {
  113. enabled: false,
  114. })
  115. }
  116. const { textGenerationModelList } = useProviderContext()
  117. const hasSetCustomAPIKEY = !!textGenerationModelList?.find(({ model_provider: provider }) => {
  118. if (provider.provider_type === 'system' && provider.quota_type === 'paid')
  119. return true
  120. if (provider.provider_type === 'custom')
  121. return true
  122. return false
  123. })
  124. const isTrailFinished = !hasSetCustomAPIKEY && textGenerationModelList
  125. .filter(({ model_provider: provider }) => provider.quota_type === 'trial')
  126. .every(({ model_provider: provider }) => {
  127. const { quota_used, quota_limit } = provider
  128. return quota_used === quota_limit
  129. })
  130. const hasSetAPIKEY = hasSetCustomAPIKEY || !isTrailFinished
  131. const [isShowSetAPIKey, { setTrue: showSetAPIKey, setFalse: hideSetAPIkey }] = useBoolean()
  132. useEffect(() => {
  133. (fetchAppDetail({ url: '/apps', id: appId }) as any).then(async (res: AppDetailResponse) => {
  134. setMode(res.mode)
  135. const modelConfig = res.model_config
  136. const model = res.model_config.model
  137. let datasets: any = null
  138. if (modelConfig.agent_mode?.enabled)
  139. datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled)
  140. if (dataSets && datasets?.length && datasets?.length > 0) {
  141. const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } })
  142. datasets = dataSetsWithDetail
  143. setDataSets(datasets)
  144. }
  145. setIntroduction(modelConfig.opening_statement)
  146. if (modelConfig.more_like_this)
  147. setMoreLikeThisConfig(modelConfig.more_like_this)
  148. if (modelConfig.suggested_questions_after_answer)
  149. setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer)
  150. if (modelConfig.speech_to_text)
  151. setSpeechToTextConfig(modelConfig.speech_to_text)
  152. if (modelConfig.retriever_resource)
  153. setCitationConfig(modelConfig.retriever_resource)
  154. const config = {
  155. modelConfig: {
  156. provider: model.provider,
  157. model_id: model.name,
  158. configs: {
  159. prompt_template: modelConfig.pre_prompt,
  160. prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form),
  161. },
  162. opening_statement: modelConfig.opening_statement,
  163. more_like_this: modelConfig.more_like_this,
  164. suggested_questions_after_answer: modelConfig.suggested_questions_after_answer,
  165. speech_to_text: modelConfig.speech_to_text,
  166. retriever_resource: modelConfig.retriever_resource,
  167. dataSets: datasets || [],
  168. },
  169. completionParams: model.completion_params,
  170. }
  171. syncToPublishedConfig(config)
  172. setPublishedConfig(config)
  173. setHasFetchedDetail(true)
  174. })
  175. }, [appId])
  176. const cannotPublish = mode === AppType.completion && !modelConfig.configs.prompt_template
  177. const saveAppConfig = async () => {
  178. const modelId = modelConfig.model_id
  179. const promptTemplate = modelConfig.configs.prompt_template
  180. const promptVariables = modelConfig.configs.prompt_variables
  181. if (cannotPublish) {
  182. notify({ type: 'error', message: t('appDebug.otherError.promptNoBeEmpty'), duration: 3000 })
  183. return
  184. }
  185. const postDatasets = dataSets.map(({ id }) => ({
  186. dataset: {
  187. enabled: true,
  188. id,
  189. },
  190. }))
  191. // new model config data struct
  192. const data: BackendModelConfig = {
  193. pre_prompt: promptTemplate,
  194. user_input_form: promptVariablesToUserInputsForm(promptVariables),
  195. opening_statement: introduction || '',
  196. more_like_this: moreLikeThisConfig,
  197. suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
  198. speech_to_text: speechToTextConfig,
  199. retriever_resource: citationConfig,
  200. agent_mode: {
  201. enabled: true,
  202. tools: [...postDatasets],
  203. },
  204. model: {
  205. provider: modelConfig.provider,
  206. name: modelId,
  207. completion_params: completionParams as any,
  208. },
  209. }
  210. await updateAppModelConfig({ url: `/apps/${appId}/model-config`, body: data })
  211. const newModelConfig = produce(modelConfig, (draft: any) => {
  212. draft.opening_statement = introduction
  213. draft.more_like_this = moreLikeThisConfig
  214. draft.suggested_questions_after_answer = suggestedQuestionsAfterAnswerConfig
  215. draft.speech_to_text = speechToTextConfig
  216. draft.retriever_resource = citationConfig
  217. draft.dataSets = dataSets
  218. })
  219. setPublishedConfig({
  220. modelConfig: newModelConfig,
  221. completionParams,
  222. })
  223. notify({ type: 'success', message: t('common.api.success'), duration: 3000 })
  224. }
  225. const [showConfirm, setShowConfirm] = useState(false)
  226. const resetAppConfig = () => {
  227. syncToPublishedConfig(publishedConfig)
  228. setShowConfirm(false)
  229. }
  230. const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false)
  231. const [showSetAPIKeyModal, setShowSetAPIKeyModal] = useState(false)
  232. if (isLoading) {
  233. return <div className='flex h-full items-center justify-center'>
  234. <Loading type='area' />
  235. </div>
  236. }
  237. return (
  238. <ConfigContext.Provider value={{
  239. appId,
  240. hasSetAPIKEY,
  241. isTrailFinished,
  242. mode,
  243. conversationId,
  244. introduction,
  245. setIntroduction,
  246. setConversationId,
  247. controlClearChatMessage,
  248. setControlClearChatMessage,
  249. prevPromptConfig,
  250. setPrevPromptConfig,
  251. moreLikeThisConfig,
  252. setMoreLikeThisConfig,
  253. suggestedQuestionsAfterAnswerConfig,
  254. setSuggestedQuestionsAfterAnswerConfig,
  255. speechToTextConfig,
  256. setSpeechToTextConfig,
  257. citationConfig,
  258. setCitationConfig,
  259. formattingChanged,
  260. setFormattingChanged,
  261. inputs,
  262. setInputs,
  263. query,
  264. setQuery,
  265. completionParams,
  266. setCompletionParams,
  267. modelConfig,
  268. setModelConfig,
  269. dataSets,
  270. setDataSets,
  271. }}
  272. >
  273. <>
  274. <div className="flex flex-col h-full">
  275. <div className='flex items-center justify-between px-6 border-b shrink-0 h-14 boder-gray-100'>
  276. <div className='text-xl text-gray-900'>{t('appDebug.pageTitle')}</div>
  277. <div className='flex items-center'>
  278. {/* Model and Parameters */}
  279. <ConfigModel
  280. mode={mode}
  281. provider={modelConfig.provider as ProviderEnum}
  282. completionParams={completionParams}
  283. modelId={modelConfig.model_id}
  284. setModelId={setModelId}
  285. onCompletionParamsChange={(newParams: CompletionParams) => {
  286. setCompletionParams(newParams)
  287. }}
  288. disabled={!hasSetAPIKEY}
  289. />
  290. <div className='mx-3 w-[1px] h-[14px] bg-gray-200'></div>
  291. <Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
  292. <Button type='primary' onClick={saveAppConfig} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
  293. </div>
  294. </div>
  295. <div className='flex grow h-[200px]'>
  296. <div className="w-[574px] shrink-0 h-full overflow-y-auto border-r border-gray-100 py-4 px-6">
  297. <Config />
  298. </div>
  299. <div className="relative grow h-full overflow-y-auto py-4 px-6 bg-gray-50 flex flex-col">
  300. <Debug hasSetAPIKEY={hasSetAPIKEY} onSetting={showSetAPIKey} />
  301. </div>
  302. </div>
  303. </div>
  304. {showConfirm && (
  305. <Confirm
  306. title={t('appDebug.resetConfig.title')}
  307. content={t('appDebug.resetConfig.message')}
  308. isShow={showConfirm}
  309. onClose={() => setShowConfirm(false)}
  310. onConfirm={resetAppConfig}
  311. onCancel={() => setShowConfirm(false)}
  312. />
  313. )}
  314. {showUseGPT4Confirm && (
  315. <Confirm
  316. title={t('appDebug.trailUseGPT4Info.title')}
  317. content={t('appDebug.trailUseGPT4Info.description')}
  318. isShow={showUseGPT4Confirm}
  319. onClose={() => setShowUseGPT4Confirm(false)}
  320. onConfirm={() => {
  321. setShowSetAPIKeyModal(true)
  322. setShowUseGPT4Confirm(false)
  323. }}
  324. onCancel={() => setShowUseGPT4Confirm(false)}
  325. />
  326. )}
  327. {
  328. showSetAPIKeyModal && (
  329. <AccountSetting activeTab="provider" onCancel={async () => {
  330. setShowSetAPIKeyModal(false)
  331. }} />
  332. )
  333. }
  334. {isShowSetAPIKey && <AccountSetting activeTab="provider" onCancel={async () => {
  335. hideSetAPIkey()
  336. }} />}
  337. </>
  338. </ConfigContext.Provider>
  339. )
  340. }
  341. export default React.memo(Configuration)