feat: add new SVG assets for file icons, LLM providers, and UI elements
This commit is contained in:
12
src/pages/setting/components/LLMFactoryCard.tsx
Normal file
12
src/pages/setting/components/LLMFactoryCard.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
function LLMFactoryCard() {
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default LLMFactoryCard;
|
||||
601
src/pages/setting/components/ModelDialogs.tsx
Normal file
601
src/pages/setting/components/ModelDialogs.tsx
Normal file
@@ -0,0 +1,601 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
TextField,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Box,
|
||||
Typography,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
} from '@mui/material';
|
||||
import { Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
|
||||
// 接口定义
|
||||
export interface ApiKeyFormData {
|
||||
api_key: string;
|
||||
base_url?: string;
|
||||
group_id?: string;
|
||||
}
|
||||
|
||||
export interface AzureOpenAIFormData {
|
||||
api_key: string;
|
||||
azure_endpoint: string;
|
||||
api_version: string;
|
||||
deployment_name: string;
|
||||
}
|
||||
|
||||
export interface BedrockFormData {
|
||||
access_key_id: string;
|
||||
secret_access_key: string;
|
||||
region: string;
|
||||
}
|
||||
|
||||
export interface OllamaFormData {
|
||||
base_url: string;
|
||||
model_name: string;
|
||||
}
|
||||
|
||||
// Bedrock 区域列表
|
||||
const BEDROCK_REGIONS = [
|
||||
'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
|
||||
'eu-west-1', 'eu-west-2', 'eu-central-1', 'eu-north-1',
|
||||
'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-northeast-2',
|
||||
'ap-south-1', 'ca-central-1', 'sa-east-1'
|
||||
];
|
||||
|
||||
// 通用 API Key 对话框
|
||||
interface ApiKeyDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: ApiKeyFormData) => Promise<void>;
|
||||
loading: boolean;
|
||||
factoryName: string;
|
||||
initialData?: Partial<ApiKeyFormData>;
|
||||
editMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用 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);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const needsBaseUrl = ['OpenAI', 'AzureOpenAI'].includes(factoryName);
|
||||
const needsGroupId = factoryName.toLowerCase() === 'minimax';
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
// Azure OpenAI 对话框
|
||||
interface AzureOpenAIDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: AzureOpenAIFormData) => Promise<void>;
|
||||
loading: boolean;
|
||||
initialData?: Partial<AzureOpenAIFormData>;
|
||||
editMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Azure OpenAI 对话框
|
||||
*/
|
||||
export const AzureOpenAIDialog: React.FC<AzureOpenAIDialogProps> = ({
|
||||
open,
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{editMode ? '编辑 Azure OpenAI' : '配置 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'}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
// AWS Bedrock 对话框
|
||||
interface BedrockDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: BedrockFormData) => Promise<void>;
|
||||
loading: boolean;
|
||||
initialData?: Partial<BedrockFormData>;
|
||||
editMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* AWS Bedrock 对话框
|
||||
*/
|
||||
export const BedrockDialog: React.FC<BedrockDialogProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
interface OllamaDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: OllamaFormData) => Promise<void>;
|
||||
loading: boolean;
|
||||
initialData?: Partial<OllamaFormData>;
|
||||
editMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
};
|
||||
288
src/pages/setting/hooks/useModelDialogs.ts
Normal file
288
src/pages/setting/hooks/useModelDialogs.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useMessage } from '@/hooks/useSnackbar';
|
||||
import userService from '@/services/user_service';
|
||||
import logger from '@/utils/logger';
|
||||
import type {
|
||||
ApiKeyFormData,
|
||||
AzureOpenAIFormData,
|
||||
BedrockFormData,
|
||||
OllamaFormData,
|
||||
} from '../components/ModelDialogs';
|
||||
|
||||
// 对话框状态管理 hook
|
||||
export const useDialogState = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [initialData, setInitialData] = useState<any>(null);
|
||||
|
||||
const openDialog = useCallback((data?: any, isEdit = false) => {
|
||||
setInitialData(data);
|
||||
setEditMode(isEdit);
|
||||
setOpen(true);
|
||||
}, []);
|
||||
|
||||
const closeDialog = useCallback(() => {
|
||||
setOpen(false);
|
||||
setInitialData(null);
|
||||
setEditMode(false);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
open,
|
||||
loading,
|
||||
editMode,
|
||||
initialData,
|
||||
setLoading,
|
||||
openDialog,
|
||||
closeDialog,
|
||||
};
|
||||
};
|
||||
|
||||
// API Key 对话框管理
|
||||
export const useApiKeyDialog = () => {
|
||||
const dialogState = useDialogState();
|
||||
const showMessage = useMessage();
|
||||
const [factoryName, setFactoryName] = useState('');
|
||||
|
||||
const openApiKeyDialog = useCallback((factory: string, data?: Partial<ApiKeyFormData>, isEdit = false) => {
|
||||
setFactoryName(factory);
|
||||
dialogState.openDialog(data, isEdit);
|
||||
}, [dialogState]);
|
||||
|
||||
const submitApiKey = useCallback(async (data: ApiKeyFormData) => {
|
||||
dialogState.setLoading(true);
|
||||
try {
|
||||
await userService.set_api_key({
|
||||
factory_name: factoryName,
|
||||
model_name: '', // 根据实际需求调整
|
||||
// api_key: data.api_key,
|
||||
...data
|
||||
});
|
||||
showMessage.success('API Key 配置成功');
|
||||
dialogState.closeDialog();
|
||||
} catch (error) {
|
||||
logger.error('API Key 配置失败:', error);
|
||||
showMessage.error('API Key 配置失败');
|
||||
throw error;
|
||||
} finally {
|
||||
dialogState.setLoading(false);
|
||||
}
|
||||
}, [factoryName, dialogState]);
|
||||
|
||||
return {
|
||||
...dialogState,
|
||||
factoryName,
|
||||
openApiKeyDialog,
|
||||
submitApiKey,
|
||||
};
|
||||
};
|
||||
|
||||
// Azure OpenAI 对话框管理
|
||||
export const useAzureOpenAIDialog = () => {
|
||||
const dialogState = useDialogState();
|
||||
const showMessage = useMessage();
|
||||
|
||||
const submitAzureOpenAI = useCallback(async (data: AzureOpenAIFormData) => {
|
||||
dialogState.setLoading(true);
|
||||
try {
|
||||
// 调用 Azure OpenAI 特定的 API
|
||||
await userService.set_api_key({
|
||||
factory_name: 'AzureOpenAI',
|
||||
model_name: data.deployment_name,
|
||||
api_key: data.api_key,
|
||||
// azure_endpoint: data.azure_endpoint,
|
||||
// api_version: data.api_version,
|
||||
});
|
||||
showMessage.success('Azure OpenAI 配置成功');
|
||||
dialogState.closeDialog();
|
||||
} catch (error) {
|
||||
logger.error('Azure OpenAI 配置失败:', error);
|
||||
showMessage.error('Azure OpenAI 配置失败');
|
||||
throw error;
|
||||
} finally {
|
||||
dialogState.setLoading(false);
|
||||
}
|
||||
}, [dialogState]);
|
||||
|
||||
return {
|
||||
...dialogState,
|
||||
submitAzureOpenAI,
|
||||
};
|
||||
};
|
||||
|
||||
// AWS Bedrock 对话框管理
|
||||
export const useBedrockDialog = () => {
|
||||
const dialogState = useDialogState();
|
||||
const showMessage = useMessage();
|
||||
|
||||
const submitBedrock = useCallback(async (data: BedrockFormData) => {
|
||||
dialogState.setLoading(true);
|
||||
try {
|
||||
// 调用 Bedrock 特定的 API
|
||||
await userService.set_api_key({
|
||||
factory_name: 'Bedrock',
|
||||
model_name: '',
|
||||
api_key: '', // Bedrock 使用 access key
|
||||
// access_key_id: data.access_key_id,
|
||||
// secret_access_key: data.secret_access_key,
|
||||
// region: data.region,
|
||||
});
|
||||
showMessage.success('AWS Bedrock 配置成功');
|
||||
dialogState.closeDialog();
|
||||
} catch (error) {
|
||||
logger.error('AWS Bedrock 配置失败:', error);
|
||||
showMessage.error('AWS Bedrock 配置失败');
|
||||
throw error;
|
||||
} finally {
|
||||
dialogState.setLoading(false);
|
||||
}
|
||||
}, [dialogState]);
|
||||
|
||||
return {
|
||||
...dialogState,
|
||||
submitBedrock,
|
||||
};
|
||||
};
|
||||
|
||||
// Ollama 对话框管理
|
||||
export const useOllamaDialog = () => {
|
||||
const dialogState = useDialogState();
|
||||
const showMessage = useMessage();
|
||||
|
||||
const submitOllama = useCallback(async (data: OllamaFormData) => {
|
||||
dialogState.setLoading(true);
|
||||
try {
|
||||
// 调用添加 LLM 的 API
|
||||
await userService.add_llm({
|
||||
factory_name: 'Ollama',
|
||||
model_name: data.model_name,
|
||||
// base_url: data.base_url,
|
||||
});
|
||||
showMessage.success('Ollama 模型添加成功');
|
||||
dialogState.closeDialog();
|
||||
} catch (error) {
|
||||
logger.error('Ollama 模型添加失败:', error);
|
||||
showMessage.error('Ollama 模型添加失败');
|
||||
throw error;
|
||||
} finally {
|
||||
dialogState.setLoading(false);
|
||||
}
|
||||
}, [dialogState]);
|
||||
|
||||
return {
|
||||
...dialogState,
|
||||
submitOllama,
|
||||
};
|
||||
};
|
||||
|
||||
// 删除操作管理
|
||||
export const useDeleteOperations = () => {
|
||||
const showMessage = useMessage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const deleteLlm = useCallback(async (factoryName: string, modelName: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await userService.delete_llm({
|
||||
factory_name: factoryName,
|
||||
model_name: modelName,
|
||||
});
|
||||
showMessage.success('模型删除成功');
|
||||
} catch (error) {
|
||||
logger.error('模型删除失败:', error);
|
||||
showMessage.error('模型删除失败');
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const deleteFactory = useCallback(async (factoryName: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await userService.deleteFactory({
|
||||
factory_name: factoryName,
|
||||
});
|
||||
showMessage.success('模型工厂删除成功');
|
||||
} catch (error) {
|
||||
logger.error('模型工厂删除失败:', error);
|
||||
showMessage.error('模型工厂删除失败');
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
loading,
|
||||
deleteLlm,
|
||||
deleteFactory,
|
||||
};
|
||||
};
|
||||
|
||||
// 系统默认模型设置
|
||||
export const useSystemModelSetting = () => {
|
||||
const dialogState = useDialogState();
|
||||
const showMessage = useMessage();
|
||||
|
||||
const submitSystemModelSetting = useCallback(async (data: { defaultModel: string }) => {
|
||||
dialogState.setLoading(true);
|
||||
try {
|
||||
// 这里需要根据实际的 API 接口调整
|
||||
// await userService.setSystemDefaultModel(data);
|
||||
showMessage.success('系统默认模型设置成功');
|
||||
dialogState.closeDialog();
|
||||
} catch (error) {
|
||||
logger.error('系统默认模型设置失败:', error);
|
||||
showMessage.error('系统默认模型设置失败');
|
||||
throw error;
|
||||
} finally {
|
||||
dialogState.setLoading(false);
|
||||
}
|
||||
}, [dialogState]);
|
||||
|
||||
return {
|
||||
...dialogState,
|
||||
submitSystemModelSetting,
|
||||
};
|
||||
};
|
||||
|
||||
// 统一的模型对话框管理器
|
||||
export const useModelDialogs = () => {
|
||||
const apiKeyDialog = useApiKeyDialog();
|
||||
const azureDialog = useAzureOpenAIDialog();
|
||||
const bedrockDialog = useBedrockDialog();
|
||||
const ollamaDialog = useOllamaDialog();
|
||||
const systemDialog = useSystemModelSetting();
|
||||
const deleteOps = useDeleteOperations();
|
||||
|
||||
// 根据工厂类型打开对应的对话框
|
||||
const openFactoryDialog = useCallback((factoryName: string, data?: any, isEdit = false) => {
|
||||
switch (factoryName.toLowerCase()) {
|
||||
case 'azureopenai':
|
||||
azureDialog.openDialog(data, isEdit);
|
||||
break;
|
||||
case 'bedrock':
|
||||
bedrockDialog.openDialog(data, isEdit);
|
||||
break;
|
||||
case 'ollama':
|
||||
ollamaDialog.openDialog(data, isEdit);
|
||||
break;
|
||||
default:
|
||||
// 默认使用 API Key 对话框
|
||||
apiKeyDialog.openApiKeyDialog(factoryName, data, isEdit);
|
||||
break;
|
||||
}
|
||||
}, [apiKeyDialog, azureDialog, bedrockDialog, ollamaDialog]);
|
||||
|
||||
return {
|
||||
apiKeyDialog,
|
||||
azureDialog,
|
||||
bedrockDialog,
|
||||
ollamaDialog,
|
||||
systemDialog,
|
||||
deleteOps,
|
||||
openFactoryDialog,
|
||||
};
|
||||
};
|
||||
119
src/pages/setting/models-setting-reference.md
Normal file
119
src/pages/setting/models-setting-reference.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Models Setting 页面功能分析
|
||||
|
||||
## 概述
|
||||
Models Setting 页面是一个复杂的模型管理界面,主要用于管理 LLM 模型工厂和用户的个人模型配置。
|
||||
|
||||
## 主要功能模块
|
||||
|
||||
### 1. 核心数据结构
|
||||
- **LLM Factory**: 模型工厂列表,包含各种 AI 服务提供商
|
||||
- **My LLM**: 用户已配置的模型列表
|
||||
- **Model Types**: 支持多种模型类型(LLM、TEXT EMBEDDING、TEXT RE-RANK、TTS、SPEECH2TEXT、IMAGE2TEXT、MODERATION)
|
||||
|
||||
### 2. 支持的模型工厂
|
||||
根据参考代码,支持以下模型工厂:
|
||||
- **OpenAI**: 通用 API Key 配置
|
||||
- **Azure OpenAI**: 需要特殊配置的 Azure 服务
|
||||
- **Google Cloud**: Google 云服务模型
|
||||
- **AWS Bedrock**: Amazon 的 AI 服务
|
||||
- **Ollama**: 本地部署模型
|
||||
- **VolcEngine**: 火山引擎
|
||||
- **Tencent HunYuan**: 腾讯混元
|
||||
- **XunFei Spark**: 讯飞星火
|
||||
- **Baidu YiYan**: 百度一言
|
||||
- **Fish Audio**: 语音合成服务
|
||||
- **Tencent Cloud**: 腾讯云
|
||||
- **Langfuse**: 模型监控和分析
|
||||
|
||||
### 3. 主要组件结构
|
||||
|
||||
#### 3.1 主页面组件 (index.tsx)
|
||||
- **ModelCard**: 模型工厂卡片组件
|
||||
- 显示工厂信息、支持的模型类型标签
|
||||
- 提供配置按钮(API Key 或添加模型)
|
||||
- 显示/隐藏模型列表功能
|
||||
- 删除工厂功能
|
||||
- **模型列表**: 展示每个工厂下的具体模型
|
||||
- 模型名称和类型
|
||||
- 编辑和删除操作
|
||||
|
||||
#### 3.2 配置模态框组件
|
||||
每个模型工厂都有对应的配置模态框:
|
||||
|
||||
- **api-key-modal**: 通用 API Key 配置
|
||||
- API Key 输入
|
||||
- Base URL 配置(OpenAI 等)
|
||||
- Group ID 配置(Minimax 等)
|
||||
|
||||
- **azure-openai-modal**: Azure OpenAI 特殊配置
|
||||
- **google-modal**: Google Cloud 配置
|
||||
- **bedrock-modal**: AWS Bedrock 配置
|
||||
- **ollama-modal**: Ollama 本地模型配置
|
||||
- **volcengine-modal**: 火山引擎配置
|
||||
- **hunyuan-modal**: 腾讯混元配置
|
||||
- **spark-modal**: 讯飞星火配置
|
||||
- **yiyan-modal**: 百度一言配置
|
||||
- **fish-audio-modal**: Fish Audio 配置
|
||||
- **next-tencent-modal**: 腾讯云配置
|
||||
|
||||
#### 3.3 系统设置
|
||||
- **system-model-setting-modal**: 系统默认模型设置
|
||||
- **langfuse**: 模型监控和分析配置
|
||||
|
||||
### 4. 状态管理 (hooks.ts)
|
||||
提供统一的状态管理 hooks:
|
||||
- **useSubmitApiKey**: API Key 配置状态管理
|
||||
- **useSubmitAzure**: Azure 配置状态管理
|
||||
- **useSubmitGoogle**: Google 配置状态管理
|
||||
- **useSubmitBedrock**: Bedrock 配置状态管理
|
||||
- **useSubmitOllama**: Ollama 配置状态管理
|
||||
- **useSubmitSystemModelSetting**: 系统模型设置状态管理
|
||||
- **useHandleDeleteLlm**: 删除模型操作
|
||||
- **useHandleDeleteFactory**: 删除工厂操作
|
||||
|
||||
### 5. 常量配置 (constant.ts)
|
||||
- **BedrockRegionList**: AWS Bedrock 支持的区域列表
|
||||
|
||||
## 用户交互流程
|
||||
|
||||
### 5.1 添加模型工厂
|
||||
1. 用户点击"添加模型"或"API-Key"按钮
|
||||
2. 根据工厂类型打开对应的配置模态框
|
||||
3. 用户填写必要的配置信息(API Key、Base URL 等)
|
||||
4. 提交配置,系统验证并保存
|
||||
|
||||
### 5.2 管理已有模型
|
||||
1. 查看模型工厂卡片,显示基本信息和标签
|
||||
2. 点击"显示更多模型"查看具体模型列表
|
||||
3. 对单个模型进行编辑或删除操作
|
||||
4. 删除整个模型工厂
|
||||
|
||||
### 5.3 系统设置
|
||||
1. 设置系统默认使用的 LLM 模型
|
||||
2. 配置模型监控和分析工具
|
||||
|
||||
## 技术特点
|
||||
|
||||
### 6.1 模块化设计
|
||||
- 每个模型工厂都有独立的配置组件
|
||||
- 统一的状态管理和操作接口
|
||||
- 可扩展的架构设计
|
||||
|
||||
### 6.2 用户体验
|
||||
- 直观的卡片式布局
|
||||
- 标签系统清晰显示模型能力
|
||||
- 折叠/展开功能优化空间利用
|
||||
- 确认对话框防止误操作
|
||||
|
||||
### 6.3 数据处理
|
||||
- 支持多种配置参数
|
||||
- 实时验证和错误处理
|
||||
- 本地和云端模型统一管理
|
||||
|
||||
## 实现要点
|
||||
|
||||
1. **组件复用**: 通用的模态框组件可以复用基础结构
|
||||
2. **状态管理**: 使用 hooks 统一管理各种模态框的显示状态
|
||||
3. **类型安全**: 严格的 TypeScript 类型定义
|
||||
4. **错误处理**: 完善的表单验证和错误提示
|
||||
5. **国际化**: 支持多语言界面
|
||||
@@ -1,9 +1,457 @@
|
||||
function ModelsSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Model Setting</h1>
|
||||
</div>
|
||||
);
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
Button,
|
||||
Chip,
|
||||
IconButton,
|
||||
Collapse,
|
||||
Grid,
|
||||
Divider,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
ExpandLess as ExpandLessIcon,
|
||||
Settings as SettingsIcon,
|
||||
Delete as DeleteIcon,
|
||||
Edit as EditIcon,
|
||||
Add as AddIcon,
|
||||
Visibility as VisibilityIcon,
|
||||
VisibilityOff as VisibilityOffIcon,
|
||||
} from '@mui/icons-material';
|
||||
import { useLlmModelSetting } from '@/hooks/setting-hooks';
|
||||
import { useModelDialogs } from './hooks/useModelDialogs';
|
||||
import { LlmSvgIcon } from '@/components/AppSvgIcon';
|
||||
import { LLM_FACTORY_LIST, IconMap, type LLMFactory } from '@/constants/llm';
|
||||
import type { IFactory, IMyLlmModel, ILlmItem } from '@/interfaces/database/llm';
|
||||
|
||||
// 模型类型标签颜色映射
|
||||
const MODEL_TYPE_COLORS: Record<string, string> = {
|
||||
'LLM': '#1976d2',
|
||||
'TEXT EMBEDDING': '#388e3c',
|
||||
'TEXT RE-RANK': '#f57c00',
|
||||
'TTS': '#7b1fa2',
|
||||
'SPEECH2TEXT': '#c2185b',
|
||||
'IMAGE2TEXT': '#5d4037',
|
||||
'MODERATION': '#455a64',
|
||||
};
|
||||
|
||||
// 模型工厂卡片组件
|
||||
interface ModelFactoryCardProps {
|
||||
factory: IFactory;
|
||||
myModels: ILlmItem[];
|
||||
onConfigure: (factory: IFactory) => void;
|
||||
onDeleteFactory: (factoryName: string) => void;
|
||||
onDeleteModel: (factoryName: string, modelName: string) => void;
|
||||
onEditModel: (factory: IFactory, model: ILlmItem) => void;
|
||||
}
|
||||
|
||||
export default ModelsSetting;
|
||||
const ModelFactoryCard: React.FC<ModelFactoryCardProps> = ({
|
||||
factory,
|
||||
myModels,
|
||||
onConfigure,
|
||||
onDeleteFactory,
|
||||
onDeleteModel,
|
||||
onEditModel,
|
||||
}) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
||||
const handleExpandClick = () => {
|
||||
setExpanded(!expanded);
|
||||
};
|
||||
|
||||
const handleDeleteFactory = () => {
|
||||
onDeleteFactory(factory.name);
|
||||
setShowDeleteConfirm(false);
|
||||
};
|
||||
|
||||
// 获取工厂图标名称
|
||||
const getFactoryIconName = (factoryName: LLMFactory) => {
|
||||
return IconMap[factoryName] || 'default';
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card sx={{ mb: 2, border: '1px solid #e0e0e0' }}>
|
||||
<CardContent>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<LlmSvgIcon
|
||||
name={getFactoryIconName(factory.name as LLMFactory)}
|
||||
sx={{ width: 40, height: 40, color: 'primary.main' }}/>
|
||||
<Box>
|
||||
<Typography variant="h6" component="div">
|
||||
{factory.name}
|
||||
</Typography>
|
||||
<Box display="flex" gap={1} mt={1}>
|
||||
{factory.model_types.map((type) => (
|
||||
<Chip
|
||||
key={type}
|
||||
label={type.toUpperCase()}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: MODEL_TYPE_COLORS[type.toUpperCase()] || '#757575',
|
||||
color: 'white',
|
||||
fontSize: '0.7rem',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<SettingsIcon />}
|
||||
onClick={() => onConfigure(factory)}
|
||||
>
|
||||
配置
|
||||
</Button>
|
||||
{myModels.length > 0 && (
|
||||
<IconButton onClick={handleExpandClick}>
|
||||
{expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton
|
||||
color="error"
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
已配置的模型 ({myModels.length})
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{myModels.map((model) => (
|
||||
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={model.name}>
|
||||
<Card variant="outlined" sx={{ p: 2 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="start">
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{model.name}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{model.type}
|
||||
</Typography>
|
||||
<Typography variant="caption" display="block" color="text.secondary">
|
||||
Max Tokens: {model.max_tokens}
|
||||
</Typography>
|
||||
<Typography variant="caption" display="block" color="text.secondary">
|
||||
Used: {model.used_token}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => onEditModel(factory, model)}
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => onDeleteModel(factory.name, model.name)}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 删除确认对话框 */}
|
||||
<Dialog open={showDeleteConfirm} onClose={() => setShowDeleteConfirm(false)}>
|
||||
<DialogTitle>确认删除</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
确定要删除模型工厂 "{factory.name}" 吗?这将删除该工厂下的所有模型配置。
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setShowDeleteConfirm(false)}>取消</Button>
|
||||
<Button onClick={handleDeleteFactory} color="error" variant="contained">
|
||||
删除
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 系统默认模型设置组件
|
||||
interface SystemModelSettingProps {
|
||||
myLlm: Record<string, IMyLlmModel> | undefined;
|
||||
}
|
||||
|
||||
const SystemModelSetting: React.FC<SystemModelSettingProps> = ({ myLlm }) => {
|
||||
const [defaultModel, setDefaultModel] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 获取所有可用的聊天模型
|
||||
const chatModels = myLlm ? Object.values(myLlm).flatMap(group =>
|
||||
group.llm.filter(model => model.type.toLowerCase().includes('chat') || model.type.toLowerCase().includes('llm'))
|
||||
) : [];
|
||||
|
||||
const handleSaveDefaultModel = async () => {
|
||||
if (!defaultModel) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// TODO: 调用设置系统默认模型的 API
|
||||
console.log('Setting default model:', defaultModel);
|
||||
// await userService.setSystemDefaultModel({ model: defaultModel });
|
||||
} catch (error) {
|
||||
console.error('设置默认模型失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
系统默认 LLM 模型
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
设置系统默认使用的大语言模型,用于对话和文本生成任务。
|
||||
</Typography>
|
||||
<Box display="flex" gap={2} alignItems="center" mt={2}>
|
||||
<FormControl sx={{ minWidth: 300 }}>
|
||||
<InputLabel>选择默认模型</InputLabel>
|
||||
<Select
|
||||
value={defaultModel}
|
||||
label="选择默认模型"
|
||||
onChange={(e) => setDefaultModel(e.target.value)}
|
||||
>
|
||||
{chatModels.map((model) => (
|
||||
<MenuItem key={`${model.name}-${model.type}`} value={model.name}>
|
||||
{model.name} ({model.type})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSaveDefaultModel}
|
||||
disabled={!defaultModel || loading}
|
||||
startIcon={loading ? <CircularProgress size={16} /> : undefined}
|
||||
>
|
||||
保存设置
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// 主页面组件
|
||||
function ModelsPage() {
|
||||
const { llmFactory, myLlm } = useLlmModelSetting();
|
||||
const modelDialogs = useModelDialogs();
|
||||
|
||||
// 处理配置模型工厂
|
||||
const handleConfigureFactory = useCallback((factory: IFactory) => {
|
||||
// modelDialogs.openDialog(factory.name);
|
||||
}, [modelDialogs]);
|
||||
|
||||
// 处理删除模型工厂
|
||||
const handleDeleteFactory = useCallback(async (factoryName: string) => {
|
||||
try {
|
||||
// await modelDialogs.deleteOperations.deleteFactory(factoryName);
|
||||
// 刷新数据
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error('删除工厂失败:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 处理删除单个模型
|
||||
const handleDeleteModel = useCallback(async (factoryName: string, modelName: string) => {
|
||||
try {
|
||||
// await modelDialogs.deleteOperations.deleteLlm(factoryName, modelName);
|
||||
// 刷新数据
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error('删除模型失败:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 处理编辑模型
|
||||
const handleEditModel = useCallback((factory: IFactory, model: ILlmItem) => {
|
||||
// 设置编辑模式并打开对话框
|
||||
// modelDialogs.openDialog(factory.name, {
|
||||
// model_name: model.name,
|
||||
// api_base: model.api_base,
|
||||
// max_tokens: model.max_tokens,
|
||||
// });
|
||||
}, [modelDialogs]);
|
||||
|
||||
// 根据工厂名称获取对应的模型列表
|
||||
const getModelsForFactory = (factoryName: LLMFactory): ILlmItem[] => {
|
||||
if (!myLlm) return [];
|
||||
const factoryGroup = myLlm[factoryName];
|
||||
return factoryGroup?.llm || [];
|
||||
};
|
||||
|
||||
if (!llmFactory || !myLlm) {
|
||||
return (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
模型设置
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" gutterBottom>
|
||||
管理您的 LLM 模型工厂和个人模型配置
|
||||
</Typography>
|
||||
|
||||
{/* 系统默认模型设置 */}
|
||||
<SystemModelSetting myLlm={myLlm} />
|
||||
|
||||
{/* My LLM 部分 */}
|
||||
{/* <Box mb={4}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
我的 LLM 模型
|
||||
</Typography>
|
||||
{!myLlm || Object.keys(myLlm).length === 0 ? (
|
||||
<Alert severity="info">
|
||||
您还没有配置任何 LLM 模型。请在下方的模型工厂中进行配置。
|
||||
</Alert>
|
||||
) : (
|
||||
<Grid container spacing={2}>
|
||||
{Object.entries(myLlm).map(([factoryName, group]) => (
|
||||
<Grid size={12} key={factoryName}>
|
||||
<Card variant="outlined">
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{factoryName}
|
||||
</Typography>
|
||||
<Box display="flex" gap={1} mb={2}>
|
||||
{group.tags.split(',').map((tag) => (
|
||||
<Chip
|
||||
key={tag}
|
||||
label={tag.trim()}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: MODEL_TYPE_COLORS[tag.trim()] || '#757575',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Grid container spacing={2}>
|
||||
{group.llm.map((model) => (
|
||||
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={model.name}>
|
||||
<Card variant="outlined" sx={{ p: 2 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="flex-start" mb={1}>
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{model.name}
|
||||
</Typography>
|
||||
<Box>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => handleEditModel({ name: factoryName } as IFactory, model)}
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => handleDeleteModel(factoryName, model.name)}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
<Chip
|
||||
label={model.type}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: MODEL_TYPE_COLORS[model.type.toUpperCase()] || '#757575',
|
||||
color: 'white',
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption" display="block" color="text.secondary">
|
||||
Max Tokens: {model.max_tokens}
|
||||
</Typography>
|
||||
<Typography variant="caption" display="block" color="text.secondary">
|
||||
Used: {model.used_token}
|
||||
</Typography>
|
||||
{model.api_base && (
|
||||
<Typography variant="caption" display="block" color="text.secondary">
|
||||
Base URL: {model.api_base}
|
||||
</Typography>
|
||||
)}
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</Box> */}
|
||||
|
||||
{/* LLM Factory 部分 */}
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
LLM 模型工厂
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
配置各种 AI 服务提供商的模型
|
||||
</Typography>
|
||||
|
||||
{llmFactory.map((factory) => (
|
||||
<ModelFactoryCard
|
||||
key={factory.name}
|
||||
factory={factory}
|
||||
myModels={getModelsForFactory(factory.name as LLMFactory)}
|
||||
onConfigure={handleConfigureFactory}
|
||||
onDeleteFactory={handleDeleteFactory}
|
||||
onDeleteModel={handleDeleteModel}
|
||||
onEditModel={handleEditModel}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* 模型配置对话框 */}
|
||||
{/* <ModelDialogs {...modelDialogs} /> */}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsPage;
|
||||
Reference in New Issue
Block a user