mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-17 06:03:22 +08:00
feat(model): 集成ModelKit支持并重构模型管理功能
重构模型管理模块,集成ModelKit作为统一模型服务接口 - 添加ZhiPu、Gemini和Other模型供应商支持 - 移除原有模型检查和列表获取逻辑,改用ModelKit实现 - 新增modelService服务层处理模型数据转换 - 删除旧版modelModal组件,改用@yokowu/modelkit-ui组件 - 更新相关依赖版本,包括ModelKit v1.4.3
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
import React, { useState } from 'react';
|
||||
import Card from '@/components/card';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { deleteDeleteModel, getMyModelList, putUpdateModel } from '@/api/Model';
|
||||
import { DomainModel, ConstsModelStatus, ConstsModelType } from '@/api/types';
|
||||
import { Stack, Box, Button, Grid2 as Grid, ButtonBase } from '@mui/material';
|
||||
import {
|
||||
deleteDeleteModel,
|
||||
putUpdateModel,
|
||||
} from '@/api/Model';
|
||||
import { DomainModel, ConstsModelStatus, ConstsModelType, } from '@/api/types';
|
||||
import { Stack, Box, Button, Grid2 as Grid, ButtonBase, ThemeProvider } from '@mui/material';
|
||||
import StyledLabel from '@/components/label';
|
||||
import { Icon, Modal, message } from '@c-x/ui';
|
||||
import { addCommasToNumber } from '@/utils';
|
||||
import NoData from '@/assets/images/nodata.png';
|
||||
import { ModelProvider } from '../constant';
|
||||
import ModelModal from './modelModal';
|
||||
import { ModelModal, Model} from '@yokowu/modelkit-ui';
|
||||
import { localModelToModelKitModel, modelService } from '@/pages/model/components/services/modelService';
|
||||
|
||||
const ModelItem = ({
|
||||
data,
|
||||
@@ -319,8 +322,11 @@ const ModelCard: React.FC<IModelCardProps> = ({
|
||||
setEditData(null);
|
||||
}}
|
||||
refresh={refreshModel}
|
||||
data={editData}
|
||||
type={modelType}
|
||||
data={editData ? localModelToModelKitModel(editData) : null}
|
||||
model_type={modelType}
|
||||
modelService={modelService}
|
||||
language="zh-CN"
|
||||
messageComponent={message}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,831 +0,0 @@
|
||||
import {
|
||||
postCreateModel,
|
||||
getGetProviderModelList,
|
||||
postCheckModel,
|
||||
putUpdateModel,
|
||||
} from '@/api';
|
||||
import { ConstsModelType, DomainModel } from '@/api/types';
|
||||
import Card from '@/components/card';
|
||||
import { ModelProvider } from '@/pages/model/constant';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
MenuItem,
|
||||
Stack,
|
||||
TextField,
|
||||
useTheme,
|
||||
alpha as addOpacityToColor,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
} from '@mui/material';
|
||||
import { Icon, message, Modal } from '@c-x/ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
interface AddModelProps {
|
||||
open: boolean;
|
||||
data: DomainModel | null;
|
||||
type: ConstsModelType;
|
||||
onClose: () => void;
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
interface AddModelForm {
|
||||
provider: keyof typeof ModelProvider;
|
||||
model: string;
|
||||
base_url: string;
|
||||
api_version: string;
|
||||
api_key: string;
|
||||
api_header_key: string;
|
||||
api_header_value: string;
|
||||
type: ConstsModelType;
|
||||
show_name: string;
|
||||
// 高级设置字段
|
||||
context_window_size: number;
|
||||
max_output_tokens: number;
|
||||
enable_r1_params: boolean;
|
||||
support_image: boolean;
|
||||
support_compute: boolean;
|
||||
support_prompt_caching: boolean;
|
||||
}
|
||||
|
||||
const titleMap = {
|
||||
[ConstsModelType.ModelTypeLLM]: '对话模型',
|
||||
[ConstsModelType.ModelTypeCoder]: '代码补全模型',
|
||||
[ConstsModelType.ModelTypeEmbedding]: '向量模型',
|
||||
[ConstsModelType.ModelTypeAudio]: '音频模型',
|
||||
[ConstsModelType.ModelTypeReranker]: '重排序模型',
|
||||
};
|
||||
|
||||
const ModelAdd = ({
|
||||
open,
|
||||
onClose,
|
||||
refresh,
|
||||
data,
|
||||
type = ConstsModelType.ModelTypeLLM,
|
||||
}: AddModelProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const providers: Record<string, any> = ModelProvider;
|
||||
|
||||
const {
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
setValue,
|
||||
watch,
|
||||
} = useForm<AddModelForm>({
|
||||
defaultValues: {
|
||||
type,
|
||||
provider: 'BaiZhiCloud',
|
||||
base_url: providers['BaiZhiCloud'].defaultBaseUrl,
|
||||
model: '',
|
||||
api_version: '',
|
||||
api_key: '',
|
||||
api_header_key: '',
|
||||
api_header_value: '',
|
||||
show_name: '',
|
||||
// 高级设置默认值
|
||||
context_window_size: 64000,
|
||||
max_output_tokens: 8192,
|
||||
enable_r1_params: false,
|
||||
support_image: false,
|
||||
support_compute: false,
|
||||
support_prompt_caching: false,
|
||||
},
|
||||
});
|
||||
|
||||
const providerBrand = watch('provider');
|
||||
|
||||
const [modelUserList, setModelUserList] = useState<{ model: string }[]>([]);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modelLoading, setModelLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [expandAdvanced, setExpandAdvanced] = useState(false);
|
||||
|
||||
const handleReset = () => {
|
||||
onClose();
|
||||
reset({
|
||||
type,
|
||||
provider: 'BaiZhiCloud',
|
||||
model: '',
|
||||
base_url: '',
|
||||
api_key: '',
|
||||
api_version: '',
|
||||
api_header_key: '',
|
||||
api_header_value: '',
|
||||
// 重置高级设置
|
||||
context_window_size: 64000,
|
||||
max_output_tokens: 8192,
|
||||
enable_r1_params: false,
|
||||
support_image: false,
|
||||
support_compute: false,
|
||||
support_prompt_caching: false,
|
||||
});
|
||||
setModelUserList([]);
|
||||
setSuccess(false);
|
||||
setLoading(false);
|
||||
setModelLoading(false);
|
||||
setError('');
|
||||
// 重置高级设置的展开状态
|
||||
setExpandAdvanced(false);
|
||||
refresh();
|
||||
};
|
||||
|
||||
const getModel = (value: AddModelForm) => {
|
||||
let header = '';
|
||||
if (value.api_header_key && value.api_header_value) {
|
||||
header = value.api_header_key + '=' + value.api_header_value;
|
||||
}
|
||||
setModelLoading(true);
|
||||
getGetProviderModelList({
|
||||
type,
|
||||
api_key: value.api_key,
|
||||
base_url: value.base_url,
|
||||
provider: value.provider as Exclude<typeof value.provider, 'Other'>,
|
||||
api_header: header,
|
||||
})
|
||||
.then((res) => {
|
||||
setModelUserList(
|
||||
(res.models || [])
|
||||
.filter((item): item is { model: string } => !!item.model)
|
||||
.sort((a, b) => a.model!.localeCompare(b.model!))
|
||||
);
|
||||
if (
|
||||
data &&
|
||||
(res.models || []).find((it) => it.model === data.model_name)
|
||||
) {
|
||||
setValue('model', data.model_name!);
|
||||
} else {
|
||||
setValue('model', res.models?.[0]?.model || '');
|
||||
}
|
||||
setSuccess(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setModelLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (value: AddModelForm) => {
|
||||
let header = '';
|
||||
if (value.api_header_key && value.api_header_value) {
|
||||
header = value.api_header_key + '=' + value.api_header_value;
|
||||
}
|
||||
setError('');
|
||||
setLoading(true);
|
||||
postCheckModel({
|
||||
// @ts-ignore
|
||||
type,
|
||||
api_key: value.api_key,
|
||||
api_base: value.base_url,
|
||||
api_version: value.api_version,
|
||||
// @ts-ignore
|
||||
provider: value.provider,
|
||||
model_name: value.model,
|
||||
api_header: header,
|
||||
})
|
||||
.then((res) => {
|
||||
if (data) {
|
||||
putUpdateModel({
|
||||
api_key: value.api_key,
|
||||
api_base: value.base_url,
|
||||
model_name: value.model,
|
||||
api_header: header,
|
||||
api_version: value.api_version,
|
||||
id: data.id,
|
||||
provider: value.provider as Exclude<typeof value.provider, 'Other'>,
|
||||
show_name: value.show_name,
|
||||
// 添加高级设置字段到 param 对象中
|
||||
param: {
|
||||
context_window: value.context_window_size,
|
||||
max_tokens: value.max_output_tokens,
|
||||
r1_enabled: value.enable_r1_params,
|
||||
support_images: value.support_image,
|
||||
support_computer_use: value.support_compute,
|
||||
support_prompt_cache: value.support_prompt_caching,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
message.success('修改成功');
|
||||
handleReset();
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
postCreateModel({
|
||||
model_type: type,
|
||||
api_key: value.api_key,
|
||||
api_base: value.base_url,
|
||||
model_name: value.model,
|
||||
api_header: header,
|
||||
provider: value.provider as Exclude<typeof value.provider, 'Other'>,
|
||||
show_name: value.show_name,
|
||||
// 添加高级设置字段到 param 对象中
|
||||
param: {
|
||||
context_window: value.context_window_size,
|
||||
max_tokens: value.max_output_tokens,
|
||||
r1_enabled: value.enable_r1_params,
|
||||
support_images: value.support_image,
|
||||
support_computer_use: value.support_compute,
|
||||
support_prompt_cache: value.support_prompt_caching,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
message.success('添加成功');
|
||||
handleReset();
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const resetCurData = (value: DomainModel) => {
|
||||
// @ts-ignore
|
||||
if (value.provider && value.provider !== 'Other') {
|
||||
getModel({
|
||||
api_key: value.api_key || '',
|
||||
base_url: value.api_base || '',
|
||||
model: value.model_name || '',
|
||||
provider: value.provider,
|
||||
api_version: value.api_version || '',
|
||||
api_header_key: value.api_header?.split('=')[0] || '',
|
||||
api_header_value: value.api_header?.split('=')[1] || '',
|
||||
type,
|
||||
show_name: value.show_name || '',
|
||||
context_window_size: 64000,
|
||||
max_output_tokens: 8192,
|
||||
enable_r1_params: false,
|
||||
support_image: false,
|
||||
support_compute: false,
|
||||
support_prompt_caching: false
|
||||
});
|
||||
}
|
||||
reset({
|
||||
type,
|
||||
provider: value.provider || 'Other',
|
||||
model: value.model_name || '',
|
||||
base_url: value.api_base || '',
|
||||
api_key: value.api_key || '',
|
||||
api_version: value.api_version || '',
|
||||
api_header_key: value.api_header?.split('=')[0] || '',
|
||||
api_header_value: value.api_header?.split('=')[1] || '',
|
||||
show_name: value.show_name || '',
|
||||
context_window_size: value.param?.context_window || 64000,
|
||||
max_output_tokens: value.param?.max_tokens || 8192,
|
||||
enable_r1_params: value.param?.r1_enabled || false,
|
||||
support_image: value.param?.support_images || false,
|
||||
support_compute: value.param?.support_computer_use || false,
|
||||
support_prompt_caching: value.param?.support_prompt_cache || false
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (data) {
|
||||
console.log(data);
|
||||
resetCurData(data);
|
||||
} else {
|
||||
reset({
|
||||
type,
|
||||
provider: 'BaiZhiCloud',
|
||||
model: '',
|
||||
base_url: providers['BaiZhiCloud'].defaultBaseUrl,
|
||||
api_key: '',
|
||||
api_version: '',
|
||||
api_header_key: '',
|
||||
api_header_value: '',
|
||||
show_name: '',
|
||||
// 高级设置默认值
|
||||
context_window_size: 64000,
|
||||
max_output_tokens: 8192,
|
||||
enable_r1_params: false,
|
||||
support_image: false,
|
||||
support_compute: false,
|
||||
support_prompt_caching: false,
|
||||
});
|
||||
}
|
||||
// 确保每次打开时高级设置都是折叠的
|
||||
setExpandAdvanced(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data, open]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={data ? `修改${titleMap[type]}` : `添加${titleMap[type]}`}
|
||||
open={open}
|
||||
width={800}
|
||||
onCancel={handleReset}
|
||||
okText='保存'
|
||||
onOk={handleSubmit(onSubmit)}
|
||||
okButtonProps={{
|
||||
loading,
|
||||
disabled: !success && providerBrand !== 'Other',
|
||||
}}
|
||||
>
|
||||
<Stack direction={'row'} alignItems={'stretch'} gap={3}>
|
||||
<Stack
|
||||
gap={1}
|
||||
sx={{
|
||||
width: 200,
|
||||
flexShrink: 0,
|
||||
bgcolor: 'background.paper2',
|
||||
borderRadius: '10px',
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ fontSize: 14, lineHeight: '24px', fontWeight: 'bold', p: 1 }}
|
||||
>
|
||||
模型供应商
|
||||
</Box>
|
||||
{Object.values(providers).map((it) => (
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'center'}
|
||||
gap={1.5}
|
||||
key={it.label}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
fontSize: 14,
|
||||
lineHeight: '24px',
|
||||
p: 1,
|
||||
borderRadius: '10px',
|
||||
fontWeight: 'bold',
|
||||
fontFamily: 'Gbold',
|
||||
...(providerBrand === it.label && {
|
||||
bgcolor: addOpacityToColor(theme.palette.primary.main, 0.1),
|
||||
color: 'primary.main',
|
||||
}),
|
||||
'&:hover': {
|
||||
color: 'primary.main',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
if (data && data.provider === it.label) {
|
||||
resetCurData(data);
|
||||
} else {
|
||||
setModelUserList([]);
|
||||
setError('');
|
||||
setModelLoading(false);
|
||||
setSuccess(false);
|
||||
reset({
|
||||
provider: it.label as keyof typeof ModelProvider,
|
||||
base_url:
|
||||
it.label === 'AzureOpenAI' ? '' : it.defaultBaseUrl,
|
||||
model: '',
|
||||
api_version: '',
|
||||
api_key: '',
|
||||
api_header_key: '',
|
||||
api_header_value: '',
|
||||
show_name: '',
|
||||
// 重置高级设置
|
||||
context_window_size: 64000,
|
||||
max_output_tokens: 8192,
|
||||
enable_r1_params: false,
|
||||
support_image: false,
|
||||
support_compute: false,
|
||||
support_prompt_caching: false,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon type={it.icon} sx={{ fontSize: 18 }} />
|
||||
{it.cn || it.label || '其他'}
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '32px' }}>
|
||||
API 地址{' '}
|
||||
<Box component={'span'} sx={{ color: 'red' }}>
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name='base_url'
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: 'URL 不能为空',
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
disabled={!providers[providerBrand].urlWrite}
|
||||
size='small'
|
||||
placeholder={providers[providerBrand].defaultBaseUrl}
|
||||
error={!!errors.base_url}
|
||||
helperText={errors.base_url?.message}
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
setModelUserList([]);
|
||||
setValue('model', '');
|
||||
setSuccess(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
sx={{ fontSize: 14, lineHeight: '32px', mt: 2 }}
|
||||
>
|
||||
<Box>
|
||||
API Secret
|
||||
{providers[providerBrand].secretRequired && (
|
||||
<Box component={'span'} sx={{ color: 'red' }}>
|
||||
{' '}
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{providers[providerBrand].modelDocumentUrl && (
|
||||
<Box
|
||||
component={'span'}
|
||||
sx={{
|
||||
color: 'info.main',
|
||||
cursor: 'pointer',
|
||||
ml: 1,
|
||||
textAlign: 'right',
|
||||
}}
|
||||
onClick={() =>
|
||||
window.open(
|
||||
providers[providerBrand].modelDocumentUrl,
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
>
|
||||
查看文档
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
<Controller
|
||||
control={control}
|
||||
name='api_key'
|
||||
rules={{
|
||||
required: {
|
||||
value: providers[providerBrand].secretRequired,
|
||||
message: 'API Secret 不能为空',
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
size='small'
|
||||
placeholder=''
|
||||
error={!!errors.api_key}
|
||||
helperText={errors.api_key?.message}
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
setModelUserList([]);
|
||||
setValue('model', '');
|
||||
setSuccess(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{(modelUserList.length !== 0 || providerBrand === 'Other') && (
|
||||
<>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '32px', mt: 2 }}>
|
||||
模型备注
|
||||
<Box component={'span'} sx={{ color: 'red' }}>
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name='show_name'
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: '模型备注不能为空',
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
size='small'
|
||||
placeholder=''
|
||||
error={!!errors.show_name}
|
||||
helperText={errors.show_name?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{providerBrand === 'AzureOpenAI' && (
|
||||
<>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '32px', mt: 2 }}>
|
||||
API Version
|
||||
</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name='api_version'
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
size='small'
|
||||
placeholder='2024-10-21'
|
||||
error={!!errors.api_version}
|
||||
helperText={errors.api_version?.message}
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
setModelUserList([]);
|
||||
setValue('model', '');
|
||||
setSuccess(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{providerBrand === 'Other' ? (
|
||||
<>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '32px', mt: 2 }}>
|
||||
模型名称{' '}
|
||||
<Box component={'span'} sx={{ color: 'red' }}>
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name='model'
|
||||
rules={{
|
||||
required: '模型名称不能为空',
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
size='small'
|
||||
placeholder=''
|
||||
error={!!errors.model}
|
||||
helperText={errors.model?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Box sx={{ fontSize: 12, color: 'error.main', mt: 1 }}>
|
||||
需要与模型供应商提供的名称完全一致,不要随便填写
|
||||
</Box>
|
||||
</>
|
||||
) : modelUserList.length === 0 ? (
|
||||
<Button
|
||||
fullWidth
|
||||
variant='outlined'
|
||||
loading={modelLoading}
|
||||
sx={{ mt: 4 }}
|
||||
onClick={handleSubmit(getModel)}
|
||||
>
|
||||
获取模型列表
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '32px', mt: 2 }}>
|
||||
模型名称{' '}
|
||||
<Box component={'span'} sx={{ color: 'red' }}>
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name='model'
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
select
|
||||
size='small'
|
||||
placeholder=''
|
||||
error={!!errors.model}
|
||||
helperText={errors.model?.message}
|
||||
>
|
||||
{modelUserList.map((it) => (
|
||||
<MenuItem key={it.model} value={it.model}>
|
||||
{it.model}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
)}
|
||||
/>
|
||||
{providers[providerBrand].customHeader && (
|
||||
<>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '32px', mt: 2 }}>
|
||||
Header
|
||||
</Box>
|
||||
<Stack direction={'row'} gap={1}>
|
||||
<Controller
|
||||
control={control}
|
||||
name='api_header_key'
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
size='small'
|
||||
placeholder='key'
|
||||
error={!!errors.api_header_key}
|
||||
helperText={errors.api_header_key?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '36px' }}>=</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name='api_header_value'
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
size='small'
|
||||
placeholder='value'
|
||||
error={!!errors.api_header_value}
|
||||
helperText={errors.api_header_value?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 高级设置部分 */}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Accordion
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
bgcolor: 'transparent',
|
||||
'&:before': {
|
||||
display: 'none',
|
||||
},
|
||||
'& .MuiAccordionSummary-root': {
|
||||
padding: 0,
|
||||
minHeight: 'auto',
|
||||
'& .MuiAccordionSummary-content': {
|
||||
margin: 0,
|
||||
},
|
||||
},
|
||||
'& .MuiAccordionDetails-root': {
|
||||
padding: 0,
|
||||
paddingTop: 1.5,
|
||||
},
|
||||
}}
|
||||
expanded={expandAdvanced}
|
||||
onChange={() => setExpandAdvanced(!expandAdvanced)}
|
||||
>
|
||||
<AccordionSummary
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
lineHeight: '32px',
|
||||
color: 'blue',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
opacity: 0.8,
|
||||
},
|
||||
}}
|
||||
>
|
||||
高级设置
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Stack spacing={2}>
|
||||
<Box>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '32px' }}>
|
||||
上下文窗口大小
|
||||
</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name='context_window_size'
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
size='small'
|
||||
placeholder='例如:16000'
|
||||
type='number'
|
||||
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||
/>
|
||||
<Box sx={{ mt: 1, display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||
{[
|
||||
{ label: '128k', value: 128000 },
|
||||
{ label: '256k', value: 256000 },
|
||||
{ label: '512k', value: 512000 },
|
||||
{ label: '1m', value: 1_000_000 }
|
||||
].map((option) => (
|
||||
<Box
|
||||
key={option.label}
|
||||
sx={{
|
||||
fontSize: 12,
|
||||
color: 'blue',
|
||||
cursor: 'pointer',
|
||||
padding: '2px 4px',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline'
|
||||
}
|
||||
}}
|
||||
onClick={() => field.onChange(option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Box sx={{ fontSize: 14, lineHeight: '32px' }}>
|
||||
最大输出 Token
|
||||
</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name='max_output_tokens'
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
size='small'
|
||||
placeholder='例如:4000'
|
||||
type='number'
|
||||
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 复选框组 - 使用更紧凑的布局 */}
|
||||
<Stack spacing={0}>
|
||||
<Controller
|
||||
control={control}
|
||||
name='enable_r1_params'
|
||||
render={({ field }) => (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onChange={(e) => field.onChange(e.target.checked)}
|
||||
size='small'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ fontSize: 12 }}>
|
||||
启用 R1 模型参数
|
||||
<Box component="span" sx={{ ml: 1, color: 'text.secondary', fontSize: 11 }}>
|
||||
(使用 QWQ 等 R1 系列模型时必须启用,避免出现 400 错误)
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
sx={{ margin: 0 }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{error && (
|
||||
<Card
|
||||
sx={{
|
||||
color: 'error.main',
|
||||
mt: 2,
|
||||
fontSize: 12,
|
||||
p: 2,
|
||||
bgcolor: 'background.paper2',
|
||||
border: '1px solid',
|
||||
borderColor: 'error.main',
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</Card>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelAdd;
|
||||
172
ui/src/pages/model/components/services/modelService.ts
Normal file
172
ui/src/pages/model/components/services/modelService.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import type {
|
||||
ModelService as IModelService,
|
||||
CreateModelReq as UICreateModelData,
|
||||
UpdateModelReq as UIUpdateModelData,
|
||||
CheckModelReq as UICheckModelData,
|
||||
ListModelReq as UIGetModelNameData,
|
||||
Model,
|
||||
ModelListItem as UIModelListItem,
|
||||
} from '@yokowu/modelkit-ui';
|
||||
import {
|
||||
DomainCreateModelReq as LocalCreateModelData,
|
||||
DomainUpdateModelReq as LocalUpdateModelData,
|
||||
DomainCheckModelReq as LocalCheckModelData,
|
||||
GetGetProviderModelListParams as LocalGetModelNameData,
|
||||
ConstsModelType,
|
||||
ConstsModelStatus,
|
||||
ConstsModelProvider,
|
||||
DomainModel,
|
||||
} from '@/api/types';
|
||||
import { ModelProvider } from '../../constant';
|
||||
import { getGetProviderModelList, postCheckModel, postCreateModel, putUpdateModel } from '@/api/Model';
|
||||
|
||||
const localModelToModelKitModel = (
|
||||
localModel: DomainModel,
|
||||
): Model => {
|
||||
return {
|
||||
id: localModel.id || '',
|
||||
model_name: localModel.model_name || '',
|
||||
provider: localModel.provider as ConstsModelProvider,
|
||||
model_type: localModel.model_type as ConstsModelType,
|
||||
base_url: localModel.api_base || '',
|
||||
api_key: localModel.api_key || '',
|
||||
api_header: localModel.api_header || '',
|
||||
api_version: localModel.api_version || '',
|
||||
param: localModel.param || undefined,
|
||||
show_name: localModel.show_name || '',
|
||||
status: localModel.status as ConstsModelStatus | undefined,
|
||||
created_at: localModel.created_at || 0,
|
||||
updated_at: localModel.updated_at || 0,
|
||||
input: localModel.input || 0,
|
||||
output: localModel.output || 0,
|
||||
is_active: localModel.is_active || false,
|
||||
is_internal: localModel.is_internal || false,
|
||||
};
|
||||
};
|
||||
|
||||
const modelkitModelTypeToLocal = (
|
||||
modelType: string,
|
||||
): ConstsModelType => {
|
||||
if (modelType === 'coder') return ConstsModelType.ModelTypeCoder;
|
||||
if (modelType === 'code') return ConstsModelType.ModelTypeCoder;
|
||||
return ConstsModelType.ModelTypeLLM;
|
||||
};
|
||||
|
||||
const modelkitModelTypeToLocalString = (
|
||||
modelType: string,
|
||||
): "llm" | "coder" | "embedding" | "rerank" => {
|
||||
if (modelType === 'coder') return 'coder';
|
||||
if (modelType === 'code') return 'coder';
|
||||
if (modelType === 'embedding') return 'embedding';
|
||||
if (modelType === 'reranker') return 'rerank';
|
||||
if (modelType === 'rerank') return 'rerank';
|
||||
|
||||
return 'llm';
|
||||
};
|
||||
|
||||
// 转换 UI 创建模型数据为本地创建模型数据
|
||||
const convertUICreateToLocalCreate = (
|
||||
uiModel: UICreateModelData,
|
||||
): LocalCreateModelData => {
|
||||
return {
|
||||
model_name: uiModel.model_name || '',
|
||||
provider: uiModel.provider as keyof typeof ModelProvider,
|
||||
model_type: modelkitModelTypeToLocal(uiModel.model_type || ''),
|
||||
api_base: uiModel.base_url || '',
|
||||
api_key: uiModel.api_key || '',
|
||||
api_header: uiModel.api_header || '',
|
||||
param: uiModel.param || undefined,
|
||||
show_name: uiModel.show_name || '',
|
||||
};
|
||||
};
|
||||
|
||||
// 转换 UI 更新模型数据为本地更新模型数据
|
||||
const convertUIUpdateToLocalUpdate = (
|
||||
uiModel: UIUpdateModelData,
|
||||
): LocalUpdateModelData => {
|
||||
return {
|
||||
id: uiModel.id || '',
|
||||
model_name: uiModel.model_name || '',
|
||||
provider: uiModel.provider as keyof typeof ModelProvider,
|
||||
api_base: uiModel.base_url || '',
|
||||
api_key: uiModel.api_key || '',
|
||||
api_header: uiModel.api_header || '',
|
||||
api_version: uiModel.api_version || '',
|
||||
param: uiModel.param || undefined,
|
||||
show_name: uiModel.show_name || '',
|
||||
status: uiModel.status as ConstsModelStatus | undefined,
|
||||
};
|
||||
};
|
||||
|
||||
// 转换 UI 检查模型数据为本地检查模型数据
|
||||
const convertUICheckToLocalCheck = (
|
||||
uiCheck: UICheckModelData,
|
||||
): LocalCheckModelData => {
|
||||
return {
|
||||
model_name: uiCheck.model_name || '',
|
||||
provider: uiCheck.provider as ConstsModelProvider,
|
||||
type: modelkitModelTypeToLocalString(uiCheck.model_type || ''),
|
||||
api_base: uiCheck.base_url || '',
|
||||
api_key: uiCheck.api_key || '',
|
||||
api_header: uiCheck.api_header || '',
|
||||
api_version: uiCheck.api_version || '',
|
||||
};
|
||||
};
|
||||
|
||||
// 转换 UI 获取模型名称数据为本地获取模型名称数据
|
||||
const convertUIGetModelNameToLocal = (
|
||||
uiData: UIGetModelNameData,
|
||||
): LocalGetModelNameData => {
|
||||
return {
|
||||
provider: uiData.provider as ConstsModelProvider,
|
||||
type: modelkitModelTypeToLocal(uiData.model_type || ''),
|
||||
base_url: uiData.base_url || '',
|
||||
api_key: uiData.api_key || '',
|
||||
api_header: uiData.api_header || '',
|
||||
};
|
||||
};
|
||||
|
||||
// ModelService 实现
|
||||
export const modelService: IModelService = {
|
||||
async createModel(data: UICreateModelData) {
|
||||
const localData = convertUICreateToLocalCreate(data);
|
||||
const result = await postCreateModel(localData);
|
||||
|
||||
// 创建成功后返回模型数据
|
||||
const model: Model = result;
|
||||
|
||||
return { model };
|
||||
},
|
||||
|
||||
async listModel(data: UIGetModelNameData) {
|
||||
const localData = convertUIGetModelNameToLocal(data);
|
||||
const result = await getGetProviderModelList(localData);
|
||||
|
||||
const models: UIModelListItem[] = result.models?.map(item => ({
|
||||
model: item.model,
|
||||
})) || [];
|
||||
|
||||
return { models };
|
||||
},
|
||||
|
||||
async checkModel(data: UICheckModelData) {
|
||||
const localData = convertUICheckToLocalCheck(data);
|
||||
const result = await postCheckModel(localData);
|
||||
|
||||
const model: Model = result;
|
||||
const error: string = '';
|
||||
return { model, error };
|
||||
},
|
||||
|
||||
async updateModel(data: UIUpdateModelData) {
|
||||
const localData = convertUIUpdateToLocalUpdate(data);
|
||||
const res = await putUpdateModel(localData);
|
||||
|
||||
// 更新成功后返回模型数据
|
||||
const model: Model = res;
|
||||
|
||||
return { model };
|
||||
},
|
||||
};
|
||||
|
||||
export { modelkitModelTypeToLocal ,localModelToModelKitModel};
|
||||
Reference in New Issue
Block a user