Refactor(setting-model): Refactor the model management interface and optimize the component structure. #10703 (#10905)

### What problem does this PR solve?

Refactor(setting-model): Refactor the model management interface and
optimize the component structure. #10703

### Type of change

- [x] Refactoring
This commit is contained in:
chanx
2025-10-31 09:27:30 +08:00
committed by GitHub
parent ff2365b146
commit 5a830ea68b
11 changed files with 1191 additions and 338 deletions

View File

@ -104,6 +104,7 @@ export interface ITenantInfo {
tenant_id: string; tenant_id: string;
chat_id: string; chat_id: string;
speech2text_id: string; speech2text_id: string;
rerank_id?: string;
tts_id: string; tts_id: string;
} }

View File

@ -680,6 +680,8 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
tocEnhanceTip: ` During the parsing of the document, table of contents information was generated (see the 'Enable Table of Contents Extraction' option in the General method). This allows the large model to return table of contents items relevant to the user's query, thereby using these items to retrieve related chunks and apply weighting to these chunks during the sorting process. This approach is derived from mimicking the behavioral logic of how humans search for knowledge in books.`, tocEnhanceTip: ` During the parsing of the document, table of contents information was generated (see the 'Enable Table of Contents Extraction' option in the General method). This allows the large model to return table of contents items relevant to the user's query, thereby using these items to retrieve related chunks and apply weighting to these chunks during the sorting process. This approach is derived from mimicking the behavioral logic of how humans search for knowledge in books.`,
}, },
setting: { setting: {
search: 'Search',
availableModels: 'Available models',
profile: 'Profile', profile: 'Profile',
avatar: 'Avatar', avatar: 'Avatar',
avatarTip: 'This will be displayed on your profile.', avatarTip: 'This will be displayed on your profile.',
@ -693,7 +695,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
passwordDescription: passwordDescription:
'Please enter your current password to change your password.', 'Please enter your current password to change your password.',
model: 'Model providers', model: 'Model providers',
modelDescription: 'Configure model parameters and API KEY here.', systemModelDescription: 'Please complete these settings before beginning',
team: 'Team', team: 'Team',
system: 'System', system: 'System',
logout: 'Log out', logout: 'Log out',
@ -726,7 +728,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
cancel: 'Cancel', cancel: 'Cancel',
addedModels: 'Added models', addedModels: 'Added models',
modelsToBeAdded: 'Models to be added', modelsToBeAdded: 'Models to be added',
addTheModel: 'Add Model', addTheModel: 'Add',
apiKey: 'API-Key', apiKey: 'API-Key',
apiKeyMessage: apiKeyMessage:
'Please enter the API key (for locally deployed model,ignore this).', 'Please enter the API key (for locally deployed model,ignore this).',
@ -742,21 +744,20 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
tongyiBaseUrlPlaceholder: '(International users only, please see tip)', tongyiBaseUrlPlaceholder: '(International users only, please see tip)',
modify: 'Modify', modify: 'Modify',
systemModelSettings: 'Set default models', systemModelSettings: 'Set default models',
chatModel: 'Chat model', chatModel: 'LLM',
chatModelTip: chatModelTip: 'The default LLM for each newly created knowledge base.',
'The default chat model for each newly created knowledge base.', embeddingModel: 'Embedding',
embeddingModel: 'Embedding model',
embeddingModelTip: embeddingModelTip:
'The default embedding model for each newly created knowledge base. If you cannot find an embedding model from the dropdown, check if you are using RAGFlow slim edition (which does not include embedding models) or check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.', 'The default embedding model for each newly created knowledge base. If you cannot find an embedding model from the dropdown, check if you are using RAGFlow slim edition (which does not include embedding models) or check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.',
img2txtModel: 'Img2txt model', img2txtModel: 'VLM',
img2txtModelTip: img2txtModelTip:
'The default img2txt model for each newly created knowledge base. It describes a picture or video. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.', 'The default VLM for each newly created knowledge base. It describes a picture or video. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.',
sequence2txtModel: 'Speech2txt model', sequence2txtModel: 'ASR',
sequence2txtModelTip: sequence2txtModelTip:
'The default ASR model for each newly created knowledgebase. Use this model to translate voices to corresponding text.', 'The default ASR model for each newly created knowledgebase. Use this model to translate voices to corresponding text.',
rerankModel: 'Rerank model', rerankModel: 'Rerank',
rerankModelTip: `The default rerank model for reranking chunks. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.`, rerankModelTip: `The default rerank model for reranking chunks. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.`,
ttsModel: 'TTS Model', ttsModel: 'TTS',
ttsModelTip: ttsModelTip:
'The default text-to-speech model. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.', 'The default text-to-speech model. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.',
workspace: 'Workspace', workspace: 'Workspace',

View File

@ -671,6 +671,8 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
tocEnhanceTip: `解析文档时生成了目录信息见General方法的启用目录抽取让大模型返回和用户问题相关的目录项从而利用目录项拿到相关chunk对这些chunk在排序中进行加权。这种方法来源于模仿人类查询书本中知识的行为逻辑`, tocEnhanceTip: `解析文档时生成了目录信息见General方法的启用目录抽取让大模型返回和用户问题相关的目录项从而利用目录项拿到相关chunk对这些chunk在排序中进行加权。这种方法来源于模仿人类查询书本中知识的行为逻辑`,
}, },
setting: { setting: {
search: '搜索',
availableModels: '可选模型',
profile: '概要', profile: '概要',
avatar: '头像', avatar: '头像',
avatarTip: '这会在你的个人主页展示', avatarTip: '这会在你的个人主页展示',
@ -684,7 +686,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
password: '密码', password: '密码',
passwordDescription: '请输入您当前的密码以更改您的密码。', passwordDescription: '请输入您当前的密码以更改您的密码。',
model: '模型提供商', model: '模型提供商',
modelDescription: '在此设置模型参数和 API KEY。', systemModelDescription: '请在开始之前完成这些设置',
team: '团队', team: '团队',
system: '系统', system: '系统',
logout: '登出', logout: '登出',
@ -715,7 +717,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
cancel: '取消', cancel: '取消',
addedModels: '添加了的模型', addedModels: '添加了的模型',
modelsToBeAdded: '待添加的模型', modelsToBeAdded: '待添加的模型',
addTheModel: '添加模型', addTheModel: '添加',
apiKey: 'API-Key', apiKey: 'API-Key',
apiKeyMessage: '请输入api key如果是本地部署的模型请忽略它', apiKeyMessage: '请输入api key如果是本地部署的模型请忽略它',
apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。', apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。',
@ -729,21 +731,21 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
tongyiBaseUrlPlaceholder: '(仅国际用户需要)', tongyiBaseUrlPlaceholder: '(仅国际用户需要)',
modify: '修改', modify: '修改',
systemModelSettings: '设置默认模型', systemModelSettings: '设置默认模型',
chatModel: '聊天模型', chatModel: 'LLM',
chatModelTip: '所有新创建的知识库都会使用默认的聊天模型。', chatModelTip: '所有新创建的知识库都会使用默认的聊天模型。',
ttsModel: 'TTS模型', ttsModel: 'TTS',
ttsModelTip: ttsModelTip:
'默认的tts模型会被用于在对话过程中请求语音生成时使用。如未显示可选模型请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。', '默认的tts模型会被用于在对话过程中请求语音生成时使用。如未显示可选模型请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。',
embeddingModel: '嵌入模型', embeddingModel: 'Embedding',
embeddingModelTip: embeddingModelTip:
'所有新创建的知识库使用的默认嵌入模型。如未显示可选模型,请检查你是否在使用 RAGFlow slim 版(不含嵌入模型);或根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。', '所有新创建的知识库使用的默认嵌入模型。如未显示可选模型,请检查你是否在使用 RAGFlow slim 版(不含嵌入模型);或根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。',
img2txtModel: 'Img2txt模型', img2txtModel: 'VLM',
img2txtModelTip: img2txtModelTip:
'所有新创建的知识库都将使用默认的 img2txt 模型。 它可以描述图片或视频。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。', '所有新创建的知识库都将使用默认的 img2txt 模型。 它可以描述图片或视频。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。',
sequence2txtModel: 'Speech2txt模型', sequence2txtModel: 'ASR',
sequence2txtModelTip: sequence2txtModelTip:
'所有新创建的知识库都将使用默认的 ASR 模型。 使用此模型将语音翻译为相应的文本。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。', '所有新创建的知识库都将使用默认的 ASR 模型。 使用此模型将语音翻译为相应的文本。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。',
rerankModel: 'Rerank模型', rerankModel: 'Rerank',
rerankModelTip: `默认的 reranking 模型。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。`, rerankModelTip: `默认的 reranking 模型。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。`,
workspace: '工作空间', workspace: '工作空间',
upgrade: '升级', upgrade: '升级',

View File

@ -31,9 +31,16 @@ export function TemplateCard({ data, showModal }: IProps) {
avatar={data.avatar ? data.avatar : 'https://github.com/shadcn.png'} avatar={data.avatar ? data.avatar : 'https://github.com/shadcn.png'}
name={data?.title[language] || 'CN'} name={data?.title[language] || 'CN'}
></RAGFlowAvatar> ></RAGFlowAvatar>
<div className="text-[18px] font-bold break-words hyphens-auto overflow-hidden"lang={language}>{data?.title[language]}</div> <div
className="text-[18px] font-bold break-words hyphens-auto overflow-hidden"
lang={language}
>
{data?.title[language]}
</div>
</div> </div>
<p className="break-words hypens-auto"lang={language}>{data?.description[language]}</p> <p className="break-words hypens-auto" lang={language}>
{data?.description[language]}
</p>
<div className="group-hover:bg-gradient-to-t from-black/70 from-10% via-black/0 via-50% to-black/0 w-full h-full group-hover:block absolute top-0 left-0 hidden rounded-xl"> <div className="group-hover:bg-gradient-to-t from-black/70 from-10% via-black/0 via-50% to-black/0 w-full h-full group-hover:block absolute top-0 left-0 hidden rounded-xl">
<Button <Button
variant="default" variant="default"

View File

@ -0,0 +1,173 @@
// src/components/ModelProviderCard.tsx
import { LlmIcon } from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import { LlmItem } from '@/hooks/llm-hooks';
import { getRealModelName } from '@/utils/llm-util';
import { EditOutlined, SettingOutlined } from '@ant-design/icons';
import { ChevronsDown, ChevronsUp, Trash2 } from 'lucide-react';
import { FC } from 'react';
import { isLocalLlmFactory } from '../../utils';
import { useHandleDeleteFactory, useHandleDeleteLlm } from '../hooks';
interface IModelCardProps {
item: LlmItem;
clickApiKey: (llmFactory: string) => void;
handleEditModel: (model: any, factory: LlmItem) => void;
}
type TagType =
| 'LLM'
| 'TEXT EMBEDDING'
| 'TEXT RE-RANK'
| 'TTS'
| 'SPEECH2TEXT'
| 'IMAGE2TEXT'
| 'MODERATION';
const sortTags = (tags: string) => {
const orderMap: Record<TagType, number> = {
LLM: 1,
'TEXT EMBEDDING': 2,
'TEXT RE-RANK': 3,
TTS: 4,
SPEECH2TEXT: 5,
IMAGE2TEXT: 6,
MODERATION: 7,
};
return tags
.split(',')
.map((tag) => tag.trim())
.sort(
(a, b) =>
(orderMap[a as TagType] || 999) - (orderMap[b as TagType] || 999),
);
};
export const ModelProviderCard: FC<IModelCardProps> = ({
item,
clickApiKey,
handleEditModel,
}) => {
const { visible, switchVisible } = useSetModalState();
const { t } = useTranslate('setting');
const { handleDeleteLlm } = useHandleDeleteLlm(item.name);
const { handleDeleteFactory } = useHandleDeleteFactory(item.name);
const handleApiKeyClick = () => {
clickApiKey(item.name);
};
const handleShowMoreClick = () => {
switchVisible();
};
return (
<div className={`w-full rounded-lg border border-border-default`}>
{/* Header */}
<div className="flex items-center justify-between p-4 cursor-pointer transition-colors">
<div className="flex items-center space-x-3">
<LlmIcon name={item.name} />
<div>
<h3 className="font-medium">{item.name}</h3>
</div>
</div>
<div className="flex items-center space-x-2">
<Button
onClick={(e) => {
e.stopPropagation();
handleApiKeyClick();
}}
className="px-3 py-1 text-sm bg-bg-input hover:bg-bg-input text-text-primary rounded-md transition-colors flex items-center space-x-1"
>
<SettingOutlined />
<span>
{isLocalLlmFactory(item.name) ? t('addTheModel') : 'API-Key'}
</span>
</Button>
<Button
onClick={(e) => {
e.stopPropagation();
handleShowMoreClick();
}}
className="px-3 py-1 text-sm bg-bg-input hover:bg-bg-input text-text-primary rounded-md transition-colors flex items-center space-x-1"
>
<span>{visible ? t('hideModels') : t('showMoreModels')}</span>
{visible ? <ChevronsDown /> : <ChevronsUp />}
</Button>
<Button
variant={'secondary'}
onClick={(e) => {
e.stopPropagation();
handleDeleteFactory();
}}
className="p-1 text-text-primary hover:text-state-error transition-colors"
>
<Trash2 />
</Button>
</div>
</div>
{/* Content */}
{visible && (
<div className="">
<div className="px-4 flex flex-wrap gap-1 mt-1">
{sortTags(item.tags).map((tag, index) => (
<span
key={index}
className="px-2 py-1 text-xs bg-bg-card text-text-secondary rounded-md"
>
{tag}
</span>
))}
</div>
<div className="m-4 bg-bg-card rounded-lg max-h-96 overflow-auto scrollbar-auto">
<div className="">
{item.llm.map((model) => (
<div
key={model.name}
className="flex items-center border-b border-border-default justify-between p-3 hover:bg-bg-card transition-colors"
>
<div className="flex items-center space-x-3">
<span className="font-medium">
{getRealModelName(model.name)}
</span>
<span className="px-2 py-1 text-xs bg-bg-card text-text-secondary rounded-md">
{model.type}
</span>
</div>
<div className="flex items-center space-x-2">
{isLocalLlmFactory(item.name) && (
<Button
variant={'secondary'}
onClick={() => handleEditModel(model, item)}
className="p-1 text-text-primary transition-colors"
>
<EditOutlined />
</Button>
)}
<Button
variant={'secondary'}
onClick={() => {
handleDeleteLlm(model.name);
console.log(handleDeleteLlm, model.name);
}}
className="p-1 hover:text-state-error transition-colors"
>
<Trash2 />
</Button>
</div>
</div>
))}
</div>
</div>
</div>
)}
</div>
);
};

View File

@ -0,0 +1,194 @@
import {
SelectWithSearch,
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { LlmModelType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import {
ISystemModelSettingSavingParams,
useComposeLlmOptionsByModelTypes,
} from '@/hooks/llm-hooks';
import { CircleQuestionMark } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useFetchSystemModelSettingOnMount } from '../hooks';
interface IProps {
loading: boolean;
onOk: (
payload: Omit<ISystemModelSettingSavingParams, 'tenant_id' | 'name'>,
) => void;
}
const SystemSetting = ({ onOk, loading }: IProps) => {
const { systemSetting: initialValues, allOptions } =
useFetchSystemModelSettingOnMount();
const { t } = useTranslate('setting');
const [formData, setFormData] = useState({
llm_id: '',
embd_id: '',
img2txt_id: '',
asr_id: '',
rerank_id: '',
tts_id: '',
});
const handleFieldChange = useCallback(
(field: string, value: string) => {
const updatedData = { ...formData, [field]: value || '' };
setFormData(updatedData);
console.log('updatedData', updatedData);
onOk(updatedData);
},
[formData, onOk],
);
useEffect(() => {
setFormData({
llm_id: initialValues.llm_id ?? '',
embd_id: initialValues.embd_id ?? '',
img2txt_id: initialValues.img2txt_id ?? '',
asr_id: initialValues.asr_id ?? '',
rerank_id: initialValues.rerank_id ?? '',
tts_id: initialValues.tts_id ?? '',
});
}, [initialValues]);
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
const llmList = useMemo(() => {
return [
{
id: 'llm_id',
label: t('chatModel'),
isRequired: true,
value: formData.llm_id,
options: modelOptions as SelectWithSearchFlagOptionType[],
tooltip: t('chatModelTip'),
},
{
id: 'embd_id',
label: t('embeddingModel'),
value: formData.embd_id,
options: allOptions[
LlmModelType.Embedding
] as SelectWithSearchFlagOptionType[],
tooltip: t('embeddingModelTip'),
},
{
id: 'img2txt_id',
label: t('img2txtModel'),
value: formData.img2txt_id,
options: allOptions[
LlmModelType.Image2text
] as SelectWithSearchFlagOptionType[],
tooltip: t('img2txtModelTip'),
},
{
id: 'asr_id',
label: t('sequence2txtModel'),
value: formData.asr_id,
options: allOptions[
LlmModelType.Speech2text
] as SelectWithSearchFlagOptionType[],
tooltip: t('sequence2txtModelTip'),
},
{
id: 'rerank_id',
label: t('rerankModel'),
value: formData.rerank_id,
options: allOptions[
LlmModelType.Rerank
] as SelectWithSearchFlagOptionType[],
tooltip: t('rerankModelTip'),
},
{
id: 'tts_id',
label: t('ttsModel'),
value: formData.tts_id,
options: allOptions[
LlmModelType.TTS
] as SelectWithSearchFlagOptionType[],
tooltip: t('ttsModelTip'),
},
];
}, [formData, modelOptions, t, allOptions]);
const Items = ({
label,
value,
options,
tooltip,
id,
isRequired,
}: {
id: string;
label: string;
value: string;
options: SelectWithSearchFlagOptionType[];
tooltip?: string;
isRequired?: boolean;
}) => {
return (
<div className="flex gap-3">
<label className="block text-sm font-medium text-text-primary mb-1 w-1/4">
{isRequired && <span className="text-red-500">*</span>}
{label}
{tooltip && (
<Tooltip>
<TooltipContent>{tooltip}</TooltipContent>
<TooltipTrigger>
<CircleQuestionMark
size={12}
className="ml-1 text-text-disabled text-xs"
/>
</TooltipTrigger>
</Tooltip>
)}
</label>
<SelectWithSearch
triggerClassName="w-3/4"
value={value}
options={options}
onChange={(value) => handleFieldChange(id, value)}
placeholder={t('common:selectPlaceholder')}
/>
</div>
);
};
return (
<div className="rounded-lg w-full">
<div className="flex flex-col py-4">
<div className="text-2xl font-semibold">{t('systemModelSettings')}</div>
<div className="text-sm text-text-secondary">
{t('systemModelDescription')}
</div>
</div>
<div className="px-7 py-6 space-y-6 max-h-[70vh] overflow-y-auto border rounded-lg">
{llmList.map((item) => (
<Items key={item.id} {...item} />
))}
</div>
{/* <div className="border-t px-6 py-4 flex justify-end">
<Button
onClick={hideModal}
disabled={loading}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
>
{t('common:cancel')}
</Button>
</div> */}
</div>
);
};
export default SystemSetting;

View File

@ -0,0 +1,157 @@
// src/components/AvailableModels.tsx
import { LlmIcon } from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import { useTranslate } from '@/hooks/common-hooks';
import { useSelectLlmList } from '@/hooks/llm-hooks';
import { Plus, Search } from 'lucide-react';
import { FC, useMemo, useState } from 'react';
type TagType =
| 'LLM'
| 'TEXT EMBEDDING'
| 'TEXT RE-RANK'
| 'TTS'
| 'SPEECH2TEXT'
| 'IMAGE2TEXT'
| 'MODERATION';
const sortTags = (tags: string) => {
const orderMap: Record<TagType, number> = {
LLM: 1,
'TEXT EMBEDDING': 2,
'TEXT RE-RANK': 3,
TTS: 4,
SPEECH2TEXT: 5,
IMAGE2TEXT: 6,
MODERATION: 7,
};
return tags
.split(',')
.map((tag) => tag.trim())
.sort(
(a, b) =>
(orderMap[a as TagType] || 999) - (orderMap[b as TagType] || 999),
);
};
export const AvailableModels: FC<{
handleAddModel: (factory: string) => void;
}> = ({ handleAddModel }) => {
const { t } = useTranslate('setting');
const { factoryList } = useSelectLlmList();
const [searchTerm, setSearchTerm] = useState('');
const [selectedTag, setSelectedTag] = useState<string | null>(null);
// 过滤模型列表
const filteredModels = useMemo(() => {
return factoryList.filter((model) => {
const matchesSearch = model.name
.toLowerCase()
.includes(searchTerm.toLowerCase());
const matchesTag =
selectedTag === null ||
model.tags.split(',').some((tag) => tag.trim() === selectedTag);
return matchesSearch && matchesTag;
});
}, [factoryList, searchTerm, selectedTag]);
// 获取所有唯一的标签
const allTags = useMemo(() => {
const tagsSet = new Set<string>();
factoryList.forEach((model) => {
model.tags.split(',').forEach((tag) => tagsSet.add(tag.trim()));
});
return Array.from(tagsSet).sort();
}, [factoryList]);
const handleTagClick = (tag: string) => {
setSelectedTag(selectedTag === tag ? null : tag);
};
return (
<div className=" text-text-primary h-full p-4">
<div className="text-text-primary text-base mb-4">
{t('availableModels')}
</div>
{/* Search Bar */}
<div className="mb-6">
<div className="relative">
<input
type="text"
placeholder={t('search')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-3 pl-10 bg-bg-input border border-border-default rounded-lg focus:outline-none focus:ring-1 focus:ring-border-button transition-colors"
/>
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-text-secondary" />
</div>
</div>
{/* Tags Filter */}
<div className="flex flex-wrap gap-2 mb-6">
<Button
variant={'secondary'}
onClick={() => setSelectedTag(null)}
className={`px-1 py-1 text-xs rounded-md bg-bg-card bg-bg-card h-5 transition-colors ${
selectedTag === null
? ' text-text-primary border border-text-primary'
: 'text-text-secondary bg-bg-input border-none'
}`}
>
All
</Button>
{allTags.map((tag) => (
<Button
variant={'secondary'}
key={tag}
onClick={() => handleTagClick(tag)}
className={`px-1 py-1 text-xs rounded-md bg-bg-card h-5 transition-colors ${
selectedTag === tag
? ' text-text-primary border border-text-primary'
: 'text-text-secondary border-none'
}`}
>
{tag}
</Button>
))}
</div>
{/* Models List */}
<div className="flex flex-col gap-4 overflow-auto h-[calc(100vh-300px)] scrollbar-auto">
{filteredModels.map((model) => (
<div
key={model.name}
className=" border border-border-default rounded-lg p-3 hover:bg-bg-input transition-colors"
>
<div className="flex items-center space-x-3 mb-3">
<LlmIcon name={model.name} imgClass="h-8 w-auto" />
<div className="flex-1">
<h3 className="font-medium truncate">{model.name}</h3>
</div>
<Button
className=" px-2 flex items-center gap-0 text-xs h-6 rounded-md transition-colors"
onClick={() => handleAddModel(model.name)}
>
<Plus size={12} />
{t('addTheModel')}
</Button>
</div>
<div className="flex flex-wrap gap-1 mb-3">
{sortTags(model.tags).map((tag, index) => (
<span
key={index}
className="px-1 flex items-center h-5 text-xs bg-bg-card text-text-secondary rounded-md"
>
{tag}
</span>
))}
</div>
</div>
))}
</div>
</div>
);
};

View File

@ -0,0 +1,27 @@
import { LlmItem, useSelectLlmList } from '@/hooks/llm-hooks';
import { ModelProviderCard } from './modal-card';
export const UsedModel = ({
handleAddModel,
handleEditModel,
}: {
handleAddModel: (factory: string) => void;
handleEditModel: (model: any, factory: LlmItem) => void;
}) => {
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
return (
<div className="flex flex-col w-full">
<div className="text-text-primary text-2xl mb-4 mt-4">Added models</div>
{llmList.map((llm) => {
return (
<ModelProviderCard
key={llm.name}
item={llm}
clickApiKey={handleAddModel}
handleEditModel={handleEditModel}
/>
);
})}
</div>
);
};

View File

@ -3,8 +3,6 @@
.factoryOperationWrapper { .factoryOperationWrapper {
text-align: right; text-align: right;
} }
.modelItem {
}
.llmList { .llmList {
padding-top: 10px; padding-top: 10px;
} }

View File

@ -1,47 +1,16 @@
import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg';
import { LlmIcon } from '@/components/svg-icon';
import { useTheme } from '@/components/theme-provider';
import { LLMFactory } from '@/constants/llm'; import { LLMFactory } from '@/constants/llm';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; import { LlmItem, useFetchMyLlmListDetailed } from '@/hooks/llm-hooks';
import {
LlmItem,
useFetchMyLlmListDetailed,
useSelectLlmList,
} from '@/hooks/llm-hooks';
import { getRealModelName } from '@/utils/llm-util';
import {
CloseCircleOutlined,
EditOutlined,
SettingOutlined,
} from '@ant-design/icons';
import {
Button,
Card,
Col,
Collapse,
CollapseProps,
Divider,
Flex,
List,
Row,
Space,
Spin,
Tag,
Tooltip,
Typography,
} from 'antd';
import { CircleHelp } from 'lucide-react';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import SettingTitle from '../components/setting-title';
import { isLocalLlmFactory } from '../utils'; import { isLocalLlmFactory } from '../utils';
import ApiKeyModal from './api-key-modal'; import ApiKeyModal from './api-key-modal';
import AzureOpenAIModal from './azure-openai-modal'; import AzureOpenAIModal from './azure-openai-modal';
import BedrockModal from './bedrock-modal'; import BedrockModal from './bedrock-modal';
import SystemSetting from './components/system-setting';
import { AvailableModels } from './components/un-add-model';
import { UsedModel } from './components/used-model';
import FishAudioModal from './fish-audio-modal'; import FishAudioModal from './fish-audio-modal';
import GoogleModal from './google-modal'; import GoogleModal from './google-modal';
import { import {
useHandleDeleteFactory,
useHandleDeleteLlm,
useSubmitApiKey, useSubmitApiKey,
useSubmitAzure, useSubmitAzure,
useSubmitBedrock, useSubmitBedrock,
@ -56,164 +25,15 @@ import {
useSubmityiyan, useSubmityiyan,
} from './hooks'; } from './hooks';
import HunyuanModal from './hunyuan-modal'; import HunyuanModal from './hunyuan-modal';
import styles from './index.less';
import TencentCloudModal from './next-tencent-modal'; import TencentCloudModal from './next-tencent-modal';
import OllamaModal from './ollama-modal'; import OllamaModal from './ollama-modal';
import SparkModal from './spark-modal'; import SparkModal from './spark-modal';
import SystemModelSettingModal from './system-model-setting-modal';
import VolcEngineModal from './volcengine-modal'; import VolcEngineModal from './volcengine-modal';
import YiyanModal from './yiyan-modal'; import YiyanModal from './yiyan-modal';
const ModelProviders = () => {
const { Text } = Typography; const { saveSystemModelSettingLoading, onSystemSettingSavingOk } =
interface IModelCardProps { useSubmitSystemModelSetting();
item: LlmItem;
clickApiKey: (llmFactory: string) => void;
handleEditModel: (model: any, factory: LlmItem) => void;
}
type TagType =
| 'LLM'
| 'TEXT EMBEDDING'
| 'TEXT RE-RANK'
| 'TTS'
| 'SPEECH2TEXT'
| 'IMAGE2TEXT'
| 'MODERATION';
const sortTags = (tags: string) => {
const orderMap: Record<TagType, number> = {
LLM: 1,
'TEXT EMBEDDING': 2,
'TEXT RE-RANK': 3,
TTS: 4,
SPEECH2TEXT: 5,
IMAGE2TEXT: 6,
MODERATION: 7,
};
return tags
.split(',')
.map((tag) => tag.trim())
.sort(
(a, b) =>
(orderMap[a as TagType] || 999) - (orderMap[b as TagType] || 999),
);
};
const ModelCard = ({ item, clickApiKey, handleEditModel }: IModelCardProps) => {
const { visible, switchVisible } = useSetModalState();
const { t } = useTranslate('setting');
const { theme } = useTheme();
const { handleDeleteLlm } = useHandleDeleteLlm(item.name);
const { handleDeleteFactory } = useHandleDeleteFactory(item.name);
const handleApiKeyClick = () => {
clickApiKey(item.name);
};
const handleShowMoreClick = () => {
switchVisible();
};
return (
<List.Item>
<Card
className={theme === 'dark' ? styles.addedCardDark : styles.addedCard}
>
<Row align={'middle'}>
<Col span={12}>
<Flex gap={'middle'} align="center">
<LlmIcon name={item.name} />
<Flex vertical gap={'small'}>
<b>{item.name}</b>
<Flex wrap="wrap">
{sortTags(item.tags).map((tag, index) => (
<Tag
key={index}
style={{
fontSize: '12px',
margin: '1px',
paddingInline: '4px',
}}
>
{tag}
</Tag>
))}
</Flex>
</Flex>
</Flex>
</Col>
<Col span={12} className={styles.factoryOperationWrapper}>
<Space size={'middle'}>
<Button onClick={handleApiKeyClick}>
<Flex align="center" gap={4}>
{isLocalLlmFactory(item.name) ||
item.name === LLMFactory.VolcEngine ||
item.name === LLMFactory.TencentHunYuan ||
item.name === LLMFactory.XunFeiSpark ||
item.name === LLMFactory.BaiduYiYan ||
item.name === LLMFactory.FishAudio ||
item.name === LLMFactory.TencentCloud ||
item.name === LLMFactory.GoogleCloud ||
item.name === LLMFactory.AzureOpenAI
? t('addTheModel')
: 'API-Key'}
<SettingOutlined />
</Flex>
</Button>
<Button onClick={handleShowMoreClick}>
<Flex align="center" gap={4}>
{visible ? t('hideModels') : t('showMoreModels')}
<MoreModelIcon />
</Flex>
</Button>
<Button type={'text'} onClick={handleDeleteFactory}>
<Flex align="center">
<CloseCircleOutlined style={{ color: '#D92D20' }} />
</Flex>
</Button>
</Space>
</Col>
</Row>
{visible && (
<List
size="small"
dataSource={item.llm}
className={styles.llmList}
renderItem={(model) => (
<List.Item>
<Space>
{getRealModelName(model.name)}
<Tag color="#b8b8b8">{model.type}</Tag>
{isLocalLlmFactory(item.name) && (
<Tooltip title={t('edit', { keyPrefix: 'common' })}>
<Button
type={'text'}
onClick={() => handleEditModel(model, item)}
>
<EditOutlined style={{ color: '#1890ff' }} />
</Button>
</Tooltip>
)}
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
<Button type={'text'} onClick={handleDeleteLlm(model.name)}>
<CloseCircleOutlined style={{ color: '#D92D20' }} />
</Button>
</Tooltip>
</Space>
</List.Item>
)}
/>
)}
</Card>
</List.Item>
);
};
const UserSettingModel = () => {
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
const { data: detailedLlmList } = useFetchMyLlmListDetailed(); const { data: detailedLlmList } = useFetchMyLlmListDetailed();
const { theme } = useTheme();
const { const {
saveApiKeyLoading, saveApiKeyLoading,
initialApiKey, initialApiKey,
@ -224,14 +44,6 @@ const UserSettingModel = () => {
hideApiKeyModal, hideApiKeyModal,
showApiKeyModal, showApiKeyModal,
} = useSubmitApiKey(); } = useSubmitApiKey();
const {
saveSystemModelSettingLoading,
onSystemSettingSavingOk,
systemSettingVisible,
hideSystemSettingModal,
showSystemSettingModal,
} = useSubmitSystemModelSetting();
const { t } = useTranslate('setting');
const { const {
llmAddingVisible, llmAddingVisible,
hideLlmAddingModal, hideLlmAddingModal,
@ -342,6 +154,7 @@ const UserSettingModel = () => {
const handleAddModel = useCallback( const handleAddModel = useCallback(
(llmFactory: string) => { (llmFactory: string) => {
console.log('handleAddModel', llmFactory);
if (isLocalLlmFactory(llmFactory)) { if (isLocalLlmFactory(llmFactory)) {
showLlmAddingModal(llmFactory); showLlmAddingModal(llmFactory);
} else if (llmFactory in ModalMap) { } else if (llmFactory in ModalMap) {
@ -378,116 +191,21 @@ const UserSettingModel = () => {
}, },
[showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList], [showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList],
); );
const items: CollapseProps['items'] = [
{
key: '1',
label: t('addedModels'),
children: (
<List
grid={{ gutter: 16, column: 1 }}
dataSource={llmList}
renderItem={(item) => (
<ModelCard
item={item}
clickApiKey={handleAddModel}
handleEditModel={handleEditModel}
></ModelCard>
)}
/>
),
},
{
key: '2',
label: (
<div className="flex items-center gap-2">
{t('modelsToBeAdded')}
<Tooltip title={t('modelsToBeAddedTooltip')}>
<CircleHelp className="size-4" />
</Tooltip>
</div>
),
children: (
<List
grid={{
gutter: {
xs: 8,
sm: 10,
md: 12,
lg: 16,
xl: 20,
xxl: 24,
},
xs: 1,
sm: 1,
md: 2,
lg: 3,
xl: 4,
xxl: 8,
}}
dataSource={factoryList}
renderItem={(item) => (
<List.Item>
<Card
className={
theme === 'dark'
? styles.toBeAddedCardDark
: styles.toBeAddedCard
}
>
<Flex vertical gap={'middle'}>
<LlmIcon name={item.name} imgClass="h-12 w-auto" />
<Flex vertical gap={'middle'}>
<b>
<Text ellipsis={{ tooltip: item.name }}>{item.name}</Text>
</b>
<Flex wrap="wrap" style={{ minHeight: '50px' }}>
{sortTags(item.tags).map((tag, index) => (
<Tag
key={index}
style={{
fontSize: '8px',
margin: '1px',
paddingInline: '4px',
height: '22px',
}}
>
{tag}
</Tag>
))}
</Flex>
</Flex>
</Flex>
<Divider className={styles.modelDivider}></Divider>
<Button
type="link"
onClick={() => handleAddModel(item.name)}
className={styles.addButton}
>
{t('addTheModel')}
</Button>
</Card>
</List.Item>
)}
/>
),
},
];
return ( return (
<section id="xx" className="w-full space-y-6"> <div className="flex w-full">
<Spin spinning={loading}> <section className="flex flex-col gap-4 w-3/5 px-5 border-r border-border-button overflow-auto scrollbar-auto">
<section className={styles.modelContainer}> <SystemSetting
<SettingTitle onOk={onSystemSettingSavingOk}
title={t('model')} loading={saveSystemModelSettingLoading}
description={t('modelDescription')} />
showRightButton <UsedModel
clickButton={showSystemSettingModal} handleAddModel={handleAddModel}
></SettingTitle> handleEditModel={handleEditModel}
<Divider></Divider> />
<Collapse defaultActiveKey={['1', '2']} ghost items={items} /> </section>
</section> <section className="flex flex-col w-2/5 overflow-auto scrollbar-auto">
</Spin> <AvailableModels handleAddModel={handleAddModel} />
</section>
<ApiKeyModal <ApiKeyModal
visible={apiKeyVisible} visible={apiKeyVisible}
hideModal={hideApiKeyModal} hideModal={hideApiKeyModal}
@ -497,14 +215,6 @@ const UserSettingModel = () => {
onOk={onApiKeySavingOk} onOk={onApiKeySavingOk}
llmFactory={llmFactory} llmFactory={llmFactory}
></ApiKeyModal> ></ApiKeyModal>
{systemSettingVisible && (
<SystemModelSettingModal
visible={systemSettingVisible}
onOk={onSystemSettingSavingOk}
hideModal={hideSystemSettingModal}
loading={saveSystemModelSettingLoading}
></SystemModelSettingModal>
)}
<OllamaModal <OllamaModal
visible={llmAddingVisible} visible={llmAddingVisible}
hideModal={hideLlmAddingModal} hideModal={hideLlmAddingModal}
@ -577,8 +287,7 @@ const UserSettingModel = () => {
loading={AzureAddingLoading} loading={AzureAddingLoading}
llmFactory={LLMFactory.AzureOpenAI} llmFactory={LLMFactory.AzureOpenAI}
></AzureOpenAIModal> ></AzureOpenAIModal>
</section> </div>
); );
}; };
export default ModelProviders;
export default UserSettingModel;

View File

@ -0,0 +1,584 @@
import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg';
import { LlmIcon } from '@/components/svg-icon';
import { useTheme } from '@/components/theme-provider';
import { LLMFactory } from '@/constants/llm';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import {
LlmItem,
useFetchMyLlmListDetailed,
useSelectLlmList,
} from '@/hooks/llm-hooks';
import { getRealModelName } from '@/utils/llm-util';
import {
CloseCircleOutlined,
EditOutlined,
SettingOutlined,
} from '@ant-design/icons';
import {
Button,
Card,
Col,
Collapse,
CollapseProps,
Divider,
Flex,
List,
Row,
Space,
Spin,
Tag,
Tooltip,
Typography,
} from 'antd';
import { CircleHelp } from 'lucide-react';
import { useCallback, useMemo } from 'react';
import SettingTitle from '../components/setting-title';
import { isLocalLlmFactory } from '../utils';
import ApiKeyModal from './api-key-modal';
import AzureOpenAIModal from './azure-openai-modal';
import BedrockModal from './bedrock-modal';
import FishAudioModal from './fish-audio-modal';
import GoogleModal from './google-modal';
import {
useHandleDeleteFactory,
useHandleDeleteLlm,
useSubmitApiKey,
useSubmitAzure,
useSubmitBedrock,
useSubmitFishAudio,
useSubmitGoogle,
useSubmitHunyuan,
useSubmitOllama,
useSubmitSpark,
useSubmitSystemModelSetting,
useSubmitTencentCloud,
useSubmitVolcEngine,
useSubmityiyan,
} from './hooks';
import HunyuanModal from './hunyuan-modal';
import styles from './index.less';
import TencentCloudModal from './next-tencent-modal';
import OllamaModal from './ollama-modal';
import SparkModal from './spark-modal';
import SystemModelSettingModal from './system-model-setting-modal';
import VolcEngineModal from './volcengine-modal';
import YiyanModal from './yiyan-modal';
const { Text } = Typography;
interface IModelCardProps {
item: LlmItem;
clickApiKey: (llmFactory: string) => void;
handleEditModel: (model: any, factory: LlmItem) => void;
}
type TagType =
| 'LLM'
| 'TEXT EMBEDDING'
| 'TEXT RE-RANK'
| 'TTS'
| 'SPEECH2TEXT'
| 'IMAGE2TEXT'
| 'MODERATION';
const sortTags = (tags: string) => {
const orderMap: Record<TagType, number> = {
LLM: 1,
'TEXT EMBEDDING': 2,
'TEXT RE-RANK': 3,
TTS: 4,
SPEECH2TEXT: 5,
IMAGE2TEXT: 6,
MODERATION: 7,
};
return tags
.split(',')
.map((tag) => tag.trim())
.sort(
(a, b) =>
(orderMap[a as TagType] || 999) - (orderMap[b as TagType] || 999),
);
};
const ModelCard = ({ item, clickApiKey, handleEditModel }: IModelCardProps) => {
const { visible, switchVisible } = useSetModalState();
const { t } = useTranslate('setting');
const { theme } = useTheme();
const { handleDeleteLlm } = useHandleDeleteLlm(item.name);
const { handleDeleteFactory } = useHandleDeleteFactory(item.name);
const handleApiKeyClick = () => {
clickApiKey(item.name);
};
const handleShowMoreClick = () => {
switchVisible();
};
return (
<List.Item>
<Card
className={theme === 'dark' ? styles.addedCardDark : styles.addedCard}
>
<Row align={'middle'}>
<Col span={12}>
<Flex gap={'middle'} align="center">
<LlmIcon name={item.name} />
<Flex vertical gap={'small'}>
<b>{item.name}</b>
<Flex wrap="wrap">
{sortTags(item.tags).map((tag, index) => (
<Tag
key={index}
style={{
fontSize: '12px',
margin: '1px',
paddingInline: '4px',
}}
>
{tag}
</Tag>
))}
</Flex>
</Flex>
</Flex>
</Col>
<Col span={12} className={styles.factoryOperationWrapper}>
<Space size={'middle'}>
<Button onClick={handleApiKeyClick}>
<Flex align="center" gap={4}>
{isLocalLlmFactory(item.name) ||
item.name === LLMFactory.VolcEngine ||
item.name === LLMFactory.TencentHunYuan ||
item.name === LLMFactory.XunFeiSpark ||
item.name === LLMFactory.BaiduYiYan ||
item.name === LLMFactory.FishAudio ||
item.name === LLMFactory.TencentCloud ||
item.name === LLMFactory.GoogleCloud ||
item.name === LLMFactory.AzureOpenAI
? t('addTheModel')
: 'API-Key'}
<SettingOutlined />
</Flex>
</Button>
<Button onClick={handleShowMoreClick}>
<Flex align="center" gap={4}>
{visible ? t('hideModels') : t('showMoreModels')}
<MoreModelIcon />
</Flex>
</Button>
<Button type={'text'} onClick={handleDeleteFactory}>
<Flex align="center">
<CloseCircleOutlined style={{ color: '#D92D20' }} />
</Flex>
</Button>
</Space>
</Col>
</Row>
{visible && (
<List
size="small"
dataSource={item.llm}
className={styles.llmList}
renderItem={(model) => (
<List.Item>
<Space>
{getRealModelName(model.name)}
<Tag color="#b8b8b8">{model.type}</Tag>
{isLocalLlmFactory(item.name) && (
<Tooltip title={t('edit', { keyPrefix: 'common' })}>
<Button
type={'text'}
onClick={() => handleEditModel(model, item)}
>
<EditOutlined style={{ color: '#1890ff' }} />
</Button>
</Tooltip>
)}
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
<Button type={'text'} onClick={handleDeleteLlm(model.name)}>
<CloseCircleOutlined style={{ color: '#D92D20' }} />
</Button>
</Tooltip>
</Space>
</List.Item>
)}
/>
)}
</Card>
</List.Item>
);
};
const UserSettingModel = () => {
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
const { data: detailedLlmList } = useFetchMyLlmListDetailed();
const { theme } = useTheme();
const {
saveApiKeyLoading,
initialApiKey,
llmFactory,
editMode,
onApiKeySavingOk,
apiKeyVisible,
hideApiKeyModal,
showApiKeyModal,
} = useSubmitApiKey();
const {
saveSystemModelSettingLoading,
onSystemSettingSavingOk,
systemSettingVisible,
hideSystemSettingModal,
showSystemSettingModal,
} = useSubmitSystemModelSetting();
const { t } = useTranslate('setting');
const {
llmAddingVisible,
hideLlmAddingModal,
showLlmAddingModal,
onLlmAddingOk,
llmAddingLoading,
editMode: llmEditMode,
initialValues: llmInitialValues,
selectedLlmFactory,
} = useSubmitOllama();
const {
volcAddingVisible,
hideVolcAddingModal,
showVolcAddingModal,
onVolcAddingOk,
volcAddingLoading,
} = useSubmitVolcEngine();
const {
HunyuanAddingVisible,
hideHunyuanAddingModal,
showHunyuanAddingModal,
onHunyuanAddingOk,
HunyuanAddingLoading,
} = useSubmitHunyuan();
const {
GoogleAddingVisible,
hideGoogleAddingModal,
showGoogleAddingModal,
onGoogleAddingOk,
GoogleAddingLoading,
} = useSubmitGoogle();
const {
TencentCloudAddingVisible,
hideTencentCloudAddingModal,
showTencentCloudAddingModal,
onTencentCloudAddingOk,
TencentCloudAddingLoading,
} = useSubmitTencentCloud();
const {
SparkAddingVisible,
hideSparkAddingModal,
showSparkAddingModal,
onSparkAddingOk,
SparkAddingLoading,
} = useSubmitSpark();
const {
yiyanAddingVisible,
hideyiyanAddingModal,
showyiyanAddingModal,
onyiyanAddingOk,
yiyanAddingLoading,
} = useSubmityiyan();
const {
FishAudioAddingVisible,
hideFishAudioAddingModal,
showFishAudioAddingModal,
onFishAudioAddingOk,
FishAudioAddingLoading,
} = useSubmitFishAudio();
const {
bedrockAddingLoading,
onBedrockAddingOk,
bedrockAddingVisible,
hideBedrockAddingModal,
showBedrockAddingModal,
} = useSubmitBedrock();
const {
AzureAddingVisible,
hideAzureAddingModal,
showAzureAddingModal,
onAzureAddingOk,
AzureAddingLoading,
} = useSubmitAzure();
const ModalMap = useMemo(
() => ({
[LLMFactory.Bedrock]: showBedrockAddingModal,
[LLMFactory.VolcEngine]: showVolcAddingModal,
[LLMFactory.TencentHunYuan]: showHunyuanAddingModal,
[LLMFactory.XunFeiSpark]: showSparkAddingModal,
[LLMFactory.BaiduYiYan]: showyiyanAddingModal,
[LLMFactory.FishAudio]: showFishAudioAddingModal,
[LLMFactory.TencentCloud]: showTencentCloudAddingModal,
[LLMFactory.GoogleCloud]: showGoogleAddingModal,
[LLMFactory.AzureOpenAI]: showAzureAddingModal,
}),
[
showBedrockAddingModal,
showVolcAddingModal,
showHunyuanAddingModal,
showTencentCloudAddingModal,
showSparkAddingModal,
showyiyanAddingModal,
showFishAudioAddingModal,
showGoogleAddingModal,
showAzureAddingModal,
],
);
const handleAddModel = useCallback(
(llmFactory: string) => {
if (isLocalLlmFactory(llmFactory)) {
showLlmAddingModal(llmFactory);
} else if (llmFactory in ModalMap) {
ModalMap[llmFactory as keyof typeof ModalMap]();
} else {
showApiKeyModal({ llm_factory: llmFactory });
}
},
[showApiKeyModal, showLlmAddingModal, ModalMap],
);
const handleEditModel = useCallback(
(model: any, factory: LlmItem) => {
if (factory) {
const detailedFactory = detailedLlmList[factory.name];
const detailedModel = detailedFactory?.llm?.find(
(m: any) => m.name === model.name,
);
const editData = {
llm_factory: factory.name,
llm_name: model.name,
model_type: model.type,
};
if (isLocalLlmFactory(factory.name)) {
showLlmAddingModal(factory.name, true, editData, detailedModel);
} else if (factory.name in ModalMap) {
ModalMap[factory.name as keyof typeof ModalMap]();
} else {
showApiKeyModal(editData, true);
}
}
},
[showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList],
);
const items: CollapseProps['items'] = [
{
key: '1',
label: t('addedModels'),
children: (
<List
grid={{ gutter: 16, column: 1 }}
dataSource={llmList}
renderItem={(item) => (
<ModelCard
item={item}
clickApiKey={handleAddModel}
handleEditModel={handleEditModel}
></ModelCard>
)}
/>
),
},
{
key: '2',
label: (
<div className="flex items-center gap-2">
{t('modelsToBeAdded')}
<Tooltip title={t('modelsToBeAddedTooltip')}>
<CircleHelp className="size-4" />
</Tooltip>
</div>
),
children: (
<List
grid={{
gutter: {
xs: 8,
sm: 10,
md: 12,
lg: 16,
xl: 20,
xxl: 24,
},
xs: 1,
sm: 1,
md: 2,
lg: 3,
xl: 4,
xxl: 8,
}}
dataSource={factoryList}
renderItem={(item) => (
<List.Item>
<Card
className={
theme === 'dark'
? styles.toBeAddedCardDark
: styles.toBeAddedCard
}
>
<Flex vertical gap={'middle'}>
<LlmIcon name={item.name} imgClass="h-12 w-auto" />
<Flex vertical gap={'middle'}>
<b>
<Text ellipsis={{ tooltip: item.name }}>{item.name}</Text>
</b>
<Flex wrap="wrap" style={{ minHeight: '50px' }}>
{sortTags(item.tags).map((tag, index) => (
<Tag
key={index}
style={{
fontSize: '8px',
margin: '1px',
paddingInline: '4px',
height: '22px',
}}
>
{tag}
</Tag>
))}
</Flex>
</Flex>
</Flex>
<Divider className={styles.modelDivider}></Divider>
<Button
type="link"
onClick={() => handleAddModel(item.name)}
className={styles.addButton}
>
{t('addTheModel')}
</Button>
</Card>
</List.Item>
)}
/>
),
},
];
return (
<section id="xx" className="w-full space-y-6">
<Spin spinning={loading}>
<section className={styles.modelContainer}>
<SettingTitle
title={t('model')}
description={t('modelDescription')}
showRightButton
clickButton={showSystemSettingModal}
></SettingTitle>
<Divider></Divider>
<Collapse defaultActiveKey={['1', '2']} ghost items={items} />
</section>
</Spin>
<ApiKeyModal
visible={apiKeyVisible}
hideModal={hideApiKeyModal}
loading={saveApiKeyLoading}
initialValue={initialApiKey}
editMode={editMode}
onOk={onApiKeySavingOk}
llmFactory={llmFactory}
></ApiKeyModal>
{systemSettingVisible && (
<SystemModelSettingModal
visible={systemSettingVisible}
onOk={onSystemSettingSavingOk}
hideModal={hideSystemSettingModal}
loading={saveSystemModelSettingLoading}
></SystemModelSettingModal>
)}
<OllamaModal
visible={llmAddingVisible}
hideModal={hideLlmAddingModal}
onOk={onLlmAddingOk}
loading={llmAddingLoading}
editMode={llmEditMode}
initialValues={llmInitialValues}
llmFactory={selectedLlmFactory}
></OllamaModal>
<VolcEngineModal
visible={volcAddingVisible}
hideModal={hideVolcAddingModal}
onOk={onVolcAddingOk}
loading={volcAddingLoading}
llmFactory={LLMFactory.VolcEngine}
></VolcEngineModal>
<HunyuanModal
visible={HunyuanAddingVisible}
hideModal={hideHunyuanAddingModal}
onOk={onHunyuanAddingOk}
loading={HunyuanAddingLoading}
llmFactory={LLMFactory.TencentHunYuan}
></HunyuanModal>
<GoogleModal
visible={GoogleAddingVisible}
hideModal={hideGoogleAddingModal}
onOk={onGoogleAddingOk}
loading={GoogleAddingLoading}
llmFactory={LLMFactory.GoogleCloud}
></GoogleModal>
<TencentCloudModal
visible={TencentCloudAddingVisible}
hideModal={hideTencentCloudAddingModal}
onOk={onTencentCloudAddingOk}
loading={TencentCloudAddingLoading}
llmFactory={LLMFactory.TencentCloud}
></TencentCloudModal>
<SparkModal
visible={SparkAddingVisible}
hideModal={hideSparkAddingModal}
onOk={onSparkAddingOk}
loading={SparkAddingLoading}
llmFactory={LLMFactory.XunFeiSpark}
></SparkModal>
<YiyanModal
visible={yiyanAddingVisible}
hideModal={hideyiyanAddingModal}
onOk={onyiyanAddingOk}
loading={yiyanAddingLoading}
llmFactory={LLMFactory.BaiduYiYan}
></YiyanModal>
<FishAudioModal
visible={FishAudioAddingVisible}
hideModal={hideFishAudioAddingModal}
onOk={onFishAudioAddingOk}
loading={FishAudioAddingLoading}
llmFactory={LLMFactory.FishAudio}
></FishAudioModal>
<BedrockModal
visible={bedrockAddingVisible}
hideModal={hideBedrockAddingModal}
onOk={onBedrockAddingOk}
loading={bedrockAddingLoading}
llmFactory={LLMFactory.Bedrock}
></BedrockModal>
<AzureOpenAIModal
visible={AzureAddingVisible}
hideModal={hideAzureAddingModal}
onOk={onAzureAddingOk}
loading={AzureAddingLoading}
llmFactory={LLMFactory.AzureOpenAI}
></AzureOpenAIModal>
</section>
);
};
export default UserSettingModel;