refactor(setting): split model dialogs into separate components

This commit is contained in:
2025-10-24 11:41:44 +08:00
parent cdc0a466b4
commit a9b47f776b
10 changed files with 1103 additions and 897 deletions

View File

@@ -124,3 +124,18 @@ export const IconMap = {
[LLM_FACTORY_LIST.CometAPI]: 'cometapi',
[LLM_FACTORY_LIST.DeerAPI]: 'deerapi',
};
export const LocalLlmFactories: LLMFactory[] = [
LLM_FACTORY_LIST.LocalAI,
LLM_FACTORY_LIST.Ollama,
LLM_FACTORY_LIST.LMStudio,
LLM_FACTORY_LIST.Xinference,
LLM_FACTORY_LIST.VLLM,
LLM_FACTORY_LIST.OpenAiAPICompatible,
LLM_FACTORY_LIST.TogetherAI,
LLM_FACTORY_LIST.Replicate,
LLM_FACTORY_LIST.OpenRouter,
LLM_FACTORY_LIST.HuggingFace,
LLM_FACTORY_LIST.GPUStack,
LLM_FACTORY_LIST.ModelScope,
]

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -5,7 +5,6 @@ import { LlmSvgIcon } from "@/components/AppSvgIcon";
import { IconMap, type LLMFactory } from "@/constants/llm";
import type { IFactory } from "@/interfaces/database/llm";
import { Box, Button, Card, CardContent, Chip, Typography } from "@mui/material";
import { useState } from "react";
// 模型类型标签颜色映射
export const MODEL_TYPE_COLORS: Record<string, string> = {

View File

@@ -1,909 +1,77 @@
import React, { useEffect, useMemo } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
Box,
Typography,
Alert,
CircularProgress,
IconButton,
InputAdornment,
ListSubheader,
} from '@mui/material';
import { Visibility, VisibilityOff } from '@mui/icons-material';
import { useForm, Controller } from 'react-hook-form';
import { LlmSvgIcon } from '@/components/AppSvgIcon';
import { IconMap, type LLMFactory } from '@/constants/llm';
import type { ITenantInfo } from '@/interfaces/database/knowledge';
import type { LlmModelType } from '@/constants/knowledge';
import type { IMyLlmModel, IThirdOAIModel } from '@/interfaces/database/llm';
import logger from '@/utils/logger';
import React from 'react';
// 基础对话框状态
interface BaseDialogState {
open: boolean;
loading: boolean;
editMode: boolean;
closeDialog: () => void;
}
// 导入独立的对话框组件
import ApiKeyDialog, { type ApiKeyFormData, type ApiKeyDialogProps } from './Dialog/ApiKeyDialog';
import AzureOpenAIDialog, { type AzureOpenAIFormData, type AzureOpenAIDialogProps } from './Dialog/AzureOpenAIDialog';
import BedrockDialog, { type BedrockFormData, type BedrockDialogProps, BEDROCK_REGIONS } from './Dialog/BedrockDialog';
import OllamaDialog, { type OllamaFormData, type OllamaDialogProps } from './Dialog/OllamaDialog';
import SystemModelDialog, { type SystemModelFormData, type SystemModelDialogProps, type ModelOption, type ModelGroup } from './Dialog/SystemModelDialog';
// ModelDialogs 整合组件的 Props 接口
interface ModelDialogsProps {
apiKeyDialog: BaseDialogState & {
initialData: any;
factoryName: string;
submitApiKey: (data: ApiKeyFormData) => Promise<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 {
api_key: string;
azure_endpoint: string;
api_version: string;
deployment_name: string;
}
export interface BedrockFormData {
access_key_id: string;
secret_access_key: string;
region: string;
}
export interface OllamaFormData {
base_url: string;
model_name: string;
}
// Bedrock 区域列表
const BEDROCK_REGIONS = [
'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
'eu-west-1', 'eu-west-2', 'eu-central-1', 'eu-north-1',
'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-northeast-2',
'ap-south-1', 'ca-central-1', 'sa-east-1'
];
// 基础对话框 Props
interface BaseDialogProps {
// 基础对话框 Props 接口
export interface BaseDialogProps {
open: boolean;
onClose: () => void;
loading: boolean;
editMode?: boolean;
}
// 通用 API Key 对话框
interface ApiKeyDialogProps extends BaseDialogProps {
onSubmit: (data: ApiKeyFormData) => Promise<void>;
factoryName: string;
initialData?: Partial<ApiKeyFormData>;
}
// 导出所有表单数据接口
export type { ApiKeyFormData, AzureOpenAIFormData, BedrockFormData, OllamaFormData, SystemModelFormData };
// Azure OpenAI 对话框
interface AzureOpenAIDialogProps extends BaseDialogProps {
onSubmit: (data: AzureOpenAIFormData) => Promise<void>;
initialData?: Partial<AzureOpenAIFormData>;
}
// 导出所有对话框 Props 接口
export type { ApiKeyDialogProps, AzureOpenAIDialogProps, BedrockDialogProps, OllamaDialogProps, SystemModelDialogProps };
// AWS Bedrock 对话框
interface BedrockDialogProps extends BaseDialogProps {
onSubmit: (data: BedrockFormData) => Promise<void>;
initialData?: Partial<BedrockFormData>;
}
// 导出其他相关接口和常量
export type { ModelOption, ModelGroup };
export { BEDROCK_REGIONS };
// 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);
}
// 模型对话框整合组件的 Props 接口
export interface ModelDialogsProps {
apiKeyDialog: {
open: boolean;
closeDialog: () => void;
submitApiKey: (data: ApiKeyFormData) => void;
loading: boolean;
factoryName: string;
initialData?: ApiKeyFormData;
editMode?: boolean;
};
const needsBaseUrl = ['OpenAI', 'AzureOpenAI'].includes(factoryName);
const needsGroupId = factoryName.toLowerCase() === 'minimax';
return (
<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 对话框
*/
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);
}
azureDialog: {
open: boolean;
closeDialog: () => void;
submitAzureOpenAI: (data: AzureOpenAIFormData) => void;
loading: boolean;
initialData?: AzureOpenAIFormData;
editMode?: boolean;
};
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 对话框
*/
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);
}
bedrockDialog: {
open: boolean;
closeDialog: () => void;
submitBedrock: (data: BedrockFormData) => void;
loading: boolean;
initialData?: BedrockFormData;
editMode?: boolean;
};
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);
}
ollamaDialog: {
open: boolean;
closeDialog: () => void;
submitOllama: (data: OllamaFormData) => void;
loading: boolean;
initialData?: OllamaFormData;
editMode?: boolean;
};
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';
systemDialog: {
open: boolean;
closeDialog: () => void;
submitSystemModelSetting: (data: SystemModelFormData) => Promise<void>;
loading: boolean;
initialData?: SystemModelFormData;
editMode?: boolean;
allModelOptions: any;
};
// all model options 包含了全部的 options
const llmOptions = useMemo(() => allModelOptions?.llmOptions || [], [allModelOptions]);
const embdOptions = useMemo(() => allModelOptions?.embeddingOptions || [], [allModelOptions]);
const img2txtOptions = useMemo(() => allModelOptions?.image2textOptions || [], [allModelOptions]);
const asrOptions = useMemo(() => allModelOptions?.speech2textOptions || [], [allModelOptions]);
const ttsOptions = useMemo(() => allModelOptions?.ttsOptions || [], [allModelOptions]);
const rerankOptions = useMemo(() => allModelOptions?.rerankOptions || [], [allModelOptions]);
useEffect(() => {
if (open && initialData) {
reset(initialData);
} else if (open) {
reset({
llm_id: '',
embd_id: '',
img2txt_id: '',
asr_id: '',
tts_id: '',
rerank_id: '',
});
}
}, [open, initialData, reset]);
const handleFormSubmit = async (data: ITenantInfo) => {
try {
await onSubmit(data);
onClose();
} catch (error) {
console.error('提交失败:', error);
}
};
return (
<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>
);
};
}
/**
* 模型对话框整合组件

View File

@@ -7,6 +7,7 @@ import type {
AzureOpenAIFormData,
BedrockFormData,
OllamaFormData,
// SystemModelFormData,
} from '../components/ModelDialogs';
import type { ITenantInfo } from '@/interfaces/database/knowledge';
import { useLlmList } from '@/hooks/llm-hooks';
@@ -108,7 +109,7 @@ export const useAzureOpenAIDialog = () => {
// 调用 Azure OpenAI 特定的 API
await userService.set_api_key({
llm_factory: 'AzureOpenAI',
llm_name: data.deployment_name,
// llm_name: data.deployment_name,
api_key: data.api_key,
// azure_endpoint: data.azure_endpoint,
// api_version: data.api_version,
@@ -175,7 +176,7 @@ export const useOllamaDialog = () => {
// 调用添加 LLM 的 API
await userService.add_llm({
llm_factory: 'Ollama',
llm_name: data.model_name,
// llm_name: data.model_name,
// base_url: data.base_url,
});
showMessage.success('Ollama 模型添加成功');

View File

@@ -29,6 +29,8 @@ import type { IFactory, IMyLlmModel, ILlmItem } from '@/interfaces/database/llm'
import LLMFactoryCard, { MODEL_TYPE_COLORS } from './components/LLMFactoryCard';
import { ModelDialogs } from './components/ModelDialogs';
import { useDialog } from '@/hooks/useDialog';
import logger from '@/utils/logger';
import { LLM_FACTORY_LIST, LocalLlmFactories, type LLMFactory } from '@/constants/llm';
function MyLlmGridItem({ model, onDelete }: { model: ILlmItem, onDelete: (model: ILlmItem) => void }) {
return (
@@ -84,11 +86,72 @@ function ModelsPage() {
return filterFactory || [];
}, [llmFactory, myLlm]);
// const ModalMap = useMemo(
// () => ({
// [LLMFactory.Bedrock]: showBedrockAddingModal,
// [LLMFactory.VolcEngine]: showVolcAddingModal,
// [LLMFactory.TencentHunYuan]: showHunyuanAddingModal,
// [LLMFactory.XunFeiSpark]: showSparkAddingModal,
// [LLMFactory.BaiduYiYan]: showyiyanAddingModal,
// [LLMFactory.FishAudio]: showFishAudioAddingModal,
// [LLMFactory.TencentCloud]: showTencentCloudAddingModal,
// [LLMFactory.GoogleCloud]: showGoogleAddingModal,
// [LLMFactory.AzureOpenAI]: showAzureAddingModal,
// }),
// [
// showBedrockAddingModal,
// showVolcAddingModal,
// showHunyuanAddingModal,
// showTencentCloudAddingModal,
// showSparkAddingModal,
// showyiyanAddingModal,
// showFishAudioAddingModal,
// showGoogleAddingModal,
// showAzureAddingModal,
// ],
// );
// 处理配置模型工厂
const handleConfigureFactory = useCallback((factory: IFactory) => {
modelDialogs.apiKeyDialog.openApiKeyDialog(factory.name);
if (factory == null) {
return;
}
// llm 的配置很多,有很多种类型 首先是local llm 然后是配置项不一样的
// 然后有很多自定义的配置项,需要单独用 dialog 来配置
const factoryName = factory.name as LLMFactory;
if (LocalLlmFactories.includes(factoryName)) {
// modelDialogs.localLlmDialog.openLocalLlmDialog(factoryName);
} else if (factoryName == LLM_FACTORY_LIST.AzureOpenAI) {
} else if (factoryName == LLM_FACTORY_LIST.Bedrock) {
} else if (factoryName == LLM_FACTORY_LIST.BaiduYiYan) {
} else if (factoryName == LLM_FACTORY_LIST.GoogleCloud) {
} else if (factoryName == LLM_FACTORY_LIST.FishAudio) {
} else if (factoryName == LLM_FACTORY_LIST.TencentCloud) {
} else if (factoryName == LLM_FACTORY_LIST.TencentHunYuan) {
} else if (factoryName == LLM_FACTORY_LIST.XunFeiSpark) {
} else if (factoryName == LLM_FACTORY_LIST.VolcEngine) {
} else {
modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName);
}
logger.debug('handleConfigureFactory', factory);
}, [modelDialogs]);
const handleEditLlmFactory = useCallback((factoryName: string) => {
if (factoryName == null) {
return;
}
modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName);
}, []);
const dialog = useDialog();
// 处理删除单个模型
@@ -163,10 +226,10 @@ function ModelsPage() {
<Grid size={12} key={factoryName}>
<Card variant="outlined">
<CardContent>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
cursor: 'pointer',
'&:hover': {
@@ -205,7 +268,7 @@ function ModelsPage() {
<Box sx={{ display: 'flex', gap: 1 }} onClick={(e) => e.stopPropagation()}>
<Button
variant='contained' color='primary' startIcon={<EditIcon />}
onClick={() => modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName)}
onClick={() => handleEditLlmFactory(factoryName)}
>
</Button>