import { Box, Flex, HStack, Table, TableContainer, Tbody, Td, Th, Thead, Tr, Switch, ModalBody, Input, ModalFooter, Button, useDisclosure } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { ModelProviderList, ModelProviderIdType, getModelProvider } from '@fastgpt/global/core/ai/provider'; import MySelect from '@fastgpt/web/components/common/MySelect'; import { modelTypeList, ModelTypeEnum } from '@fastgpt/global/core/ai/model'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MyTag from '@fastgpt/web/components/common/Tag/index'; import dynamic from 'next/dynamic'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { deleteSystemModel, getModelConfigJson, getSystemModelDefaultConfig, getSystemModelDetail, getSystemModelList, getTestModel, putSystemModel, putUpdateDefaultModels } from '@/web/core/ai/config'; import MyBox from '@fastgpt/web/components/common/MyBox'; import { SystemModelItemType } from '@fastgpt/service/core/ai/type'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import MyIconButton from '@fastgpt/web/components/common/Icon/button'; import { useForm } from 'react-hook-form'; import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput'; import MyTextarea from '@/components/common/Textarea/MyTextarea'; import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; import { clientInitData } from '@/web/common/system/staticData'; import { useUserStore } from '@/web/support/user/useUserStore'; import MyMenu from '@fastgpt/web/components/common/MyMenu'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import { putUpdateWithJson } from '@/web/core/ai/config'; import CopyBox from '@fastgpt/web/components/common/String/CopyBox'; import MyIcon from '@fastgpt/web/components/common/Icon'; import AIModelSelector from '@/components/Select/AIModelSelector'; import { useRefresh } from '../../../../../../packages/web/hooks/useRefresh'; import { Prompt_CQJson, Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent'; const MyModal = dynamic(() => import('@fastgpt/web/components/common/MyModal')); const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { const { t } = useTranslation(); const { userInfo } = useUserStore(); const { defaultModels, feConfigs } = useSystemStore(); const isRoot = userInfo?.username === 'root'; const [provider, setProvider] = useState(''); const providerList = useRef<{ label: any; value: ModelProviderIdType | '' }[]>([ { label: t('common:common.All'), value: '' }, ...ModelProviderList.map((item) => ({ label: ( {t(item.name as any)} ), value: item.id })) ]); const [modelType, setModelType] = useState(''); const selectModelTypeList = useRef<{ label: string; value: ModelTypeEnum | '' }[]>([ { label: t('common:common.All'), value: '' }, ...modelTypeList.map((item) => ({ label: t(item.label), value: item.value })) ]); const [search, setSearch] = useState(''); const [showActive, setShowActive] = useState(false); const { data: systemModelList = [], runAsync: refreshSystemModelList, loading: loadingModels } = useRequest2(getSystemModelList, { manual: false }); const refreshModels = useCallback(async () => { clientInitData(); refreshSystemModelList(); }, [refreshSystemModelList]); const modelList = useMemo(() => { const formatLLMModelList = systemModelList .filter((item) => item.type === ModelTypeEnum.llm) .map((item) => ({ ...item, typeLabel: t('common:model.type.chat'), priceLabel: typeof item.inputPrice === 'number' ? ( {`${t('common:common.Input')}:`} {item.inputPrice || 0} {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} {`${t('common:common.Output')}:`} {item.outputPrice || 0} {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} ) : ( {item.charsPointsPrice || 0} {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} ), tagColor: 'blue' })); const formatVectorModelList = systemModelList .filter((item) => item.type === ModelTypeEnum.embedding) .map((item) => ({ ...item, typeLabel: t('common:model.type.embedding'), priceLabel: ( {item.charsPointsPrice || 0} {` ${t('common:support.wallet.subscription.point')} / 1K Tokens`} ), tagColor: 'yellow' })); const formatAudioSpeechModelList = systemModelList .filter((item) => item.type === ModelTypeEnum.tts) .map((item) => ({ ...item, typeLabel: t('common:model.type.tts'), priceLabel: ( {item.charsPointsPrice || 0} {` ${t('common:support.wallet.subscription.point')} / 1K ${t('common:unit.character')}`} ), tagColor: 'green' })); const formatWhisperModel = systemModelList .filter((item) => item.type === ModelTypeEnum.stt) .map((item) => ({ ...item, typeLabel: t('common:model.type.stt'), priceLabel: ( {item.charsPointsPrice || 0} {` ${t('common:support.wallet.subscription.point')} / 60${t('common:unit.seconds')}`} ), tagColor: 'purple' })); const formatRerankModelList = systemModelList .filter((item) => item.type === ModelTypeEnum.rerank) .map((item) => ({ ...item, typeLabel: t('common:model.type.reRank'), priceLabel: - , tagColor: 'red' })); const list = (() => { if (modelType === ModelTypeEnum.llm) return formatLLMModelList; if (modelType === ModelTypeEnum.embedding) return formatVectorModelList; if (modelType === ModelTypeEnum.tts) return formatAudioSpeechModelList; if (modelType === ModelTypeEnum.stt) return formatWhisperModel; if (modelType === ModelTypeEnum.rerank) return formatRerankModelList; return [ ...formatLLMModelList, ...formatVectorModelList, ...formatAudioSpeechModelList, ...formatWhisperModel, ...formatRerankModelList ]; })(); const formatList = list.map((item) => { const provider = getModelProvider(item.provider); return { ...item, avatar: provider.avatar, providerId: provider.id, providerName: t(provider.name as any), order: provider.order }; }); formatList.sort((a, b) => a.order - b.order); const filterList = formatList.filter((item) => { const providerFilter = provider ? item.providerId === provider : true; const regx = new RegExp(search, 'i'); const nameFilter = search ? regx.test(item.name) : true; const activeFilter = showActive ? item.isActive : true; return providerFilter && nameFilter && activeFilter; }); return filterList; }, [systemModelList, t, modelType, provider, search, showActive]); const activeModelLength = useMemo(() => { return modelList.filter((item) => item.isActive).length; }, [modelList]); const filterProviderList = useMemo(() => { const allProviderIds: string[] = systemModelList.map((model) => model.provider); return providerList.current.filter( (item) => allProviderIds.includes(item.value) || item.value === '' ); }, [systemModelList]); const { runAsync: onTestModel, loading: testingModel } = useRequest2(getTestModel, { manual: true, successToast: t('common:common.Success') }); const { runAsync: updateModel, loading: updatingModel } = useRequest2(putSystemModel, { onSuccess: refreshModels }); const { ConfirmModal, openConfirm } = useConfirm({ type: 'delete', content: t('account:model.delete_model_confirm') }); const { runAsync: deleteModel } = useRequest2(deleteSystemModel, { onSuccess: refreshModels }); const [editModelData, setEditModelData] = useState(); const { runAsync: onEditModel, loading: loadingData } = useRequest2( (modelId: string) => getSystemModelDetail(modelId), { onSuccess: (data: SystemModelItemType) => { setEditModelData(data); } } ); const onCreateModel = (type: ModelTypeEnum) => { const defaultModel = defaultModels[type]; setEditModelData({ ...defaultModel, model: '', name: '', charsPointsPrice: 0, inputPrice: undefined, outputPrice: undefined, isCustom: true, isActive: true, // @ts-ignore type }); }; const { isOpen: isOpenJsonConfig, onOpen: onOpenJsonConfig, onClose: onCloseJsonConfig } = useDisclosure(); const { onOpen: onOpenDefaultModel, onClose: onCloseDefaultModel, isOpen: isOpenDefaultModel } = useDisclosure(); const isLoading = loadingModels || loadingData || updatingModel || testingModel; const [showModelId, setShowModelId] = useState(true); return ( <> {isRoot && ( {Tab} {t('account:create_model')}} menuList={[ { children: [ { label: t('common:model.type.chat'), onClick: () => onCreateModel(ModelTypeEnum.llm) }, { label: t('common:model.type.embedding'), onClick: () => onCreateModel(ModelTypeEnum.embedding) }, { label: t('common:model.type.tts'), onClick: () => onCreateModel(ModelTypeEnum.tts) }, { label: t('common:model.type.stt'), onClick: () => onCreateModel(ModelTypeEnum.stt) }, { label: t('common:model.type.reRank'), onClick: () => onCreateModel(ModelTypeEnum.rerank) } ] } ]} /> )} {t('common:model.provider')} {t('common:model.model_type')} setSearch(e.target.value)} placeholder={t('common:model.search_name_placeholder')} /> {feConfigs?.isPlus && } {modelList.map((item, index) => ( {feConfigs?.isPlus && } ))}
setShowModelId(!showModelId)} > {showModelId ? t('account:model.model_id') : t('common:model.name')} {t('common:model.model_type')}{t('common:model.billing')} setShowActive(!showActive)} color={showActive ? 'primary.600' : 'myGray.600'} > {t('account:model.active')}({activeModelLength})
{showModelId ? item.model : item.name} {item.contextToken && ( {Math.floor(item.contextToken / 1000)}k )} {item.vision && ( {t('account:model.vision_tag')} )} {item.toolChoice && ( {t('account:model.tool_choice_tag')} )} {item.typeLabel} {item.priceLabel} updateModel({ model: item.model, metadata: { isActive: e.target.checked } }) } colorScheme={'myBlue'} /> onTestModel(item.model)} /> onEditModel(item.model)} /> {item.isCustom && ( openConfirm(() => deleteModel({ model: item.model }))()} /> )}
{!!editModelData && ( setEditModelData(undefined)} /> )} {isOpenJsonConfig && ( )} {isOpenDefaultModel && ( )} ); }; const InputStyles = { maxW: '300px', bg: 'myGray.50', w: '100%', rows: 3 }; const ModelEditModal = ({ modelData, onSuccess, onClose }: { modelData: SystemModelItemType; onSuccess: () => void; onClose: () => void; }) => { const { t } = useTranslation(); const { feConfigs } = useSystemStore(); const { register, getValues, setValue, handleSubmit, watch, reset } = useForm({ defaultValues: modelData }); const isCustom = !!modelData.isCustom; const isLLMModel = modelData?.type === ModelTypeEnum.llm; const isEmbeddingModel = modelData?.type === ModelTypeEnum.embedding; const isTTSModel = modelData?.type === ModelTypeEnum.tts; const isSTTModel = modelData?.type === ModelTypeEnum.stt; const isRerankModel = modelData?.type === ModelTypeEnum.rerank; const provider = watch('provider'); const providerData = useMemo(() => getModelProvider(provider), [provider]); const providerList = useRef<{ label: any; value: ModelProviderIdType }[]>( ModelProviderList.map((item) => ({ label: ( {t(item.name as any)} ), value: item.id })) ); const priceUnit = useMemo(() => { if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens'; if (isTTSModel) return `/ 1k ${t('common:unit.character')}`; if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`; return ''; return ''; }, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]); const { runAsync: updateModel, loading: updatingModel } = useRequest2( async (data: SystemModelItemType) => { return putSystemModel({ model: data.model, metadata: data }).then(onSuccess); }, { onSuccess: () => { onClose(); }, successToast: t('common:common.Success') } ); const [key, setKey] = useState(0); const { runAsync: loadDefaultConfig, loading: loadingDefaultConfig } = useRequest2( getSystemModelDefaultConfig, { onSuccess(res) { reset({ ...getValues(), ...res }); setTimeout(() => { setKey((prev) => prev + 1); }, 0); } } ); return ( {priceUnit && feConfigs?.isPlus && ( <> {isLLMModel && ( <> )} )} {isLLMModel && ( <> )} {isEmbeddingModel && ( <> )} {isTTSModel && ( <> )}
{t('account:model.param_name')}
{t('account:model.model_id')} {isCustom ? ( ) : ( modelData?.model )}
{t('common:model.provider')} setValue('provider', value)} list={providerList.current} {...InputStyles} />
{t('account:model.alias')}
{t('account:model.charsPointsPrice')} {priceUnit}
{t('account:model.input_price')} {priceUnit}
{t('account:model.output_price')} {priceUnit}
{t('common:core.ai.Max context')}
{t('account:model.max_quote')}
{t('common:core.chat.response.module maxToken')}
{t('account:model.max_temperature')}
{t('account:model.default_token')}
{t('common:core.ai.Max context')}
{t('account:model.defaultConfig')} { if (!e) { setValue('defaultConfig', {}); return; } try { setValue('defaultConfig', JSON.parse(e)); } catch (error) { console.error(error); } }} {...InputStyles} />
{t('account:model.voices')} { try { setValue('voices', JSON.parse(e)); } catch (error) { console.error(error); } }} {...InputStyles} />
{t('account:model.request_url')}
{t('account:model.request_auth')}
{isLLMModel && ( {feConfigs?.isPlus && ( )}
{t('account:model.param_name')}
{t('account:model.tool_choice')}
{t('account:model.function_call')}
{t('account:model.vision')}
{t('account:model.censor')}
{t('account:model.dataset_process')}
{t('account:model.used_in_classify')}
{t('account:model.used_in_extract_fields')}
{t('account:model.used_in_tool_call')}
{t('account:model.default_system_chat_prompt')}
{t('account:model.custom_cq_prompt')}
{t('account:model.custom_extract_prompt')}
{t('account:model.default_config')} { if (!e) { setValue('defaultConfig', {}); return; } try { setValue('defaultConfig', JSON.parse(e)); } catch (error) { console.error(error); } }} {...InputStyles} />
)}
{!modelData.isCustom && ( )}
); }; const JsonConfigModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void; }) => { const { t } = useTranslation(); const [data, setData] = useState(''); const { loading } = useRequest2(getModelConfigJson, { manual: false, onSuccess(res) { setData(res); } }); const { openConfirm, ConfirmModal } = useConfirm({ content: t('account:model.json_config_confirm') }); const { runAsync } = useRequest2(putUpdateWithJson, { onSuccess: () => { onSuccess(); onClose(); } }); return ( {t('account:model.json_config_tip')} ); }; const labelStyles = { fontSize: 'sm', color: 'myGray.900', mb: 0.5 }; const DefaultModelModal = ({ onSuccess, onClose }: { onSuccess: () => void; onClose: () => void; }) => { const { t } = useTranslation(); const { defaultModels, llmModelList, embeddingModelList, ttsModelList, sttModelList, reRankModelList } = useSystemStore(); // Create a copy of defaultModels for local state management const [defaultData, setDefaultData] = useState(defaultModels); const { runAsync, loading } = useRequest2(putUpdateDefaultModels, { onSuccess: () => { onSuccess(); onClose(); }, successToast: t('common:common.Update Success') }); return ( {t('common:model.type.chat')} ({ value: item.model, label: item.name }))} onchange={(e) => { setDefaultData((state) => ({ ...state, llm: llmModelList.find((item) => item.model === e) })); }} /> {t('common:model.type.embedding')} ({ value: item.model, label: item.name }))} onchange={(e) => { setDefaultData((state) => ({ ...state, embedding: embeddingModelList.find((item) => item.model === e) })); }} /> {t('common:model.type.tts')} ({ value: item.model, label: item.name }))} onchange={(e) => { setDefaultData((state) => ({ ...state, tts: ttsModelList.find((item) => item.model === e) })); }} /> {t('common:model.type.stt')} ({ value: item.model, label: item.name }))} onchange={(e) => { setDefaultData((state) => ({ ...state, stt: sttModelList.find((item) => item.model === e) })); }} /> {t('common:model.type.reRank')} ({ value: item.model, label: item.name }))} onchange={(e) => { setDefaultData((state) => ({ ...state, rerank: reRankModelList.find((item) => item.model === e) })); }} /> ); }; export default ModelTable;