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..b3ebb9e9
--- /dev/null
+++ b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js
@@ -0,0 +1,251 @@
+// ModelSettingsVisualEditor.js
+import React, { useEffect, useState } from 'react';
+import { Table, Button, Input, Modal, Form, Space } from '@douyinfe/semi-ui';
+import { IconDelete, IconPlus, IconSearch } 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 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);
+
+ // 转换回JSON格式
+ const generateJSONOutput = async () => {
+ const output = {
+ ModelPrice: {},
+ ModelRatio: {},
+ CompletionRatio: {}
+ };
+ let currentConvertModelName = '';
+ try {
+ models.forEach(model => {
+ currentConvertModelName = model.name;
+ if (model.price !== '') output.ModelPrice[model.name] = parseFloat(model.price);
+ if (model.ratio !== '') output.ModelRatio[model.name] = parseFloat(model.ratio);
+ if (model.completionRatio != '') output.CompletionRatio[model.name] = parseFloat(model.completionRatio);
+ });
+ } catch (error) {
+ console.error('JSON转换错误:', error);
+ showError('JSON转换错误, 请检查输入+模型名称: ' + currentConvertModelName);
+ return;
+ }
+
+ const finalOutput = {
+ ModelPrice: JSON.stringify(output.ModelPrice, null, 2),
+ ModelRatio: JSON.stringify(output.ModelRatio, null, 2),
+ CompletionRatio: JSON.stringify(output.CompletionRatio, null, 2)
+ }
+
+ forEach(finalOutput, (value, key) => {
+ API.put('/api/option/', {
+ key: key,
+ value
+ }).then(res => {
+ if (res.data.success) {
+ showSuccess('保存成功');
+ } else {
+ showError(res.data.message);
+ }
+ })
+ })
+
+
+ showSuccess('转换成功');
+ props.refresh();
+ };
+
+ 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) => {
+ 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) => {
+ setModels(prev => [...prev, {
+ name: values.name,
+ price: values.price || '',
+ ratio: values.ratio || '',
+ completionRatio: values.completionRatio || ''
+ }]);
+ setVisible(false);
+ };
+
+
+ return (
+ <>
+
+
+ } onClick={() => setVisible(true)}>
+ 添加模型
+
+
+ }
+ 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 }))}
+ />
+
+
+ >
+ );
+}