From 369ecf365ab531f1b093a3c56298f914459ab047 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:10:38 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E8=AE=BE=E7=BD=AE=E5=8F=AF=E8=A7=86=E5=8C=96=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 5 + .../Operation/ModelSettingsVisualEditor.js | 251 ++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js 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) => ( + + + } + 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 }))} + /> + +
+ + ); +} From 8504e0724542fce45ae44a6e6bf592ccea12ccd1 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:29:43 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E6=95=B0=E6=8D=AE=E6=8F=90=E4=BA=A4=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Operation/ModelSettingsVisualEditor.js | 96 ++++++++++++------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js index b3ebb9e9..86ec2cd8 100644 --- a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js +++ b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js @@ -1,7 +1,7 @@ // 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 { IconDelete, IconPlus, IconSearch,IconSave } from '@douyinfe/semi-icons'; import { showError, showSuccess } from '../../../helpers'; import { API } from '../../../helpers'; export default function ModelSettingsVisualEditor(props) { @@ -10,6 +10,7 @@ export default function ModelSettingsVisualEditor(props) { const [currentModel, setCurrentModel] = useState(null); const [searchText, setSearchText] = useState(''); const [currentPage, setCurrentPage] = useState(1); + const [loading, setLoading] = useState(false); const pageSize = 10; useEffect(() => { @@ -53,49 +54,66 @@ export default function ModelSettingsVisualEditor(props) { // 然后基于过滤后的数据计算分页数据 const pagedData = getPagedData(filteredModels, currentPage, pageSize); - // 转换回JSON格式 - const generateJSONOutput = async () => { + 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); 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); + + // 准备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('部分保存失败,请重试'); } - }) - }) - - - showSuccess('转换成功'); - props.refresh(); + } + + // 检查每个请求的结果 + 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 = [ @@ -111,7 +129,7 @@ export default function ModelSettingsVisualEditor(props) { render: (text, record) => ( updateModel(record.name, 'price', value)} /> ) @@ -166,27 +184,33 @@ export default function ModelSettingsVisualEditor(props) { const deleteModel = (name) => { setModels(prev => prev.filter(model => model.name !== name)); }; - const addModel = (values) => { - setModels(prev => [...prev, { + // 检查模型名称是否存在, 如果存在则拒绝添加 + if (models.some(model => model.name === values.name)) { + showError('模型名称已存在'); + return; + } + setModels(prev => [{ name: values.name, price: values.price || '', ratio: values.ratio || '', completionRatio: values.completionRatio || '' - }]); + }, ...prev]); setVisible(false); + showSuccess('添加成功'); }; return ( <> +

模型价格

- } From 498590d9fde5c0f5afc37fcb92427ece34e4b535 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:42:02 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E8=AE=BE=E7=BD=AE=E5=8F=AF=E8=A7=86=E5=8C=96=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=EF=BC=8C=E5=A2=9E=E5=BC=BA=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E5=92=8C=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Operation/ModelSettingsVisualEditor.js | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js index 86ec2cd8..31541f52 100644 --- a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js +++ b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js @@ -1,7 +1,7 @@ // 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 { IconDelete, IconPlus, IconSearch, IconSave } from '@douyinfe/semi-icons'; import { showError, showSuccess } from '../../../helpers'; import { API } from '../../../helpers'; export default function ModelSettingsVisualEditor(props) { @@ -55,40 +55,44 @@ export default function ModelSettingsVisualEditor(props) { const pagedData = getPagedData(filteredModels, currentPage, pageSize); const SubmitData = async () => { - setLoading(true); + 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); - if (model.ratio !== '') output.ModelRatio[model.name] = parseFloat(model.ratio); - if (model.completionRatio != '') output.CompletionRatio[model.name] = parseFloat(model.completionRatio); + 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), + 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; @@ -97,17 +101,17 @@ export default function ModelSettingsVisualEditor(props) { 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('保存失败,请重试'); @@ -130,6 +134,7 @@ export default function ModelSettingsVisualEditor(props) { updateModel(record.name, 'price', value)} /> ) @@ -141,7 +146,9 @@ export default function ModelSettingsVisualEditor(props) { render: (text, record) => ( updateModel(record.name, 'ratio', value)} /> ) @@ -153,7 +160,8 @@ export default function ModelSettingsVisualEditor(props) { render: (text, record) => ( updateModel(record.name, 'completionRatio', value)} /> ) @@ -172,6 +180,10 @@ export default function ModelSettingsVisualEditor(props) { ]; const updateModel = (name, field, value) => { + if (isNaN(value)) { + showError('请输入数字'); + return; + } setModels(prev => prev.map(model => model.name === name @@ -203,7 +215,7 @@ export default function ModelSettingsVisualEditor(props) { return ( <> -

模型价格

+

模型价格