/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useEffect, useState } from 'react'; import { Table, Button, Input, Modal, Form, Space, Typography, Radio, Notification, } from '@douyinfe/semi-ui'; import { IconDelete, IconPlus, IconSearch, IconSave, IconBolt, } from '@douyinfe/semi-icons'; import { API, showError, showSuccess } from '../../../helpers'; import { useTranslation } from 'react-i18next'; export default function ModelRatioNotSetEditor(props) { const { t } = useTranslation(); const [models, setModels] = useState([]); const [visible, setVisible] = useState(false); const [batchVisible, setBatchVisible] = useState(false); const [currentModel, setCurrentModel] = useState(null); const [searchText, setSearchText] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [loading, setLoading] = useState(false); const [enabledModels, setEnabledModels] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [batchFillType, setBatchFillType] = useState('ratio'); const [batchFillValue, setBatchFillValue] = useState(''); const [batchRatioValue, setBatchRatioValue] = useState(''); const [batchCompletionRatioValue, setBatchCompletionRatioValue] = useState(''); const { Text } = Typography; // 定义可选的每页显示条数 const pageSizeOptions = [10, 20, 50, 100]; const getAllEnabledModels = async () => { try { const res = await API.get('/api/channel/models_enabled'); const { success, message, data } = res.data; if (success) { setEnabledModels(data); } else { showError(message); } } catch (error) { console.error(t('获取启用模型失败:'), error); showError(t('获取启用模型失败')); } }; useEffect(() => { // 获取所有启用的模型 getAllEnabledModels(); }, []); useEffect(() => { try { const modelPrice = JSON.parse(props.options.ModelPrice || '{}'); const modelRatio = JSON.parse(props.options.ModelRatio || '{}'); const completionRatio = JSON.parse(props.options.CompletionRatio || '{}'); // 找出所有未设置价格和倍率的模型 const unsetModels = enabledModels.filter((modelName) => { const hasPrice = modelPrice[modelName] !== undefined; const hasRatio = modelRatio[modelName] !== undefined; // 如果模型没有价格或者没有倍率设置,则显示 return !hasPrice && !hasRatio; }); // 创建模型数据 const modelData = unsetModels.map((name) => ({ name, price: modelPrice[name] || '', ratio: modelRatio[name] || '', completionRatio: completionRatio[name] || '', })); setModels(modelData); // 清空选择 setSelectedRowKeys([]); } catch (error) { console.error(t('JSON解析错误:'), error); } }, [props.options, enabledModels]); // 首先声明分页相关的工具函数 const getPagedData = (data, currentPage, pageSize) => { const start = (currentPage - 1) * pageSize; const end = start + pageSize; return data.slice(start, end); }; // 处理页面大小变化 const handlePageSizeChange = (size) => { setPageSize(size); // 重新计算当前页,避免数据丢失 const totalPages = Math.ceil(filteredModels.length / size); if (currentPage > totalPages) { setCurrentPage(totalPages || 1); } }; // 在 return 语句之前,先处理过滤和分页逻辑 const filteredModels = models.filter((model) => searchText ? model.name.includes(searchText) : true, ); // 然后基于过滤后的数据计算分页数据 const pagedData = getPagedData(filteredModels, currentPage, pageSize); const SubmitData = async () => { setLoading(true); const output = { ModelPrice: JSON.parse(props.options.ModelPrice || '{}'), ModelRatio: JSON.parse(props.options.ModelRatio || '{}'), CompletionRatio: JSON.parse(props.options.CompletionRatio || '{}'), }; try { // 数据转换 - 只处理已修改的模型 models.forEach((model) => { // 只有当用户设置了值时才更新 if (model.price !== '') { // 如果价格不为空,则转换为浮点数,忽略倍率参数 output.ModelPrice[model.name] = parseFloat(model.price); } else { if (model.ratio !== '') output.ModelRatio[model.name] = parseFloat(model.ratio); if (model.completionRatio !== '') output.CompletionRatio[model.name] = parseFloat( model.completionRatio, ); } }); // 准备API请求数组 const finalOutput = { ModelPrice: JSON.stringify(output.ModelPrice, null, 2), ModelRatio: JSON.stringify(output.ModelRatio, null, 2), CompletionRatio: JSON.stringify(output.CompletionRatio, null, 2), }; const requestQueue = Object.entries(finalOutput).map(([key, value]) => { return API.put('/api/option/', { key, value, }); }); // 批量处理请求 const results = await Promise.all(requestQueue); // 验证结果 if (requestQueue.length === 1) { if (results.includes(undefined)) return; } else if (requestQueue.length > 1) { if (results.includes(undefined)) { return showError(t('部分保存失败,请重试')); } } // 检查每个请求的结果 for (const res of results) { if (!res.data.success) { return showError(res.data.message); } } showSuccess(t('保存成功')); props.refresh(); // 重新获取未设置的模型 getAllEnabledModels(); } catch (error) { console.error(t('保存失败:'), error); showError(t('保存失败,请重试')); } finally { setLoading(false); } }; const columns = [ { title: t('模型名称'), dataIndex: 'name', key: 'name', }, { title: t('模型固定价格'), dataIndex: 'price', key: 'price', render: (text, record) => ( updateModel(record.name, 'price', value)} /> ), }, { title: t('模型倍率'), dataIndex: 'ratio', key: 'ratio', render: (text, record) => ( updateModel(record.name, 'ratio', value)} /> ), }, { title: t('补全倍率'), dataIndex: 'completionRatio', key: 'completionRatio', render: (text, record) => ( updateModel(record.name, 'completionRatio', value) } /> ), }, ]; const updateModel = (name, field, value) => { if (value !== '' && isNaN(value)) { showError(t('请输入数字')); return; } setModels((prev) => prev.map((model) => model.name === name ? { ...model, [field]: value } : model, ), ); }; const addModel = (values) => { // 检查模型名称是否存在, 如果存在则拒绝添加 if (models.some((model) => model.name === values.name)) { showError(t('模型名称已存在')); return; } setModels((prev) => [ { name: values.name, price: values.price || '', ratio: values.ratio || '', completionRatio: values.completionRatio || '', }, ...prev, ]); setVisible(false); showSuccess(t('添加成功')); }; // 批量填充功能 const handleBatchFill = () => { if (selectedRowKeys.length === 0) { showError(t('请先选择需要批量设置的模型')); return; } if (batchFillType === 'bothRatio') { if (batchRatioValue === '' || batchCompletionRatioValue === '') { showError(t('请输入模型倍率和补全倍率')); return; } if (isNaN(batchRatioValue) || isNaN(batchCompletionRatioValue)) { showError(t('请输入有效的数字')); return; } } else { if (batchFillValue === '') { showError(t('请输入填充值')); return; } if (isNaN(batchFillValue)) { showError(t('请输入有效的数字')); return; } } // 根据选择的类型批量更新模型 setModels((prev) => prev.map((model) => { if (selectedRowKeys.includes(model.name)) { if (batchFillType === 'price') { return { ...model, price: batchFillValue, ratio: '', completionRatio: '', }; } else if (batchFillType === 'ratio') { return { ...model, price: '', ratio: batchFillValue, }; } else if (batchFillType === 'completionRatio') { return { ...model, price: '', completionRatio: batchFillValue, }; } else if (batchFillType === 'bothRatio') { return { ...model, price: '', ratio: batchRatioValue, completionRatio: batchCompletionRatioValue, }; } } return model; }), ); setBatchVisible(false); Notification.success({ title: t('批量设置成功'), content: t('已为 {{count}} 个模型设置{{type}}', { count: selectedRowKeys.length, type: batchFillType === 'price' ? t('固定价格') : batchFillType === 'ratio' ? t('模型倍率') : batchFillType === 'completionRatio' ? t('补全倍率') : t('模型倍率和补全倍率'), }), duration: 3, }); }; const handleBatchTypeChange = (value) => { console.log(t('Changing batch type to:'), value); setBatchFillType(value); // 切换类型时清空对应的值 if (value !== 'bothRatio') { setBatchFillValue(''); } else { setBatchRatioValue(''); setBatchCompletionRatioValue(''); } }; const rowSelection = { selectedRowKeys, onChange: (selectedKeys) => { setSelectedRowKeys(selectedKeys); }, }; return ( <> } placeholder={t('搜索模型名称')} value={searchText} onChange={(value) => { setSearchText(value); setCurrentPage(1); }} style={{ width: 200 }} /> {t('此页面仅显示未设置价格或倍率的模型,设置后将自动从列表中移除')} setCurrentPage(page), onPageSizeChange: handlePageSizeChange, pageSizeOptions: pageSizeOptions, showTotal: true, showSizeChanger: true, }} empty={
{t('没有未设置的模型')}
} /> {/* 添加模型弹窗 */} setVisible(false)} onOk={() => { currentModel && addModel(currentModel); }} >
setCurrentModel((prev) => ({ ...prev, name: value })) } /> {t('定价模式')}: {currentModel?.priceMode ? t('固定价格') : t('倍率模式')} } onChange={(checked) => { setCurrentModel((prev) => ({ ...prev, price: '', ratio: '', completionRatio: '', priceMode: checked, })); }} /> {currentModel?.priceMode ? ( setCurrentModel((prev) => ({ ...prev, price: value })) } /> ) : ( <> setCurrentModel((prev) => ({ ...prev, ratio: value })) } /> setCurrentModel((prev) => ({ ...prev, completionRatio: value, })) } /> )}
{/* 批量设置弹窗 */} setBatchVisible(false)} onOk={handleBatchFill} width={500} >
handleBatchTypeChange('price')} > {t('固定价格')} handleBatchTypeChange('ratio')} > {t('模型倍率')} handleBatchTypeChange('completionRatio')} > {t('补全倍率')} handleBatchTypeChange('bothRatio')} > {t('模型倍率和补全倍率同时设置')}
{batchFillType === 'bothRatio' ? ( <> setBatchRatioValue(value)} /> setBatchCompletionRatioValue(value)} /> ) : ( setBatchFillValue(value)} /> )} {t('将为选中的 ')} {selectedRowKeys.length}{' '} {t(' 个模型设置相同的值')}
{t('当前设置类型: ')}{' '} {batchFillType === 'price' ? t('固定价格') : batchFillType === 'ratio' ? t('模型倍率') : batchFillType === 'completionRatio' ? t('补全倍率') : t('模型倍率和补全倍率')}
); }