diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js
index 1c50c971..d7d69f71 100644
--- a/web/src/components/OperationSetting.js
+++ b/web/src/components/OperationSetting.js
@@ -8,6 +8,7 @@ import SettingsDataDashboard from '../pages/Setting/Operation/SettingsDataDashbo
import SettingsMonitoring from '../pages/Setting/Operation/SettingsMonitoring.js';
import SettingsCreditLimit from '../pages/Setting/Operation/SettingsCreditLimit.js';
import SettingsMagnification from '../pages/Setting/Operation/SettingsMagnification.js';
+import ModelSettingsVisualEditor from '../pages/Setting/Operation/ModelSettingsVisualEditor.js';
import { API, showError, showSuccess } from '../helpers';
import SettingsChats from '../pages/Setting/Operation/SettingsChats.js';
@@ -141,6 +142,10 @@ const OperationSetting = () => {
+ {/*可视化倍率设置*/}
+
+
+
>
);
diff --git a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js
new file mode 100644
index 00000000..6158b17e
--- /dev/null
+++ b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js
@@ -0,0 +1,296 @@
+// ModelSettingsVisualEditor.js
+import React, { useEffect, useState } from 'react';
+import { Table, Button, Input, Modal, Form, Space } from '@douyinfe/semi-ui';
+import { IconDelete, IconPlus, IconSearch, IconSave } from '@douyinfe/semi-icons';
+import { showError, showSuccess } from '../../../helpers';
+import { API } from '../../../helpers';
+export default function ModelSettingsVisualEditor(props) {
+ const [models, setModels] = useState([]);
+ const [visible, setVisible] = useState(false);
+ const [currentModel, setCurrentModel] = useState(null);
+ const [searchText, setSearchText] = useState('');
+ const [currentPage, setCurrentPage] = useState(1);
+ const [loading, setLoading] = useState(false);
+ const pageSize = 10;
+
+ useEffect(() => {
+ try {
+ const modelPrice = JSON.parse(props.options.ModelPrice || '{}');
+ const modelRatio = JSON.parse(props.options.ModelRatio || '{}');
+ const completionRatio = JSON.parse(props.options.CompletionRatio || '{}');
+
+ // 合并所有模型名称
+ const modelNames = new Set([
+ ...Object.keys(modelPrice),
+ ...Object.keys(modelRatio),
+ ...Object.keys(completionRatio)
+ ]);
+
+ const modelData = Array.from(modelNames).map(name => ({
+ name,
+ price: modelPrice[name] === undefined ? '' : modelPrice[name],
+ ratio: modelRatio[name] === undefined ? '' : modelRatio[name],
+ completionRatio: completionRatio[name] === undefined ? '' : completionRatio[name]
+ }));
+
+ setModels(modelData);
+ } catch (error) {
+ console.error('JSON解析错误:', error);
+ }
+ }, [props.options]);
+
+ // 首先声明分页相关的工具函数
+ const getPagedData = (data, currentPage, pageSize) => {
+ const start = (currentPage - 1) * pageSize;
+ const end = start + pageSize;
+ return data.slice(start, end);
+ };
+
+ // 在 return 语句之前,先处理过滤和分页逻辑
+ const filteredModels = models.filter(model =>
+ searchText ? model.name.toLowerCase().includes(searchText.toLowerCase()) : true
+ );
+
+ // 然后基于过滤后的数据计算分页数据
+ const pagedData = getPagedData(filteredModels, currentPage, pageSize);
+
+ const SubmitData = async () => {
+ setLoading(true);
+ const output = {
+ ModelPrice: {},
+ ModelRatio: {},
+ CompletionRatio: {}
+ };
+ let currentConvertModelName = '';
+
+ try {
+ // 数据转换
+ models.forEach(model => {
+ currentConvertModelName = model.name;
+ 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('部分保存失败,请重试');
+ }
+ }
+
+ // 检查每个请求的结果
+ for (const res of results) {
+ if (!res.data.success) {
+ return showError(res.data.message);
+ }
+ }
+
+ showSuccess('保存成功');
+ props.refresh();
+
+ } catch (error) {
+ console.error('保存失败:', error);
+ showError('保存失败,请重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const columns = [
+ {
+ title: '模型名称',
+ dataIndex: 'name',
+ key: 'name',
+ },
+ {
+ title: '固定价格',
+ dataIndex: 'price',
+ key: 'price',
+ render: (text, record) => (
+ updateModel(record.name, 'price', value)}
+ />
+ )
+ },
+ {
+ title: '模型倍率',
+ dataIndex: 'ratio',
+ key: 'ratio',
+ render: (text, record) => (
+ updateModel(record.name, 'ratio', value)}
+ />
+ )
+ },
+ {
+ title: '补全倍率',
+ dataIndex: 'completionRatio',
+ key: 'completionRatio',
+ render: (text, record) => (
+ updateModel(record.name, 'completionRatio', value)}
+ />
+ )
+ },
+ {
+ title: '操作',
+ key: 'action',
+ render: (_, record) => (
+ }
+ type="danger"
+ onClick={() => deleteModel(record.name)}
+ />
+ )
+ }
+ ];
+
+ const updateModel = (name, field, value) => {
+ if (isNaN(value)) {
+ showError('请输入数字');
+ return;
+ }
+ setModels(prev =>
+ prev.map(model =>
+ model.name === name
+ ? { ...model, [field]: value }
+ : model
+ )
+ );
+ };
+
+ const deleteModel = (name) => {
+ setModels(prev => prev.filter(model => model.name !== name));
+ };
+ const addModel = (values) => {
+ // 检查模型名称是否存在, 如果存在则拒绝添加
+ if (models.some(model => model.name === values.name)) {
+ showError('模型名称已存在');
+ return;
+ }
+ // 不允许同时添加固定价格和倍率
+ if (values.price !== '' && (values.ratio !== '' || values.completionRatio !== '')) {
+ showError('固定价格和倍率不能同时存在');
+ return;
+ }
+ setModels(prev => [{
+ name: values.name,
+ price: values.price || '',
+ ratio: values.ratio || '',
+ completionRatio: values.completionRatio || ''
+ }, ...prev]);
+ setVisible(false);
+ showSuccess('添加成功');
+ };
+
+
+ return (
+ <>
+
模型价格
+
+
+ } onClick={() => setVisible(true)}>
+ 添加模型
+
+ } onClick={SubmitData}>
+ 应用更改
+
+ }
+ placeholder="搜索模型名称"
+ value={searchText}
+ onChange={value => {
+ setSearchText(value)
+ // 搜索时重置页码
+ setCurrentPage(1);
+ }}
+ style={{ width: 200 }}
+ />
+
+ setCurrentPage(page),
+ showTotal: true,
+ showSizeChanger: false
+ }}
+ />
+
+
+ setVisible(false)}
+ onOk={() => {
+ currentModel && addModel(currentModel);
+ }}
+ >
+ setCurrentModel(prev => ({ ...prev, name: value }))}
+ />
+ setCurrentModel(prev => ({ ...prev, price: value }))}
+ />
+ setCurrentModel(prev => ({ ...prev, ratio: value }))}
+ />
+ setCurrentModel(prev => ({ ...prev, completionRatio: value }))}
+ />
+
+
+ >
+ );
+}