index.tsx 12 KB

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