From a9b47f776bb5fbf28b0ad4dfaca5e5f3e045e345 Mon Sep 17 00:00:00 2001 From: "guangfei.zhao" Date: Fri, 24 Oct 2025 11:41:44 +0800 Subject: [PATCH] refactor(setting): split model dialogs into separate components --- src/constants/llm.ts | 15 + .../components/Dialog/ApiKeyDialog.tsx | 169 ++++ .../components/Dialog/AzureOpenAIDialog.tsx | 171 ++++ .../components/Dialog/BedrockDialog.tsx | 203 ++++ .../components/Dialog/OllamaDialog.tsx | 111 ++ .../components/Dialog/SystemModelDialog.tsx | 306 ++++++ .../setting/components/LLMFactoryCard.tsx | 1 - src/pages/setting/components/ModelDialogs.tsx | 944 ++---------------- src/pages/setting/hooks/useModelDialogs.ts | 5 +- src/pages/setting/models.tsx | 75 +- 10 files changed, 1103 insertions(+), 897 deletions(-) create mode 100644 src/pages/setting/components/Dialog/ApiKeyDialog.tsx create mode 100644 src/pages/setting/components/Dialog/AzureOpenAIDialog.tsx create mode 100644 src/pages/setting/components/Dialog/BedrockDialog.tsx create mode 100644 src/pages/setting/components/Dialog/OllamaDialog.tsx create mode 100644 src/pages/setting/components/Dialog/SystemModelDialog.tsx diff --git a/src/constants/llm.ts b/src/constants/llm.ts index c3f5f6c..ea4d7a7 100644 --- a/src/constants/llm.ts +++ b/src/constants/llm.ts @@ -124,3 +124,18 @@ export const IconMap = { [LLM_FACTORY_LIST.CometAPI]: 'cometapi', [LLM_FACTORY_LIST.DeerAPI]: 'deerapi', }; + +export const LocalLlmFactories: LLMFactory[] = [ + LLM_FACTORY_LIST.LocalAI, + LLM_FACTORY_LIST.Ollama, + LLM_FACTORY_LIST.LMStudio, + LLM_FACTORY_LIST.Xinference, + LLM_FACTORY_LIST.VLLM, + LLM_FACTORY_LIST.OpenAiAPICompatible, + LLM_FACTORY_LIST.TogetherAI, + LLM_FACTORY_LIST.Replicate, + LLM_FACTORY_LIST.OpenRouter, + LLM_FACTORY_LIST.HuggingFace, + LLM_FACTORY_LIST.GPUStack, + LLM_FACTORY_LIST.ModelScope, +] diff --git a/src/pages/setting/components/Dialog/ApiKeyDialog.tsx b/src/pages/setting/components/Dialog/ApiKeyDialog.tsx new file mode 100644 index 0000000..03167ee --- /dev/null +++ b/src/pages/setting/components/Dialog/ApiKeyDialog.tsx @@ -0,0 +1,169 @@ +import React, { useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + Typography, + IconButton, + InputAdornment, + CircularProgress, +} from '@mui/material'; +import { Visibility, VisibilityOff } from '@mui/icons-material'; +import { Controller, useForm } from 'react-hook-form'; + +// 表单数据接口 +export interface ApiKeyFormData { + api_key: string; + base_url?: string; + group_id?: string; +} + +// 对话框 Props 接口 +export interface ApiKeyDialogProps { + open: boolean; + onClose: () => void; + onSubmit: (data: ApiKeyFormData) => void; + loading: boolean; + factoryName: string; + initialData?: ApiKeyFormData; + editMode?: boolean; +} + +/** + * API Key 配置对话框 + */ +function ApiKeyDialog({ + open, + onClose, + onSubmit, + loading, + factoryName, + initialData, + editMode = false, +}: ApiKeyDialogProps) { + const [showApiKey, setShowApiKey] = React.useState(false); + + const { + control, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + defaultValues: { + api_key: '', + base_url: '', + group_id: '', + }, + }); + + // 当对话框打开或初始数据变化时重置表单 + useEffect(() => { + if (open) { + reset(initialData || { api_key: '', base_url: '', group_id: '' }); + } + }, [open, initialData, reset]); + + const handleFormSubmit = (data: ApiKeyFormData) => { + onSubmit(data); + }; + + const toggleShowApiKey = () => { + setShowApiKey(!showApiKey); + }; + + const needsBaseUrl = ['OpenAI', 'AzureOpenAI'].includes(factoryName); + const needsGroupId = factoryName.toLowerCase() === 'minimax'; + + return ( + + + {editMode ? '编辑' : '配置'} {factoryName} API Key + + + + ( + + + {showApiKey ? : } + + + ), + }} + /> + )} + /> + + {needsBaseUrl && ( + ( + + )} + /> + )} + + {needsGroupId && ( + ( + + )} + /> + )} + + + + + + + + ); +}; + +export default ApiKeyDialog; \ No newline at end of file diff --git a/src/pages/setting/components/Dialog/AzureOpenAIDialog.tsx b/src/pages/setting/components/Dialog/AzureOpenAIDialog.tsx new file mode 100644 index 0000000..385288b --- /dev/null +++ b/src/pages/setting/components/Dialog/AzureOpenAIDialog.tsx @@ -0,0 +1,171 @@ +import React, { useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + Typography, + IconButton, + InputAdornment, + CircularProgress, +} from '@mui/material'; +import { Visibility, VisibilityOff } from '@mui/icons-material'; +import { Controller, useForm } from 'react-hook-form'; + +// 表单数据接口 +export interface AzureOpenAIFormData { + api_key: string; + endpoint: string; + api_version: string; +} + +// 对话框 Props 接口 +export interface AzureOpenAIDialogProps { + open: boolean; + onClose: () => void; + onSubmit: (data: AzureOpenAIFormData) => void; + loading: boolean; + initialData?: AzureOpenAIFormData; + editMode?: boolean; +} + +/** + * Azure OpenAI 配置对话框 + */ +function AzureOpenAIDialog ({ + open, + onClose, + onSubmit, + loading, + initialData, + editMode = false, +}: AzureOpenAIDialogProps) { + const [showApiKey, setShowApiKey] = React.useState(false); + + const { + control, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + defaultValues: { + api_key: '', + endpoint: '', + api_version: '2024-02-01', + }, + }); + + // 当对话框打开或初始数据变化时重置表单 + useEffect(() => { + if (open) { + reset(initialData || { api_key: '', endpoint: '', api_version: '2024-02-01' }); + } + }, [open, initialData, reset]); + + const handleFormSubmit = (data: AzureOpenAIFormData) => { + onSubmit(data); + }; + + const toggleShowApiKey = () => { + setShowApiKey(!showApiKey); + }; + + return ( + + + {editMode ? '编辑' : '配置'} Azure OpenAI + + + + ( + + + {showApiKey ? : } + + + ), + }} + /> + )} + /> + + ( + + )} + /> + + ( + + )} + /> + + + + + + + + ); +}; + +export default AzureOpenAIDialog; \ No newline at end of file diff --git a/src/pages/setting/components/Dialog/BedrockDialog.tsx b/src/pages/setting/components/Dialog/BedrockDialog.tsx new file mode 100644 index 0000000..f85a886 --- /dev/null +++ b/src/pages/setting/components/Dialog/BedrockDialog.tsx @@ -0,0 +1,203 @@ +import React, { useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + Typography, + IconButton, + InputAdornment, + FormControl, + InputLabel, + Select, + MenuItem, + CircularProgress, +} from '@mui/material'; +import { Visibility, VisibilityOff } from '@mui/icons-material'; +import { Controller, useForm } from 'react-hook-form'; + +// AWS Bedrock 支持的区域列表 +export const BEDROCK_REGIONS = [ + { value: 'us-east-1', label: 'US East (N. Virginia)' }, + { value: 'us-west-2', label: 'US West (Oregon)' }, + { value: 'ap-southeast-2', label: 'Asia Pacific (Sydney)' }, + { value: 'ap-northeast-1', label: 'Asia Pacific (Tokyo)' }, + { value: 'eu-central-1', label: 'Europe (Frankfurt)' }, + { value: 'eu-west-3', label: 'Europe (Paris)' }, +]; + +// 表单数据接口 +export interface BedrockFormData { + access_key_id: string; + secret_access_key: string; + region: string; +} + +// 对话框 Props 接口 +export interface BedrockDialogProps { + open: boolean; + onClose: () => void; + onSubmit: (data: BedrockFormData) => void; + loading: boolean; + initialData?: BedrockFormData; + editMode?: boolean; +} + +/** + * AWS Bedrock 配置对话框 + */ +function BedrockDialog ({ + open, + onClose, + onSubmit, + loading, + initialData, + editMode = false, +}: BedrockDialogProps) { + const [showAccessKey, setShowAccessKey] = React.useState(false); + const [showSecretKey, setShowSecretKey] = React.useState(false); + + const { + control, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + defaultValues: { + access_key_id: '', + secret_access_key: '', + region: 'us-east-1', + }, + }); + + // 当对话框打开或初始数据变化时重置表单 + useEffect(() => { + if (open) { + reset(initialData || { access_key_id: '', secret_access_key: '', region: 'us-east-1' }); + } + }, [open, initialData, reset]); + + const handleFormSubmit = (data: BedrockFormData) => { + onSubmit(data); + }; + + const toggleShowAccessKey = () => { + setShowAccessKey(!showAccessKey); + }; + + const toggleShowSecretKey = () => { + setShowSecretKey(!showSecretKey); + }; + + return ( + + + {editMode ? '编辑' : '配置'} AWS Bedrock + + + + ( + + + {showAccessKey ? : } + + + ), + }} + /> + )} + /> + + ( + + + {showSecretKey ? : } + + + ), + }} + /> + )} + /> + + ( + + Region + + {errors.region && ( + + {errors.region.message} + + )} + + )} + /> + + + + + + + + ); +}; + +export default BedrockDialog; \ No newline at end of file diff --git a/src/pages/setting/components/Dialog/OllamaDialog.tsx b/src/pages/setting/components/Dialog/OllamaDialog.tsx new file mode 100644 index 0000000..88e183b --- /dev/null +++ b/src/pages/setting/components/Dialog/OllamaDialog.tsx @@ -0,0 +1,111 @@ +import React, { useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + Typography, + CircularProgress, +} from '@mui/material'; +import { Controller, useForm } from 'react-hook-form'; + +// 表单数据接口 +export interface OllamaFormData { + base_url: string; +} + +// 对话框 Props 接口 +export interface OllamaDialogProps { + open: boolean; + onClose: () => void; + onSubmit: (data: OllamaFormData) => void; + loading: boolean; + initialData?: OllamaFormData; + editMode?: boolean; +} + +/** + * Ollama 配置对话框 + */ +function OllamaDialog ({ + open, + onClose, + onSubmit, + loading, + initialData, + editMode = false, +}: OllamaDialogProps) { + const { + control, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + defaultValues: { + base_url: 'http://localhost:11434', + }, + }); + + // 当对话框打开或初始数据变化时重置表单 + useEffect(() => { + if (open) { + reset(initialData || { base_url: 'http://localhost:11434' }); + } + }, [open, initialData, reset]); + + const handleFormSubmit = (data: OllamaFormData) => { + onSubmit(data); + }; + + return ( + + + {editMode ? '编辑' : '配置'} Ollama + + + + ( + + )} + /> + + + + + + + + ); +}; + +export default OllamaDialog; \ No newline at end of file diff --git a/src/pages/setting/components/Dialog/SystemModelDialog.tsx b/src/pages/setting/components/Dialog/SystemModelDialog.tsx new file mode 100644 index 0000000..02ab344 --- /dev/null +++ b/src/pages/setting/components/Dialog/SystemModelDialog.tsx @@ -0,0 +1,306 @@ +import React, { useEffect, useMemo } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + FormControl, + InputLabel, + Select, + MenuItem, + Box, + Typography, + CircularProgress, + ListSubheader, +} from '@mui/material'; +import { useForm, Controller } from 'react-hook-form'; +import { LlmSvgIcon } from '@/components/AppSvgIcon'; +import { IconMap, type LLMFactory } from '@/constants/llm'; +import type { ITenantInfo } from '@/interfaces/database/knowledge'; +import type { LlmModelType } from '@/constants/knowledge'; +import type { IMyLlmModel, IThirdOAIModel } from '@/interfaces/database/llm'; + +interface AllModelOptionItem { + label: string; + options: { + value: string; + label: string; + disabled: boolean; + model: IThirdOAIModel + }[]; +} + +// 导出接口供其他文件使用 +export interface SystemModelFormData extends Partial { } + +// 系统默认模型设置对话框 +export interface SystemModelDialogProps { + open: boolean; + onClose: () => void; + loading: boolean; + editMode?: boolean; + onSubmit: (data: SystemModelFormData) => Promise; + initialData?: Partial; + allModelOptions: Record; +} + + +export interface ModelOption { + value: string; + label: string; + disabled: boolean; + model: IThirdOAIModel; +} +export interface ModelGroup { + label: string; + options: ModelOption[]; +} + +/** + * 系统默认模型设置对话框 + */ +function SystemModelDialog({ + open, + onClose, + onSubmit, + loading, + initialData, + editMode = false, + allModelOptions +}: SystemModelDialogProps) { + const { control, handleSubmit, reset, formState: { errors } } = useForm({ + defaultValues: {} + }); + + // 获取工厂图标名称 + const getFactoryIconName = (factoryName: LLMFactory) => { + return IconMap[factoryName] || 'default'; + }; + + // all model options 包含了全部的 options + const llmOptions = useMemo(() => allModelOptions?.llmOptions || [], [allModelOptions]); + const embdOptions = useMemo(() => allModelOptions?.embeddingOptions || [], [allModelOptions]); + const img2txtOptions = useMemo(() => allModelOptions?.image2textOptions || [], [allModelOptions]); + const asrOptions = useMemo(() => allModelOptions?.speech2textOptions || [], [allModelOptions]); + const ttsOptions = useMemo(() => allModelOptions?.ttsOptions || [], [allModelOptions]); + const rerankOptions = useMemo(() => allModelOptions?.rerankOptions || [], [allModelOptions]); + + useEffect(() => { + if (open && initialData) { + reset(initialData); + } else if (open) { + reset({ + llm_id: '', + embd_id: '', + img2txt_id: '', + asr_id: '', + tts_id: '', + rerank_id: '', + }); + } + }, [open, initialData, reset]); + + const handleFormSubmit = async (data: ITenantInfo) => { + try { + await onSubmit(data); + onClose(); + } catch (error) { + console.error('提交失败:', error); + } + }; + + return ( + + + 设置默认模型 + + + + ( + + 聊天模型 + + {errors.llm_id && ( + + {errors.llm_id.message} + + )} + + )} + /> + + ( + + 嵌入模型 + + {errors.embd_id && ( + + {errors.embd_id.message} + + )} + + )} + /> + + ( + + Img2txt模型 + + + )} + /> + + ( + + Speech2txt模型 + + + )} + /> + + ( + + Rerank模型 + + + )} + /> + + ( + + TTS模型 + + + )} + /> + + + + + + + + ); +}; + +export default SystemModelDialog; \ No newline at end of file diff --git a/src/pages/setting/components/LLMFactoryCard.tsx b/src/pages/setting/components/LLMFactoryCard.tsx index 2ddbc17..97ed07e 100644 --- a/src/pages/setting/components/LLMFactoryCard.tsx +++ b/src/pages/setting/components/LLMFactoryCard.tsx @@ -5,7 +5,6 @@ import { LlmSvgIcon } from "@/components/AppSvgIcon"; import { IconMap, type LLMFactory } from "@/constants/llm"; import type { IFactory } from "@/interfaces/database/llm"; import { Box, Button, Card, CardContent, Chip, Typography } from "@mui/material"; -import { useState } from "react"; // 模型类型标签颜色映射 export const MODEL_TYPE_COLORS: Record = { diff --git a/src/pages/setting/components/ModelDialogs.tsx b/src/pages/setting/components/ModelDialogs.tsx index 098b562..38ece79 100644 --- a/src/pages/setting/components/ModelDialogs.tsx +++ b/src/pages/setting/components/ModelDialogs.tsx @@ -1,909 +1,77 @@ -import React, { useEffect, useMemo } from 'react'; -import { - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Button, - TextField, - FormControl, - InputLabel, - Select, - MenuItem, - Box, - Typography, - Alert, - CircularProgress, - IconButton, - InputAdornment, - ListSubheader, -} from '@mui/material'; -import { Visibility, VisibilityOff } from '@mui/icons-material'; -import { useForm, Controller } from 'react-hook-form'; -import { LlmSvgIcon } from '@/components/AppSvgIcon'; -import { IconMap, type LLMFactory } from '@/constants/llm'; -import type { ITenantInfo } from '@/interfaces/database/knowledge'; -import type { LlmModelType } from '@/constants/knowledge'; -import type { IMyLlmModel, IThirdOAIModel } from '@/interfaces/database/llm'; -import logger from '@/utils/logger'; +import React from 'react'; -// 基础对话框状态 -interface BaseDialogState { - open: boolean; - loading: boolean; - editMode: boolean; - closeDialog: () => void; -} +// 导入独立的对话框组件 +import ApiKeyDialog, { type ApiKeyFormData, type ApiKeyDialogProps } from './Dialog/ApiKeyDialog'; +import AzureOpenAIDialog, { type AzureOpenAIFormData, type AzureOpenAIDialogProps } from './Dialog/AzureOpenAIDialog'; +import BedrockDialog, { type BedrockFormData, type BedrockDialogProps, BEDROCK_REGIONS } from './Dialog/BedrockDialog'; +import OllamaDialog, { type OllamaFormData, type OllamaDialogProps } from './Dialog/OllamaDialog'; +import SystemModelDialog, { type SystemModelFormData, type SystemModelDialogProps, type ModelOption, type ModelGroup } from './Dialog/SystemModelDialog'; -// ModelDialogs 整合组件的 Props 接口 -interface ModelDialogsProps { - apiKeyDialog: BaseDialogState & { - initialData: any; - factoryName: string; - submitApiKey: (data: ApiKeyFormData) => Promise; - }; - azureDialog: BaseDialogState & { - initialData: any; - submitAzureOpenAI: (data: AzureOpenAIFormData) => Promise; - }; - bedrockDialog: BaseDialogState & { - initialData: any; - submitBedrock: (data: BedrockFormData) => Promise; - }; - ollamaDialog: BaseDialogState & { - initialData: any; - submitOllama: (data: OllamaFormData) => Promise; - }; - systemDialog: BaseDialogState & { - allModelOptions: any; - initialData: Partial; - submitSystemModelSetting: (data: Partial) => Promise; - }; -} -// 接口定义 -export interface ApiKeyFormData { - api_key: string; - base_url?: string; - group_id?: string; -} -export interface AzureOpenAIFormData { - api_key: string; - azure_endpoint: string; - api_version: string; - deployment_name: string; -} - -export interface BedrockFormData { - access_key_id: string; - secret_access_key: string; - region: string; -} - -export interface OllamaFormData { - base_url: string; - model_name: string; -} - -// Bedrock 区域列表 -const BEDROCK_REGIONS = [ - 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', - 'eu-west-1', 'eu-west-2', 'eu-central-1', 'eu-north-1', - 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-northeast-2', - 'ap-south-1', 'ca-central-1', 'sa-east-1' -]; - -// 基础对话框 Props -interface BaseDialogProps { +// 基础对话框 Props 接口 +export interface BaseDialogProps { open: boolean; onClose: () => void; loading: boolean; editMode?: boolean; } -// 通用 API Key 对话框 -interface ApiKeyDialogProps extends BaseDialogProps { - onSubmit: (data: ApiKeyFormData) => Promise; - factoryName: string; - initialData?: Partial; -} +// 导出所有表单数据接口 +export type { ApiKeyFormData, AzureOpenAIFormData, BedrockFormData, OllamaFormData, SystemModelFormData }; -// Azure OpenAI 对话框 -interface AzureOpenAIDialogProps extends BaseDialogProps { - onSubmit: (data: AzureOpenAIFormData) => Promise; - initialData?: Partial; -} +// 导出所有对话框 Props 接口 +export type { ApiKeyDialogProps, AzureOpenAIDialogProps, BedrockDialogProps, OllamaDialogProps, SystemModelDialogProps }; -// AWS Bedrock 对话框 -interface BedrockDialogProps extends BaseDialogProps { - onSubmit: (data: BedrockFormData) => Promise; - initialData?: Partial; -} +// 导出其他相关接口和常量 +export type { ModelOption, ModelGroup }; +export { BEDROCK_REGIONS }; -// Ollama 对话框 -interface OllamaDialogProps extends BaseDialogProps { - onSubmit: (data: OllamaFormData) => Promise; - initialData?: Partial; -} - -interface AllModelOptionItem { - label: string; - options: { - value: string; - label: string; - disabled: boolean; - model: IThirdOAIModel - }[]; -} - -// 系统默认模型设置对话框 -interface SystemModelDialogProps extends BaseDialogProps { - onSubmit: (data: Partial) => Promise; - initialData?: Partial; - allModelOptions: Record; -} - -/** - * 通用 API Key 对话框 - */ -export const ApiKeyDialog: React.FC = ({ - open, - onClose, - onSubmit, - loading, - factoryName, - initialData, - editMode = false -}) => { - const { control, handleSubmit, reset, formState: { errors } } = useForm({ - defaultValues: { - api_key: '', - base_url: '', - group_id: '' - } - }); - const [showApiKey, setShowApiKey] = React.useState(false); - - logger.info('ApiKeyDialog 初始化:', { open, editMode, factoryName, initialData }); - - useEffect(() => { - if (open && initialData) { - reset(initialData); - } else if (open) { - reset({ api_key: '', base_url: '', group_id: '' }); - } - }, [open, initialData, reset]); - - const handleFormSubmit = async (data: ApiKeyFormData) => { - try { - await onSubmit(data); - onClose(); - } catch (error) { - console.error('提交失败:', error); - } +// 模型对话框整合组件的 Props 接口 +export interface ModelDialogsProps { + apiKeyDialog: { + open: boolean; + closeDialog: () => void; + submitApiKey: (data: ApiKeyFormData) => void; + loading: boolean; + factoryName: string; + initialData?: ApiKeyFormData; + editMode?: boolean; }; - - const needsBaseUrl = ['OpenAI', 'AzureOpenAI'].includes(factoryName); - const needsGroupId = factoryName.toLowerCase() === 'minimax'; - - return ( - - - {editMode ? `编辑 ${factoryName}` : `配置 ${factoryName}`} - - - - ( - - setShowApiKey(!showApiKey)} - edge="end" - > - {showApiKey ? : } - - - ), - }} - /> - )} - /> - - {needsBaseUrl && ( - ( - - )} - /> - )} - - {needsGroupId && ( - ( - - )} - /> - )} - - - - - - - - ); -}; - - -/** - * Azure OpenAI 对话框 - */ -export const AzureOpenAIDialog: React.FC = ({ - open, - onClose, - onSubmit, - loading, - initialData, - editMode = false -}) => { - const { control, handleSubmit, reset, formState: { errors } } = useForm({ - defaultValues: { - api_key: '', - azure_endpoint: '', - api_version: '2023-12-01-preview', - deployment_name: '' - } - }); - const [showApiKey, setShowApiKey] = React.useState(false); - - useEffect(() => { - if (open && initialData) { - reset(initialData); - } else if (open) { - reset({ - api_key: '', - azure_endpoint: '', - api_version: '2023-12-01-preview', - deployment_name: '' - }); - } - }, [open, initialData, reset]); - - const handleFormSubmit = async (data: AzureOpenAIFormData) => { - try { - await onSubmit(data); - onClose(); - } catch (error) { - console.error('提交失败:', error); - } + azureDialog: { + open: boolean; + closeDialog: () => void; + submitAzureOpenAI: (data: AzureOpenAIFormData) => void; + loading: boolean; + initialData?: AzureOpenAIFormData; + editMode?: boolean; }; - - return ( - - - {editMode ? '编辑 Azure OpenAI' : '配置 Azure OpenAI'} - - - - ( - - setShowApiKey(!showApiKey)} - edge="end" - > - {showApiKey ? : } - - - ), - }} - /> - )} - /> - - ( - - )} - /> - - ( - - )} - /> - - ( - - )} - /> - - - - - - - - ); -}; - - -/** - * AWS Bedrock 对话框 - */ -export const BedrockDialog: React.FC = ({ - open, - onClose, - onSubmit, - loading, - initialData, - editMode = false -}) => { - const { control, handleSubmit, reset, formState: { errors } } = useForm({ - defaultValues: { - access_key_id: '', - secret_access_key: '', - region: 'us-east-1' - } - }); - const [showSecretKey, setShowSecretKey] = React.useState(false); - - useEffect(() => { - if (open && initialData) { - reset(initialData); - } else if (open) { - reset({ - access_key_id: '', - secret_access_key: '', - region: 'us-east-1' - }); - } - }, [open, initialData, reset]); - - const handleFormSubmit = async (data: BedrockFormData) => { - try { - await onSubmit(data); - onClose(); - } catch (error) { - console.error('提交失败:', error); - } + bedrockDialog: { + open: boolean; + closeDialog: () => void; + submitBedrock: (data: BedrockFormData) => void; + loading: boolean; + initialData?: BedrockFormData; + editMode?: boolean; }; - - return ( - - - {editMode ? '编辑 AWS Bedrock' : '配置 AWS Bedrock'} - - - - ( - - )} - /> - - ( - - setShowSecretKey(!showSecretKey)} - edge="end" - > - {showSecretKey ? : } - - - ), - }} - /> - )} - /> - - ( - - Region - - {errors.region && ( - - {errors.region.message} - - )} - - )} - /> - - - - - - - - ); -}; - - -/** - * Ollama 对话框 - */ -export const OllamaDialog: React.FC = ({ - open, - onClose, - onSubmit, - loading, - initialData, - editMode = false -}) => { - const { control, handleSubmit, reset, formState: { errors } } = useForm({ - defaultValues: { - base_url: 'http://localhost:11434', - model_name: '' - } - }); - - useEffect(() => { - if (open && initialData) { - reset(initialData); - } else if (open) { - reset({ - base_url: 'http://localhost:11434', - model_name: '' - }); - } - }, [open, initialData, reset]); - - const handleFormSubmit = async (data: OllamaFormData) => { - try { - await onSubmit(data); - onClose(); - } catch (error) { - console.error('提交失败:', error); - } + ollamaDialog: { + open: boolean; + closeDialog: () => void; + submitOllama: (data: OllamaFormData) => void; + loading: boolean; + initialData?: OllamaFormData; + editMode?: boolean; }; - - return ( - - - {editMode ? '编辑 Ollama' : '配置 Ollama'} - - - - 请确保 Ollama 服务正在运行,并且可以通过指定的 URL 访问。 - - - ( - - )} - /> - - ( - - )} - /> - - - - - - - - ); -}; - - - -/** - * 系统默认模型设置对话框 - */ -export const SystemModelDialog: React.FC = ({ - open, - onClose, - onSubmit, - loading, - initialData, - editMode = false, - allModelOptions -}) => { - const { control, handleSubmit, reset, formState: { errors } } = useForm({ - defaultValues: {} - }); - - // 获取工厂图标名称 - const getFactoryIconName = (factoryName: LLMFactory) => { - return IconMap[factoryName] || 'default'; + systemDialog: { + open: boolean; + closeDialog: () => void; + submitSystemModelSetting: (data: SystemModelFormData) => Promise; + loading: boolean; + initialData?: SystemModelFormData; + editMode?: boolean; + allModelOptions: any; }; - - // all model options 包含了全部的 options - const llmOptions = useMemo(() => allModelOptions?.llmOptions || [], [allModelOptions]); - const embdOptions = useMemo(() => allModelOptions?.embeddingOptions || [], [allModelOptions]); - const img2txtOptions = useMemo(() => allModelOptions?.image2textOptions || [], [allModelOptions]); - const asrOptions = useMemo(() => allModelOptions?.speech2textOptions || [], [allModelOptions]); - const ttsOptions = useMemo(() => allModelOptions?.ttsOptions || [], [allModelOptions]); - const rerankOptions = useMemo(() => allModelOptions?.rerankOptions || [], [allModelOptions]); - - useEffect(() => { - if (open && initialData) { - reset(initialData); - } else if (open) { - reset({ - llm_id: '', - embd_id: '', - img2txt_id: '', - asr_id: '', - tts_id: '', - rerank_id: '', - }); - } - }, [open, initialData, reset]); - - const handleFormSubmit = async (data: ITenantInfo) => { - try { - await onSubmit(data); - onClose(); - } catch (error) { - console.error('提交失败:', error); - } - }; - - return ( - - - 设置默认模型 - - - - ( - - 聊天模型 - - {errors.llm_id && ( - - {errors.llm_id.message} - - )} - - )} - /> - - ( - - 嵌入模型 - - {errors.embd_id && ( - - {errors.embd_id.message} - - )} - - )} - /> - - ( - - Img2txt模型 - - - )} - /> - - ( - - Speech2txt模型 - - - )} - /> - - ( - - Rerank模型 - - - )} - /> - - ( - - TTS模型 - - - )} - /> - - - - - - - - ); -}; - +} /** * 模型对话框整合组件 diff --git a/src/pages/setting/hooks/useModelDialogs.ts b/src/pages/setting/hooks/useModelDialogs.ts index ef0b713..43f81dd 100644 --- a/src/pages/setting/hooks/useModelDialogs.ts +++ b/src/pages/setting/hooks/useModelDialogs.ts @@ -7,6 +7,7 @@ import type { AzureOpenAIFormData, BedrockFormData, OllamaFormData, + // SystemModelFormData, } from '../components/ModelDialogs'; import type { ITenantInfo } from '@/interfaces/database/knowledge'; import { useLlmList } from '@/hooks/llm-hooks'; @@ -108,7 +109,7 @@ export const useAzureOpenAIDialog = () => { // 调用 Azure OpenAI 特定的 API await userService.set_api_key({ llm_factory: 'AzureOpenAI', - llm_name: data.deployment_name, + // llm_name: data.deployment_name, api_key: data.api_key, // azure_endpoint: data.azure_endpoint, // api_version: data.api_version, @@ -175,7 +176,7 @@ export const useOllamaDialog = () => { // 调用添加 LLM 的 API await userService.add_llm({ llm_factory: 'Ollama', - llm_name: data.model_name, + // llm_name: data.model_name, // base_url: data.base_url, }); showMessage.success('Ollama 模型添加成功'); diff --git a/src/pages/setting/models.tsx b/src/pages/setting/models.tsx index 9dbba7f..b89a174 100644 --- a/src/pages/setting/models.tsx +++ b/src/pages/setting/models.tsx @@ -29,6 +29,8 @@ import type { IFactory, IMyLlmModel, ILlmItem } from '@/interfaces/database/llm' import LLMFactoryCard, { MODEL_TYPE_COLORS } from './components/LLMFactoryCard'; import { ModelDialogs } from './components/ModelDialogs'; import { useDialog } from '@/hooks/useDialog'; +import logger from '@/utils/logger'; +import { LLM_FACTORY_LIST, LocalLlmFactories, type LLMFactory } from '@/constants/llm'; function MyLlmGridItem({ model, onDelete }: { model: ILlmItem, onDelete: (model: ILlmItem) => void }) { return ( @@ -84,11 +86,72 @@ function ModelsPage() { return filterFactory || []; }, [llmFactory, myLlm]); + // 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 handleConfigureFactory = useCallback((factory: IFactory) => { - modelDialogs.apiKeyDialog.openApiKeyDialog(factory.name); + if (factory == null) { + return; + } + // llm 的配置很多,有很多种类型 首先是local llm 然后是配置项不一样的 + // 然后有很多自定义的配置项,需要单独用 dialog 来配置 + const factoryName = factory.name as LLMFactory; + if (LocalLlmFactories.includes(factoryName)) { + // modelDialogs.localLlmDialog.openLocalLlmDialog(factoryName); + } else if (factoryName == LLM_FACTORY_LIST.AzureOpenAI) { + + } else if (factoryName == LLM_FACTORY_LIST.Bedrock) { + + } else if (factoryName == LLM_FACTORY_LIST.BaiduYiYan) { + + } else if (factoryName == LLM_FACTORY_LIST.GoogleCloud) { + + } else if (factoryName == LLM_FACTORY_LIST.FishAudio) { + + } else if (factoryName == LLM_FACTORY_LIST.TencentCloud) { + + } else if (factoryName == LLM_FACTORY_LIST.TencentHunYuan) { + + } else if (factoryName == LLM_FACTORY_LIST.XunFeiSpark) { + + } else if (factoryName == LLM_FACTORY_LIST.VolcEngine) { + + } else { + modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName); + } + logger.debug('handleConfigureFactory', factory); }, [modelDialogs]); + const handleEditLlmFactory = useCallback((factoryName: string) => { + if (factoryName == null) { + return; + } + modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName); + }, []); + const dialog = useDialog(); // 处理删除单个模型 @@ -163,10 +226,10 @@ function ModelsPage() { - e.stopPropagation()}>