index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. import { useState } from 'react'
  2. import useSWR from 'swr'
  3. import { useTranslation } from 'react-i18next'
  4. import type {
  5. BackendModel,
  6. FormValue,
  7. ProviderConfigModal,
  8. ProviderEnum,
  9. } from './declarations'
  10. import ModelSelector from './model-selector'
  11. import ModelCard from './model-card'
  12. import ModelItem from './model-item'
  13. import ModelModal from './model-modal'
  14. import config from './configs'
  15. import { ConfigurableProviders } from './utils'
  16. import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
  17. import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
  18. import {
  19. changeModelProviderPriority,
  20. deleteModelProvider,
  21. deleteModelProviderModel,
  22. fetchDefaultModal,
  23. fetchModelProviders,
  24. setModelProvider,
  25. updateDefaultModel,
  26. } from '@/service/common'
  27. import { useToastContext } from '@/app/components/base/toast'
  28. import Confirm from '@/app/components/base/confirm/common'
  29. import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
  30. import { useEventEmitterContextContext } from '@/context/event-emitter'
  31. import { useProviderContext } from '@/context/provider-context'
  32. import Tooltip from '@/app/components/base/tooltip'
  33. const MODEL_CARD_LIST = [
  34. config.openai,
  35. config.anthropic,
  36. ]
  37. const MODEL_LIST = [
  38. config.azure_openai,
  39. config.replicate,
  40. config.huggingface_hub,
  41. config.minimax,
  42. config.spark,
  43. config.tongyi,
  44. config.wenxin,
  45. config.chatglm,
  46. ]
  47. const titleClassName = `
  48. flex items-center h-9 text-sm font-medium text-gray-900
  49. `
  50. const tipClassName = `
  51. ml-0.5 w-[14px] h-[14px] text-gray-400
  52. `
  53. type DeleteModel = {
  54. model_name: string
  55. model_type: string
  56. }
  57. const ModelPage = () => {
  58. const { t } = useTranslation()
  59. const { updateModelList } = useProviderContext()
  60. const { data: providers, mutate: mutateProviders } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
  61. const { data: textGenerationDefaultModel, mutate: mutateTextGenerationDefaultModel } = useSWR('/workspaces/current/default-model?model_type=text-generation', fetchDefaultModal)
  62. const { data: embeddingsDefaultModel, mutate: mutateEmbeddingsDefaultModel } = useSWR('/workspaces/current/default-model?model_type=embeddings', fetchDefaultModal)
  63. const { data: speech2textDefaultModel, mutate: mutateSpeech2textDefaultModel } = useSWR('/workspaces/current/default-model?model_type=speech2text', fetchDefaultModal)
  64. const [showMoreModel, setShowMoreModel] = useState(false)
  65. const [showModal, setShowModal] = useState(false)
  66. const { notify } = useToastContext()
  67. const { eventEmitter } = useEventEmitterContextContext()
  68. const [modelModalConfig, setModelModalConfig] = useState<ProviderConfigModal | undefined>(undefined)
  69. const [confirmShow, setConfirmShow] = useState(false)
  70. const [deleteModel, setDeleteModel] = useState<DeleteModel & { providerKey: ProviderEnum }>()
  71. const [modalMode, setModalMode] = useState('add')
  72. const handleOpenModal = (newModelModalConfig: ProviderConfigModal | undefined, editValue?: FormValue) => {
  73. if (newModelModalConfig) {
  74. setShowModal(true)
  75. const defaultValue = editValue ? { ...newModelModalConfig.defaultValue, ...editValue } : newModelModalConfig.defaultValue
  76. setModelModalConfig({
  77. ...newModelModalConfig,
  78. defaultValue,
  79. })
  80. if (editValue)
  81. setModalMode('edit')
  82. else
  83. setModalMode('add')
  84. }
  85. }
  86. const handleCancelModal = () => {
  87. setShowModal(false)
  88. }
  89. const handleUpdateProvidersAndModelList = () => {
  90. updateModelList(ModelType.textGeneration)
  91. updateModelList(ModelType.embeddings)
  92. mutateProviders()
  93. }
  94. const handleSave = async (v?: FormValue) => {
  95. if (v && modelModalConfig) {
  96. let body, url
  97. if (ConfigurableProviders.includes(modelModalConfig.key)) {
  98. const { model_name, model_type, ...config } = v
  99. body = {
  100. model_name,
  101. model_type,
  102. config,
  103. }
  104. url = `/workspaces/current/model-providers/${modelModalConfig.key}/models`
  105. }
  106. else {
  107. body = {
  108. config: v,
  109. }
  110. url = `/workspaces/current/model-providers/${modelModalConfig.key}`
  111. }
  112. try {
  113. eventEmitter?.emit('provider-save')
  114. const res = await setModelProvider({ url, body })
  115. if (res.result === 'success') {
  116. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  117. handleUpdateProvidersAndModelList()
  118. handleCancelModal()
  119. }
  120. eventEmitter?.emit('')
  121. }
  122. catch (e) {
  123. eventEmitter?.emit('')
  124. }
  125. }
  126. }
  127. const handleConfirm = (deleteModel: DeleteModel, providerKey: ProviderEnum) => {
  128. setDeleteModel({ ...deleteModel, providerKey })
  129. setConfirmShow(true)
  130. }
  131. const handleOperate = async ({ type, value }: Record<string, any>, provierKey: ProviderEnum) => {
  132. if (type === 'delete') {
  133. if (!value) {
  134. const res = await deleteModelProvider({ url: `/workspaces/current/model-providers/${provierKey}` })
  135. if (res.result === 'success') {
  136. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  137. handleUpdateProvidersAndModelList()
  138. }
  139. }
  140. else {
  141. handleConfirm(value, provierKey)
  142. }
  143. }
  144. if (type === 'priority') {
  145. const res = await changeModelProviderPriority({
  146. url: `/workspaces/current/model-providers/${provierKey}/preferred-provider-type`,
  147. body: {
  148. preferred_provider_type: value,
  149. },
  150. })
  151. if (res.result === 'success') {
  152. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  153. mutateProviders()
  154. }
  155. }
  156. }
  157. const handleDeleteModel = async () => {
  158. const { model_name, model_type, providerKey } = deleteModel || {}
  159. const res = await deleteModelProviderModel({
  160. url: `/workspaces/current/model-providers/${providerKey}/models?model_name=${model_name}&model_type=${model_type}`,
  161. })
  162. if (res.result === 'success') {
  163. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  164. setConfirmShow(false)
  165. handleUpdateProvidersAndModelList()
  166. }
  167. }
  168. const mutateDefaultModel = (type: ModelType) => {
  169. if (type === ModelType.textGeneration)
  170. mutateTextGenerationDefaultModel()
  171. if (type === ModelType.embeddings)
  172. mutateEmbeddingsDefaultModel()
  173. if (type === ModelType.speech2text)
  174. mutateSpeech2textDefaultModel()
  175. }
  176. const handleChangeDefaultModel = async (type: ModelType, v: BackendModel) => {
  177. const res = await updateDefaultModel({
  178. url: '/workspaces/current/default-model',
  179. body: {
  180. model_type: type,
  181. provider_name: v.model_provider.provider_name,
  182. model_name: v.model_name,
  183. },
  184. })
  185. if (res.result === 'success') {
  186. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  187. mutateDefaultModel(type)
  188. }
  189. }
  190. return (
  191. <div className='relative pt-1 -mt-2'>
  192. <div className='grid grid-cols-3 gap-4 mb-5'>
  193. <div className='w-full'>
  194. <div className={titleClassName}>
  195. {t('common.modelProvider.systemReasoningModel.key')}
  196. <Tooltip
  197. selector='model-page-system-reasoning-model-tip'
  198. htmlContent={
  199. <div className='w-[261px] text-gray-500'>{t('common.modelProvider.systemReasoningModel.tip')}</div>
  200. }
  201. >
  202. <HelpCircle className={tipClassName} />
  203. </Tooltip>
  204. </div>
  205. <div>
  206. <ModelSelector
  207. value={textGenerationDefaultModel && { providerName: textGenerationDefaultModel.model_provider.provider_name, modelName: textGenerationDefaultModel.model_name }}
  208. modelType={ModelType.textGeneration}
  209. onChange={v => handleChangeDefaultModel(ModelType.textGeneration, v)}
  210. />
  211. </div>
  212. </div>
  213. <div className='w-full'>
  214. <div className={titleClassName}>
  215. {t('common.modelProvider.embeddingModel.key')}
  216. <Tooltip
  217. selector='model-page-system-embedding-model-tip'
  218. htmlContent={
  219. <div className='w-[261px] text-gray-500'>{t('common.modelProvider.embeddingModel.tip')}</div>
  220. }
  221. >
  222. <HelpCircle className={tipClassName} />
  223. </Tooltip>
  224. </div>
  225. <div>
  226. <ModelSelector
  227. value={embeddingsDefaultModel && { providerName: embeddingsDefaultModel.model_provider.provider_name, modelName: embeddingsDefaultModel.model_name }}
  228. modelType={ModelType.embeddings}
  229. onChange={v => handleChangeDefaultModel(ModelType.embeddings, v)}
  230. />
  231. </div>
  232. </div>
  233. <div className='w-full'>
  234. <div className={titleClassName}>
  235. {t('common.modelProvider.speechToTextModel.key')}
  236. <Tooltip
  237. selector='model-page-system-speechToText-model-tip'
  238. htmlContent={
  239. <div className='w-[261px] text-gray-500'>{t('common.modelProvider.speechToTextModel.tip')}</div>
  240. }
  241. >
  242. <HelpCircle className={tipClassName} />
  243. </Tooltip>
  244. </div>
  245. <div>
  246. <ModelSelector
  247. value={speech2textDefaultModel && { providerName: speech2textDefaultModel.model_provider.provider_name, modelName: speech2textDefaultModel.model_name }}
  248. modelType={ModelType.speech2text}
  249. onChange={v => handleChangeDefaultModel(ModelType.speech2text, v)}
  250. />
  251. </div>
  252. </div>
  253. </div>
  254. <div className='mb-5 h-[0.5px] bg-gray-100' />
  255. <div className='mb-3 text-sm font-medium text-gray-800'>{t('common.modelProvider.models')}</div>
  256. <div className='grid grid-cols-2 gap-4 mb-6'>
  257. {
  258. MODEL_CARD_LIST.map((model, index) => (
  259. <ModelCard
  260. key={index}
  261. modelItem={model.item}
  262. currentProvider={providers?.[model.item.key]}
  263. onOpenModal={editValue => handleOpenModal(model.modal, editValue)}
  264. onOperate={v => handleOperate(v, model.item.key)}
  265. />
  266. ))
  267. }
  268. </div>
  269. {
  270. MODEL_LIST.slice(0, showMoreModel ? MODEL_LIST.length : 3).map((model, index) => (
  271. <ModelItem
  272. key={index}
  273. modelItem={model.item}
  274. currentProvider={providers?.[model.item.key]}
  275. onOpenModal={editValue => handleOpenModal(model.modal, editValue)}
  276. onOperate={v => handleOperate(v, model.item.key)}
  277. onUpdate={mutateProviders}
  278. />
  279. ))
  280. }
  281. {
  282. !showMoreModel && (
  283. <div className='inline-flex items-center px-1 h-[26px] cursor-pointer' onClick={() => setShowMoreModel(true)}>
  284. <ChevronDownDouble className='mr-1 w-3 h-3 text-gray-500' />
  285. <div className='text-xs font-medium text-gray-500'>{t('common.modelProvider.showMoreModelProvider')}</div>
  286. </div>
  287. )
  288. }
  289. <ModelModal
  290. isShow={showModal}
  291. modelModal={modelModalConfig}
  292. onCancel={handleCancelModal}
  293. onSave={handleSave}
  294. mode={modalMode}
  295. />
  296. <Confirm
  297. isShow={confirmShow}
  298. onCancel={() => setConfirmShow(false)}
  299. title={deleteModel?.model_name || ''}
  300. desc={t('common.modelProvider.item.deleteDesc', { modelName: deleteModel?.model_name }) || ''}
  301. onConfirm={handleDeleteModel}
  302. />
  303. </div>
  304. )
  305. }
  306. export default ModelPage