/* 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) => ( handleDeleteMapping(record.virtualModel)} position='leftTop' > ), }, ]; return ( <>
暂无模型映射配置 } /> {/* 添加/编辑模态框 */} { setModalVisible(false); setEditingMapping(null); setModels([{ model: '', priorities: 0 }]); setVirtualModelError(''); setModelErrors([]); if (formApiRef.current) { formApiRef.current.reset(); } }} width={600} >
(formApiRef.current = formAPI)} labelPosition='left' labelWidth={120} >
实际模型列表: {models.map((model, index) => (
updateModel(index, 'model', value)} /> {modelErrors[index] && ( {modelErrors[index]} )}
updateModel(index, 'priorities', value)} />
))}
); }