index.tsx 13 KB

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