refactor(setting): split model dialogs into separate components
This commit is contained in:
169
src/pages/setting/components/Dialog/ApiKeyDialog.tsx
Normal file
169
src/pages/setting/components/Dialog/ApiKeyDialog.tsx
Normal file
@@ -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<ApiKeyFormData>({
|
||||
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 (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{editMode ? '编辑' : '配置'} {factoryName} API Key
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" sx={{ mt: 2 }}>
|
||||
<Controller
|
||||
name="api_key"
|
||||
control={control}
|
||||
rules={{ required: 'API Key 是必填项' }}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="API Key"
|
||||
type={showApiKey ? 'text' : 'password'}
|
||||
margin="normal"
|
||||
error={!!errors.api_key}
|
||||
helperText={errors.api_key?.message}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle api key visibility"
|
||||
onClick={toggleShowApiKey}
|
||||
edge="end"
|
||||
>
|
||||
{showApiKey ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{needsBaseUrl && (
|
||||
<Controller
|
||||
name="base_url"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="Base URL"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
margin="normal"
|
||||
helperText="可选,自定义 API 端点"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{needsGroupId && (
|
||||
<Controller
|
||||
name="group_id"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="Group ID"
|
||||
margin="normal"
|
||||
helperText="Minimax 专用的 Group ID"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} disabled={loading}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit(handleFormSubmit)}
|
||||
variant="contained"
|
||||
disabled={loading}
|
||||
startIcon={loading ? <CircularProgress size={20} /> : null}
|
||||
>
|
||||
{editMode ? '更新' : '保存'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiKeyDialog;
|
||||
171
src/pages/setting/components/Dialog/AzureOpenAIDialog.tsx
Normal file
171
src/pages/setting/components/Dialog/AzureOpenAIDialog.tsx
Normal file
@@ -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<AzureOpenAIFormData>({
|
||||
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 (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{editMode ? '编辑' : '配置'} Azure OpenAI
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" sx={{ mt: 2 }}>
|
||||
<Controller
|
||||
name="api_key"
|
||||
control={control}
|
||||
rules={{ required: 'API Key 是必填项' }}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="API Key"
|
||||
type={showApiKey ? 'text' : 'password'}
|
||||
margin="normal"
|
||||
error={!!errors.api_key}
|
||||
helperText={errors.api_key?.message}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle api key visibility"
|
||||
onClick={toggleShowApiKey}
|
||||
edge="end"
|
||||
>
|
||||
{showApiKey ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="endpoint"
|
||||
control={control}
|
||||
rules={{
|
||||
required: 'Endpoint 是必填项',
|
||||
pattern: {
|
||||
value: /^https?:\/\/.+/,
|
||||
message: 'Endpoint 必须是有效的 URL'
|
||||
}
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="Endpoint"
|
||||
margin="normal"
|
||||
error={!!errors.endpoint}
|
||||
helperText={errors.endpoint?.message || 'Azure OpenAI 服务的端点 URL'}
|
||||
placeholder="https://your-resource.openai.azure.com/"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="api_version"
|
||||
control={control}
|
||||
rules={{ required: 'API Version 是必填项' }}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="API Version"
|
||||
margin="normal"
|
||||
error={!!errors.api_version}
|
||||
helperText={errors.api_version?.message || 'Azure OpenAI API 版本'}
|
||||
placeholder="2024-02-01"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} disabled={loading}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit(handleFormSubmit)}
|
||||
variant="contained"
|
||||
disabled={loading}
|
||||
startIcon={loading ? <CircularProgress size={20} /> : null}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default AzureOpenAIDialog;
|
||||
203
src/pages/setting/components/Dialog/BedrockDialog.tsx
Normal file
203
src/pages/setting/components/Dialog/BedrockDialog.tsx
Normal file
@@ -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<BedrockFormData>({
|
||||
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 (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{editMode ? '编辑' : '配置'} AWS Bedrock
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" sx={{ mt: 2 }}>
|
||||
<Controller
|
||||
name="access_key_id"
|
||||
control={control}
|
||||
rules={{ required: 'Access Key ID 是必填项' }}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="Access Key ID"
|
||||
type={showAccessKey ? 'text' : 'password'}
|
||||
margin="normal"
|
||||
error={!!errors.access_key_id}
|
||||
helperText={errors.access_key_id?.message}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle access key visibility"
|
||||
onClick={toggleShowAccessKey}
|
||||
edge="end"
|
||||
>
|
||||
{showAccessKey ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="secret_access_key"
|
||||
control={control}
|
||||
rules={{ required: 'Secret Access Key 是必填项' }}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="Secret Access Key"
|
||||
type={showSecretKey ? 'text' : 'password'}
|
||||
margin="normal"
|
||||
error={!!errors.secret_access_key}
|
||||
helperText={errors.secret_access_key?.message}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle secret key visibility"
|
||||
onClick={toggleShowSecretKey}
|
||||
edge="end"
|
||||
>
|
||||
{showSecretKey ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="region"
|
||||
control={control}
|
||||
rules={{ required: 'Region 是必填项' }}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth margin="normal" error={!!errors.region}>
|
||||
<InputLabel>Region</InputLabel>
|
||||
<Select {...field} label="Region">
|
||||
{BEDROCK_REGIONS.map((region) => (
|
||||
<MenuItem key={region.value} value={region.value}>
|
||||
{region.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.region && (
|
||||
<Typography variant="caption" color="error" sx={{ mt: 1, ml: 2 }}>
|
||||
{errors.region.message}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} disabled={loading}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit(handleFormSubmit)}
|
||||
variant="contained"
|
||||
disabled={loading}
|
||||
startIcon={loading ? <CircularProgress size={20} /> : null}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BedrockDialog;
|
||||
111
src/pages/setting/components/Dialog/OllamaDialog.tsx
Normal file
111
src/pages/setting/components/Dialog/OllamaDialog.tsx
Normal file
@@ -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<OllamaFormData>({
|
||||
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 (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{editMode ? '编辑' : '配置'} Ollama
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" sx={{ mt: 2 }}>
|
||||
<Controller
|
||||
name="base_url"
|
||||
control={control}
|
||||
rules={{
|
||||
required: 'Base URL 是必填项',
|
||||
pattern: {
|
||||
value: /^https?:\/\/.+/,
|
||||
message: 'Base URL 必须是有效的 URL'
|
||||
}
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
label="Base URL"
|
||||
margin="normal"
|
||||
error={!!errors.base_url}
|
||||
helperText={errors.base_url?.message || 'Ollama 服务的基础 URL'}
|
||||
placeholder="http://localhost:11434"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} disabled={loading}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit(handleFormSubmit)}
|
||||
variant="contained"
|
||||
disabled={loading}
|
||||
startIcon={loading ? <CircularProgress size={20} /> : null}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default OllamaDialog;
|
||||
306
src/pages/setting/components/Dialog/SystemModelDialog.tsx
Normal file
306
src/pages/setting/components/Dialog/SystemModelDialog.tsx
Normal file
@@ -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<ITenantInfo> { }
|
||||
|
||||
// 系统默认模型设置对话框
|
||||
export interface SystemModelDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
loading: boolean;
|
||||
editMode?: boolean;
|
||||
onSubmit: (data: SystemModelFormData) => Promise<void>;
|
||||
initialData?: Partial<ITenantInfo>;
|
||||
allModelOptions: Record<string, AllModelOptionItem[]>;
|
||||
}
|
||||
|
||||
|
||||
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<ITenantInfo>({
|
||||
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 (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
设置默认模型
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" sx={{ mt: 2 }}>
|
||||
<Controller
|
||||
name="llm_id"
|
||||
control={control}
|
||||
rules={{ required: '聊天模型是必填项' }}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth margin="normal" error={!!errors.llm_id}>
|
||||
<InputLabel>聊天模型</InputLabel>
|
||||
<Select {...field} label="聊天模型">
|
||||
{llmOptions.map((group) => [
|
||||
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
|
||||
...group.options.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<LlmSvgIcon
|
||||
name={getFactoryIconName(group.label as LLMFactory)}
|
||||
sx={{ width: 20, height: 20, color: 'primary.main' }}
|
||||
/>
|
||||
{option.label}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))
|
||||
])}
|
||||
</Select>
|
||||
{errors.llm_id && (
|
||||
<Typography variant="caption" color="error" sx={{ mt: 1, ml: 2 }}>
|
||||
{errors.llm_id.message}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="embd_id"
|
||||
control={control}
|
||||
rules={{ required: '嵌入模型是必填项' }}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth margin="normal" error={!!errors.embd_id}>
|
||||
<InputLabel>嵌入模型</InputLabel>
|
||||
<Select {...field} label="嵌入模型">
|
||||
{embdOptions.map((group) => [
|
||||
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
|
||||
...group.options.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<LlmSvgIcon
|
||||
name={getFactoryIconName(group.label as LLMFactory)}
|
||||
sx={{ width: 20, height: 20, color: 'primary.main' }}
|
||||
/>
|
||||
{option.label}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))
|
||||
])}
|
||||
</Select>
|
||||
{errors.embd_id && (
|
||||
<Typography variant="caption" color="error" sx={{ mt: 1, ml: 2 }}>
|
||||
{errors.embd_id.message}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="img2txt_id"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth margin="normal">
|
||||
<InputLabel>Img2txt模型</InputLabel>
|
||||
<Select {...field} label="Img2txt模型">
|
||||
{img2txtOptions.map((group) => [
|
||||
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
|
||||
...group.options.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<LlmSvgIcon
|
||||
name={getFactoryIconName(group.label as LLMFactory)}
|
||||
sx={{ width: 20, height: 20, color: 'primary.main' }}
|
||||
/>
|
||||
{option.label}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))
|
||||
])}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="asr_id"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth margin="normal">
|
||||
<InputLabel>Speech2txt模型</InputLabel>
|
||||
<Select {...field} label="Speech2txt模型">
|
||||
{asrOptions.map((group) => [
|
||||
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
|
||||
...group.options.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<LlmSvgIcon
|
||||
name={getFactoryIconName(group.label as LLMFactory)}
|
||||
sx={{ width: 20, height: 20, color: 'primary.main' }}
|
||||
/>
|
||||
{option.label}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))
|
||||
])}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="rerank_id"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth margin="normal">
|
||||
<InputLabel>Rerank模型</InputLabel>
|
||||
<Select {...field} label="Rerank模型">
|
||||
{rerankOptions.map((group) => [
|
||||
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
|
||||
...group.options.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<LlmSvgIcon
|
||||
name={getFactoryIconName(group.label as LLMFactory)}
|
||||
sx={{ width: 20, height: 20, color: 'primary.main' }}
|
||||
/>
|
||||
{option.label}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))
|
||||
])}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="tts_id"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth margin="normal">
|
||||
<InputLabel>TTS模型</InputLabel>
|
||||
<Select {...field} label="TTS模型">
|
||||
{ttsOptions.map((group) => [
|
||||
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
|
||||
...group.options.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<LlmSvgIcon
|
||||
name={getFactoryIconName(group.label as LLMFactory)}
|
||||
sx={{ width: 20, height: 20, color: 'primary.main' }}
|
||||
/>
|
||||
{option.label}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))
|
||||
])}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} disabled={loading}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit(handleFormSubmit)}
|
||||
variant="contained"
|
||||
disabled={loading}
|
||||
startIcon={loading ? <CircularProgress size={20} /> : null}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemModelDialog;
|
||||
Reference in New Issue
Block a user