/*
Copyright (c) 2025 Tethys Plex
This file is part of Veloera.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
import React, { useEffect, useState, useRef } from 'react';
import {
Button,
Table,
Modal,
Form,
Input,
InputNumber,
Space,
Popconfirm,
Typography,
Banner,
Card,
Spin,
Tag,
} from '@douyinfe/semi-ui';
import { API, showError, showSuccess, showWarning } from '../../../helpers';
import { useTranslation } from 'react-i18next';
import {
IconPlus,
IconEdit,
IconDelete,
IconRefresh,
} from '@douyinfe/semi-icons';
const { Title, Text } = Typography;
export default function SettingModelMapping() {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [mappings, setMappings] = useState({});
const [modalVisible, setModalVisible] = useState(false);
const [editingMapping, setEditingMapping] = useState(null);
const [models, setModels] = useState([{ model: '', priorities: 0 }]);
const [virtualModelError, setVirtualModelError] = useState('');
const [modelErrors, setModelErrors] = useState([]);
const formApiRef = useRef();
// 获取模型映射配置
const fetchMappings = async () => {
try {
setLoading(true);
const res = await API.get('/api/model_mapping/');
if (res.data.success) {
setMappings(res.data.data.mapping || {});
} else {
showError(res.data.message);
}
} catch (error) {
showError('获取模型映射配置失败:' + error.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchMappings();
}, []);
// 当models变化时自动验证
useEffect(() => {
validateModels();
}, [models]);
// 实时验证虚拟模型名
const validateVirtualModel = (value) => {
const trimmedValue = value.trim();
if (!trimmedValue) {
setVirtualModelError('');
return;
}
if (!editingMapping && mappings[trimmedValue]) {
setVirtualModelError(`虚拟模型名 '${trimmedValue}' 已存在`);
} else if (editingMapping && editingMapping !== trimmedValue && mappings[trimmedValue]) {
setVirtualModelError(`虚拟模型名 '${trimmedValue}' 已存在`);
} else {
setVirtualModelError('');
}
};
// 实时验证实际模型名
const validateModels = () => {
const errors = [];
const modelNames = new Set();
const duplicates = new Set();
models.forEach((model, index) => {
const trimmedModel = model.model.trim();
if (trimmedModel) {
if (modelNames.has(trimmedModel)) {
duplicates.add(trimmedModel);
errors[index] = `模型名重复: ${trimmedModel}`;
} else {
modelNames.add(trimmedModel);
errors[index] = '';
}
} else {
errors[index] = '';
}
});
// 为重复的模型设置错误信息
models.forEach((model, index) => {
const trimmedModel = model.model.trim();
if (duplicates.has(trimmedModel) && !errors[index]) {
errors[index] = `模型名重复: ${trimmedModel}`;
}
});
setModelErrors(errors);
};
// 转换数据为表格格式
const getTableData = () => {
return Object.entries(mappings).map(([virtualModel, items]) => ({
key: virtualModel,
virtualModel,
models: items || [],
}));
};
// 添加/编辑映射
const handleSaveMapping = async () => {
try {
const values = await formApiRef.current.validate();
const { virtualModel } = values;
// 处理模型列表数据
const processedModels = models
.map((model) => ({
model: model.model.trim(),
priorities: parseInt(model.priorities) || 0,
}))
.filter((model) => model.model); // 过滤空模型
// 前端验证:检查虚拟模型名是否重复
const trimmedVirtualModel = virtualModel.trim();
if (!editingMapping && mappings[trimmedVirtualModel]) {
showError(`虚拟模型名 '${trimmedVirtualModel}' 已存在,请使用不同的名称`);
return;
}
// 如果是编辑模式但虚拟模型名已存在且不是当前编辑的项目
if (editingMapping && editingMapping !== trimmedVirtualModel && mappings[trimmedVirtualModel]) {
showError(`虚拟模型名 '${trimmedVirtualModel}' 已存在,请使用不同的名称`);
return;
}
// 前端验证:检查同一虚拟模型内实际模型名是否重复
const modelNames = new Set();
const duplicateModels = [];
for (let i = 0; i < processedModels.length; i++) {
const modelName = processedModels[i].model;
if (modelNames.has(modelName)) {
duplicateModels.push(modelName);
} else {
modelNames.add(modelName);
}
}
if (duplicateModels.length > 0) {
showError(`实际模型名重复: ${duplicateModels.join(', ')}`);
return;
}
// 检查是否有空的实际模型名
if (processedModels.length === 0) {
showError('至少需要配置一个实际模型');
return;
}
const newMappings = { ...mappings };
// 如果是编辑模式且虚拟模型名发生了变化,需要删除原来的映射
if (editingMapping && editingMapping !== trimmedVirtualModel) {
delete newMappings[editingMapping];
}
newMappings[trimmedVirtualModel] = processedModels;
const res = await API.put('/api/model_mapping/', {
mapping: newMappings,
});
if (res.data.success) {
showSuccess(res.data.message || '保存成功');
setMappings(newMappings);
setModalVisible(false);
if (formApiRef.current) {
formApiRef.current.reset();
}
setEditingMapping(null);
setModels([{ model: '', priorities: 0 }]);
setVirtualModelError('');
setModelErrors([]);
} else {
showError(res.data.message);
}
} catch (error) {
if (error.errorFields) {
showWarning('请检查表单输入');
} else {
showError('保存失败:' + error.message);
}
}
};
// 删除映射
const handleDeleteMapping = async (virtualModel) => {
try {
const newMappings = { ...mappings };
delete newMappings[virtualModel];
const res = await API.put('/api/model_mapping/', {
mapping: newMappings,
});
if (res.data.success) {
showSuccess('删除成功');
setMappings(newMappings);
} else {
showError(res.data.message);
}
} catch (error) {
showError('删除失败:' + error.message);
}
};
// 重新加载配置
const handleReload = async () => {
try {
const res = await API.post('/api/model_mapping/reload');
if (res.data.success) {
showSuccess(res.data.message);
await fetchMappings();
} else {
showError(res.data.message);
}
} catch (error) {
showError('重新加载失败:' + error.message);
}
};
// 打开编辑模态框
const openEditModal = (virtualModel = null) => {
setEditingMapping(virtualModel);
setModalVisible(true);
setVirtualModelError('');
setModelErrors([]);
// 延迟设置表单值,确保模态框已渲染
setTimeout(() => {
if (formApiRef.current) {
if (virtualModel && mappings[virtualModel]) {
formApiRef.current.setValues({
virtualModel,
});
setModels(mappings[virtualModel] || [{ model: '', priorities: 0 }]);
} else {
formApiRef.current.reset();
setModels([{ model: '', priorities: 0 }]);
}
}
}, 100);
};
// 添加模型
const addModel = () => {
setModels([...models, { model: '', priorities: 0 }]);
};
// 删除模型
const removeModel = (index) => {
if (models.length > 1) {
const newModels = models.filter((_, i) => i !== index);
setModels(newModels);
}
};
// 更新模型
const updateModel = (index, field, value) => {
const newModels = [...models];
newModels[index][field] = value;
setModels(newModels);
};
// 复制虚拟模型名
const copyVirtualModel = (virtualModel) => {
navigator.clipboard
.writeText(virtualModel)
.then(() => {
showSuccess(`已复制: ${virtualModel}`);
})
.catch(() => {
// 备用方法
const textarea = document.createElement('textarea');
textarea.value = virtualModel;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
showSuccess(`已复制: ${virtualModel}`);
});
};
const columns = [
{
title: '虚拟模型名',
dataIndex: 'virtualModel',
key: 'virtualModel',
width: 200,
render: (virtualModel) => (
copyVirtualModel(virtualModel)}
onMouseEnter={(e) => {
e.target.style.transform = 'scale(1.02)';
e.target.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.target.style.transform = 'scale(1)';
e.target.style.boxShadow = 'none';
}}
title='点击复制模型名'
>
{virtualModel}
),
},
{
title: '实际模型映射',
dataIndex: 'models',
key: 'models',
render: (models) => {
// 找到最高优先级的值
const maxPriority = Math.max(...models.map(model => model.priorities));
return (
{models.map((model, index) => {
// 判断是否为最高优先级
const isHighestPriority = model.priorities === maxPriority;
return (
= 8
? 'blue'
: model.priorities >= 5
? 'orange'
: 'grey'
}
style={
isHighestPriority
? { fontWeight: 'bold' }
: {}
}
>
{model.model} (优先级: {model.priorities})
);
})}
);
},
},
{
title: '操作',
key: 'action',
width: 150,
render: (_, record) => (
}
size='small'
onClick={() => openEditModal(record.virtualModel)}
>
编辑
handleDeleteMapping(record.virtualModel)}
position='leftTop'
>
} type='danger' size='small'>
删除
),
},
];
return (
<>
}
onClick={() => openEditModal()}
>
添加映射
} onClick={handleReload}>
重新加载
暂无模型映射配置
}
/>
{/* 添加/编辑模态框 */}
{
setModalVisible(false);
setEditingMapping(null);
setModels([{ model: '', priorities: 0 }]);
setVirtualModelError('');
setModelErrors([]);
if (formApiRef.current) {
formApiRef.current.reset();
}
}}
width={600}
>
实际模型列表:
{models.map((model, index) => (
))}
}
size='small'
style={{ marginTop: 8 }}
>
添加模型
>
);
}