refactor(setting): split model dialogs into separate components
This commit is contained in:
@@ -124,3 +124,18 @@ export const IconMap = {
|
|||||||
[LLM_FACTORY_LIST.CometAPI]: 'cometapi',
|
[LLM_FACTORY_LIST.CometAPI]: 'cometapi',
|
||||||
[LLM_FACTORY_LIST.DeerAPI]: 'deerapi',
|
[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,
|
||||||
|
]
|
||||||
|
|||||||
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;
|
||||||
@@ -5,7 +5,6 @@ import { LlmSvgIcon } from "@/components/AppSvgIcon";
|
|||||||
import { IconMap, type LLMFactory } from "@/constants/llm";
|
import { IconMap, type LLMFactory } from "@/constants/llm";
|
||||||
import type { IFactory } from "@/interfaces/database/llm";
|
import type { IFactory } from "@/interfaces/database/llm";
|
||||||
import { Box, Button, Card, CardContent, Chip, Typography } from "@mui/material";
|
import { Box, Button, Card, CardContent, Chip, Typography } from "@mui/material";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
// 模型类型标签颜色映射
|
// 模型类型标签颜色映射
|
||||||
export const MODEL_TYPE_COLORS: Record<string, string> = {
|
export const MODEL_TYPE_COLORS: Record<string, string> = {
|
||||||
|
|||||||
@@ -1,909 +1,77 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React 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';
|
|
||||||
|
|
||||||
// 基础对话框状态
|
// 导入独立的对话框组件
|
||||||
interface BaseDialogState {
|
import ApiKeyDialog, { type ApiKeyFormData, type ApiKeyDialogProps } from './Dialog/ApiKeyDialog';
|
||||||
open: boolean;
|
import AzureOpenAIDialog, { type AzureOpenAIFormData, type AzureOpenAIDialogProps } from './Dialog/AzureOpenAIDialog';
|
||||||
loading: boolean;
|
import BedrockDialog, { type BedrockFormData, type BedrockDialogProps, BEDROCK_REGIONS } from './Dialog/BedrockDialog';
|
||||||
editMode: boolean;
|
import OllamaDialog, { type OllamaFormData, type OllamaDialogProps } from './Dialog/OllamaDialog';
|
||||||
closeDialog: () => void;
|
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<void>;
|
|
||||||
};
|
|
||||||
azureDialog: BaseDialogState & {
|
|
||||||
initialData: any;
|
|
||||||
submitAzureOpenAI: (data: AzureOpenAIFormData) => Promise<void>;
|
|
||||||
};
|
|
||||||
bedrockDialog: BaseDialogState & {
|
|
||||||
initialData: any;
|
|
||||||
submitBedrock: (data: BedrockFormData) => Promise<void>;
|
|
||||||
};
|
|
||||||
ollamaDialog: BaseDialogState & {
|
|
||||||
initialData: any;
|
|
||||||
submitOllama: (data: OllamaFormData) => Promise<void>;
|
|
||||||
};
|
|
||||||
systemDialog: BaseDialogState & {
|
|
||||||
allModelOptions: any;
|
|
||||||
initialData: Partial<ITenantInfo>;
|
|
||||||
submitSystemModelSetting: (data: Partial<ITenantInfo>) => Promise<void>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接口定义
|
|
||||||
export interface ApiKeyFormData {
|
|
||||||
api_key: string;
|
|
||||||
base_url?: string;
|
|
||||||
group_id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AzureOpenAIFormData {
|
// 基础对话框 Props 接口
|
||||||
api_key: string;
|
export interface BaseDialogProps {
|
||||||
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 {
|
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
editMode?: boolean;
|
editMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通用 API Key 对话框
|
// 导出所有表单数据接口
|
||||||
interface ApiKeyDialogProps extends BaseDialogProps {
|
export type { ApiKeyFormData, AzureOpenAIFormData, BedrockFormData, OllamaFormData, SystemModelFormData };
|
||||||
onSubmit: (data: ApiKeyFormData) => Promise<void>;
|
|
||||||
|
// 导出所有对话框 Props 接口
|
||||||
|
export type { ApiKeyDialogProps, AzureOpenAIDialogProps, BedrockDialogProps, OllamaDialogProps, SystemModelDialogProps };
|
||||||
|
|
||||||
|
// 导出其他相关接口和常量
|
||||||
|
export type { ModelOption, ModelGroup };
|
||||||
|
export { BEDROCK_REGIONS };
|
||||||
|
|
||||||
|
// 模型对话框整合组件的 Props 接口
|
||||||
|
export interface ModelDialogsProps {
|
||||||
|
apiKeyDialog: {
|
||||||
|
open: boolean;
|
||||||
|
closeDialog: () => void;
|
||||||
|
submitApiKey: (data: ApiKeyFormData) => void;
|
||||||
|
loading: boolean;
|
||||||
factoryName: string;
|
factoryName: string;
|
||||||
initialData?: Partial<ApiKeyFormData>;
|
initialData?: ApiKeyFormData;
|
||||||
}
|
editMode?: boolean;
|
||||||
|
|
||||||
// Azure OpenAI 对话框
|
|
||||||
interface AzureOpenAIDialogProps extends BaseDialogProps {
|
|
||||||
onSubmit: (data: AzureOpenAIFormData) => Promise<void>;
|
|
||||||
initialData?: Partial<AzureOpenAIFormData>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AWS Bedrock 对话框
|
|
||||||
interface BedrockDialogProps extends BaseDialogProps {
|
|
||||||
onSubmit: (data: BedrockFormData) => Promise<void>;
|
|
||||||
initialData?: Partial<BedrockFormData>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ollama 对话框
|
|
||||||
interface OllamaDialogProps extends BaseDialogProps {
|
|
||||||
onSubmit: (data: OllamaFormData) => Promise<void>;
|
|
||||||
initialData?: Partial<OllamaFormData>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AllModelOptionItem {
|
|
||||||
label: string;
|
|
||||||
options: {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
disabled: boolean;
|
|
||||||
model: IThirdOAIModel
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 系统默认模型设置对话框
|
|
||||||
interface SystemModelDialogProps extends BaseDialogProps {
|
|
||||||
onSubmit: (data: Partial<ITenantInfo>) => Promise<void>;
|
|
||||||
initialData?: Partial<ITenantInfo>;
|
|
||||||
allModelOptions: Record<string, AllModelOptionItem[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用 API Key 对话框
|
|
||||||
*/
|
|
||||||
export const ApiKeyDialog: React.FC<ApiKeyDialogProps> = ({
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
onSubmit,
|
|
||||||
loading,
|
|
||||||
factoryName,
|
|
||||||
initialData,
|
|
||||||
editMode = false
|
|
||||||
}) => {
|
|
||||||
const { control, handleSubmit, reset, formState: { errors } } = useForm<ApiKeyFormData>({
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
azureDialog: {
|
||||||
const needsBaseUrl = ['OpenAI', 'AzureOpenAI'].includes(factoryName);
|
open: boolean;
|
||||||
const needsGroupId = factoryName.toLowerCase() === 'minimax';
|
closeDialog: () => void;
|
||||||
|
submitAzureOpenAI: (data: AzureOpenAIFormData) => void;
|
||||||
return (
|
loading: boolean;
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
initialData?: AzureOpenAIFormData;
|
||||||
<DialogTitle>
|
editMode?: boolean;
|
||||||
{editMode ? `编辑 ${factoryName}` : `配置 ${factoryName}`}
|
|
||||||
</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'}
|
|
||||||
error={!!errors.api_key}
|
|
||||||
helperText={errors.api_key?.message}
|
|
||||||
margin="normal"
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setShowApiKey(!showApiKey)}
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
bedrockDialog: {
|
||||||
|
open: boolean;
|
||||||
/**
|
closeDialog: () => void;
|
||||||
* Azure OpenAI 对话框
|
submitBedrock: (data: BedrockFormData) => void;
|
||||||
*/
|
loading: boolean;
|
||||||
export const AzureOpenAIDialog: React.FC<AzureOpenAIDialogProps> = ({
|
initialData?: BedrockFormData;
|
||||||
open,
|
editMode?: boolean;
|
||||||
onClose,
|
|
||||||
onSubmit,
|
|
||||||
loading,
|
|
||||||
initialData,
|
|
||||||
editMode = false
|
|
||||||
}) => {
|
|
||||||
const { control, handleSubmit, reset, formState: { errors } } = useForm<AzureOpenAIFormData>({
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
ollamaDialog: {
|
||||||
return (
|
open: boolean;
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
closeDialog: () => void;
|
||||||
<DialogTitle>
|
submitOllama: (data: OllamaFormData) => void;
|
||||||
{editMode ? '编辑 Azure OpenAI' : '配置 Azure OpenAI'}
|
loading: boolean;
|
||||||
</DialogTitle>
|
initialData?: OllamaFormData;
|
||||||
<DialogContent>
|
editMode?: boolean;
|
||||||
<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'}
|
|
||||||
error={!!errors.api_key}
|
|
||||||
helperText={errors.api_key?.message}
|
|
||||||
margin="normal"
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setShowApiKey(!showApiKey)}
|
|
||||||
edge="end"
|
|
||||||
>
|
|
||||||
{showApiKey ? <VisibilityOff /> : <Visibility />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="azure_endpoint"
|
|
||||||
control={control}
|
|
||||||
rules={{ required: 'Azure Endpoint 是必填项' }}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="Azure Endpoint"
|
|
||||||
placeholder="https://your-resource.openai.azure.com"
|
|
||||||
error={!!errors.azure_endpoint}
|
|
||||||
helperText={errors.azure_endpoint?.message}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="api_version"
|
|
||||||
control={control}
|
|
||||||
rules={{ required: 'API Version 是必填项' }}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="API Version"
|
|
||||||
error={!!errors.api_version}
|
|
||||||
helperText={errors.api_version?.message}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="deployment_name"
|
|
||||||
control={control}
|
|
||||||
rules={{ required: 'Deployment Name 是必填项' }}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="Deployment Name"
|
|
||||||
error={!!errors.deployment_name}
|
|
||||||
helperText={errors.deployment_name?.message}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
systemDialog: {
|
||||||
|
open: boolean;
|
||||||
/**
|
closeDialog: () => void;
|
||||||
* AWS Bedrock 对话框
|
submitSystemModelSetting: (data: SystemModelFormData) => Promise<void>;
|
||||||
*/
|
loading: boolean;
|
||||||
export const BedrockDialog: React.FC<BedrockDialogProps> = ({
|
initialData?: SystemModelFormData;
|
||||||
open,
|
editMode?: boolean;
|
||||||
onClose,
|
allModelOptions: any;
|
||||||
onSubmit,
|
|
||||||
loading,
|
|
||||||
initialData,
|
|
||||||
editMode = false
|
|
||||||
}) => {
|
|
||||||
const { control, handleSubmit, reset, formState: { errors } } = useForm<BedrockFormData>({
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
|
||||||
<DialogTitle>
|
|
||||||
{editMode ? '编辑 AWS Bedrock' : '配置 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"
|
|
||||||
error={!!errors.access_key_id}
|
|
||||||
helperText={errors.access_key_id?.message}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<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'}
|
|
||||||
error={!!errors.secret_access_key}
|
|
||||||
helperText={errors.secret_access_key?.message}
|
|
||||||
margin="normal"
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setShowSecretKey(!showSecretKey)}
|
|
||||||
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={region}>
|
|
||||||
{region}
|
|
||||||
</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}
|
|
||||||
>
|
|
||||||
{editMode ? '更新' : '保存'}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ollama 对话框
|
|
||||||
*/
|
|
||||||
export const OllamaDialog: React.FC<OllamaDialogProps> = ({
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
onSubmit,
|
|
||||||
loading,
|
|
||||||
initialData,
|
|
||||||
editMode = false
|
|
||||||
}) => {
|
|
||||||
const { control, handleSubmit, reset, formState: { errors } } = useForm<OllamaFormData>({
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
|
||||||
<DialogTitle>
|
|
||||||
{editMode ? '编辑 Ollama' : '配置 Ollama'}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<Alert severity="info" sx={{ mb: 2 }}>
|
|
||||||
请确保 Ollama 服务正在运行,并且可以通过指定的 URL 访问。
|
|
||||||
</Alert>
|
|
||||||
<Box component="form" sx={{ mt: 2 }}>
|
|
||||||
<Controller
|
|
||||||
name="base_url"
|
|
||||||
control={control}
|
|
||||||
rules={{ required: 'Base URL 是必填项' }}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="Base URL"
|
|
||||||
error={!!errors.base_url}
|
|
||||||
helperText={errors.base_url?.message || 'Ollama 服务的访问地址'}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="model_name"
|
|
||||||
control={control}
|
|
||||||
rules={{ required: '模型名称是必填项' }}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="模型名称"
|
|
||||||
placeholder="llama2, codellama, mistral 等"
|
|
||||||
error={!!errors.model_name}
|
|
||||||
helperText={errors.model_name?.message || '要使用的 Ollama 模型名称'}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</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 const SystemModelDialog: React.FC<SystemModelDialogProps> = ({
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
onSubmit,
|
|
||||||
loading,
|
|
||||||
initialData,
|
|
||||||
editMode = false,
|
|
||||||
allModelOptions
|
|
||||||
}) => {
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模型对话框整合组件
|
* 模型对话框整合组件
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
AzureOpenAIFormData,
|
AzureOpenAIFormData,
|
||||||
BedrockFormData,
|
BedrockFormData,
|
||||||
OllamaFormData,
|
OllamaFormData,
|
||||||
|
// SystemModelFormData,
|
||||||
} from '../components/ModelDialogs';
|
} from '../components/ModelDialogs';
|
||||||
import type { ITenantInfo } from '@/interfaces/database/knowledge';
|
import type { ITenantInfo } from '@/interfaces/database/knowledge';
|
||||||
import { useLlmList } from '@/hooks/llm-hooks';
|
import { useLlmList } from '@/hooks/llm-hooks';
|
||||||
@@ -108,7 +109,7 @@ export const useAzureOpenAIDialog = () => {
|
|||||||
// 调用 Azure OpenAI 特定的 API
|
// 调用 Azure OpenAI 特定的 API
|
||||||
await userService.set_api_key({
|
await userService.set_api_key({
|
||||||
llm_factory: 'AzureOpenAI',
|
llm_factory: 'AzureOpenAI',
|
||||||
llm_name: data.deployment_name,
|
// llm_name: data.deployment_name,
|
||||||
api_key: data.api_key,
|
api_key: data.api_key,
|
||||||
// azure_endpoint: data.azure_endpoint,
|
// azure_endpoint: data.azure_endpoint,
|
||||||
// api_version: data.api_version,
|
// api_version: data.api_version,
|
||||||
@@ -175,7 +176,7 @@ export const useOllamaDialog = () => {
|
|||||||
// 调用添加 LLM 的 API
|
// 调用添加 LLM 的 API
|
||||||
await userService.add_llm({
|
await userService.add_llm({
|
||||||
llm_factory: 'Ollama',
|
llm_factory: 'Ollama',
|
||||||
llm_name: data.model_name,
|
// llm_name: data.model_name,
|
||||||
// base_url: data.base_url,
|
// base_url: data.base_url,
|
||||||
});
|
});
|
||||||
showMessage.success('Ollama 模型添加成功');
|
showMessage.success('Ollama 模型添加成功');
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import type { IFactory, IMyLlmModel, ILlmItem } from '@/interfaces/database/llm'
|
|||||||
import LLMFactoryCard, { MODEL_TYPE_COLORS } from './components/LLMFactoryCard';
|
import LLMFactoryCard, { MODEL_TYPE_COLORS } from './components/LLMFactoryCard';
|
||||||
import { ModelDialogs } from './components/ModelDialogs';
|
import { ModelDialogs } from './components/ModelDialogs';
|
||||||
import { useDialog } from '@/hooks/useDialog';
|
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 }) {
|
function MyLlmGridItem({ model, onDelete }: { model: ILlmItem, onDelete: (model: ILlmItem) => void }) {
|
||||||
return (
|
return (
|
||||||
@@ -84,11 +86,72 @@ function ModelsPage() {
|
|||||||
return filterFactory || [];
|
return filterFactory || [];
|
||||||
}, [llmFactory, myLlm]);
|
}, [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) => {
|
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]);
|
}, [modelDialogs]);
|
||||||
|
|
||||||
|
const handleEditLlmFactory = useCallback((factoryName: string) => {
|
||||||
|
if (factoryName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
|
|
||||||
// 处理删除单个模型
|
// 处理删除单个模型
|
||||||
@@ -205,7 +268,7 @@ function ModelsPage() {
|
|||||||
<Box sx={{ display: 'flex', gap: 1 }} onClick={(e) => e.stopPropagation()}>
|
<Box sx={{ display: 'flex', gap: 1 }} onClick={(e) => e.stopPropagation()}>
|
||||||
<Button
|
<Button
|
||||||
variant='contained' color='primary' startIcon={<EditIcon />}
|
variant='contained' color='primary' startIcon={<EditIcon />}
|
||||||
onClick={() => modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName)}
|
onClick={() => handleEditLlmFactory(factoryName)}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user