Merge pull request #618 from HynoR/feat/modeledit

feat: 可视化模型定价编辑器
This commit is contained in:
Calcium-Ion
2024-12-14 21:32:05 +08:00
committed by GitHub
2 changed files with 301 additions and 0 deletions

View File

@@ -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 = () => {
<Card style={{ marginTop: '10px' }}>
<SettingsMagnification options={inputs} refresh={onRefresh} />
</Card>
{/*可视化倍率设置*/}
<Card style={{ marginTop: '10px' }}>
<ModelSettingsVisualEditor options={inputs} refresh={onRefresh} />
</Card>
</Spin>
</>
);

View File

@@ -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) => (
<Input
value={text}
placeholder="按量计价"
onChange={value => updateModel(record.name, 'price', value)}
/>
)
},
{
title: '模型倍率',
dataIndex: 'ratio',
key: 'ratio',
render: (text, record) => (
<Input
value={text}
placeholder={record.price !== '' ? '固定价格' : '默认补全倍率'}
disabled={record.price !== ''}
onChange={value => updateModel(record.name, 'ratio', value)}
/>
)
},
{
title: '补全倍率',
dataIndex: 'completionRatio',
key: 'completionRatio',
render: (text, record) => (
<Input
value={text}
placeholder={record.price !== '' ? '固定价格' : '默认补全倍率'}
disabled={record.price !== ''}
onChange={value => updateModel(record.name, 'completionRatio', value)}
/>
)
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Button
icon={<IconDelete />}
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 (
<>
<h3>模型价格</h3>
<Space vertical align="start" style={{ width: '100%' }}>
<Space>
<Button icon={<IconPlus />} onClick={() => setVisible(true)}>
添加模型
</Button>
<Button type="primary" icon={<IconSave />} onClick={SubmitData}>
应用更改
</Button>
<Input
prefix={<IconSearch />}
placeholder="搜索模型名称"
value={searchText}
onChange={value => {
setSearchText(value)
// 搜索时重置页码
setCurrentPage(1);
}}
style={{ width: 200 }}
/>
</Space>
<Table
columns={columns}
dataSource={pagedData} // 使用分页后的数据
pagination={{
currentPage: currentPage,
pageSize: pageSize,
total: filteredModels.length,
onPageChange: page => setCurrentPage(page),
showTotal: true,
showSizeChanger: false
}}
/>
</Space>
<Modal
title="添加模型"
visible={visible}
onCancel={() => setVisible(false)}
onOk={() => {
currentModel && addModel(currentModel);
}}
>
<Form>
<p>请输入固定价格或者模型倍率+补全倍率</p>
<Form.Input
field="name"
label="模型名称"
placeholder="strawberry"
required
onChange={value => setCurrentModel(prev => ({ ...prev, name: value }))}
/>
<Form.Input
field="price"
label="固定价格(每次)"
placeholder="输入每次价格"
onChange={value => setCurrentModel(prev => ({ ...prev, price: value }))}
/>
<Form.Input
field="ratio"
label="模型倍率"
placeholder="输入模型倍率"
onChange={value => setCurrentModel(prev => ({ ...prev, ratio: value }))}
/>
<Form.Input
field="completionRatio"
label="补全倍率"
placeholder="输入补全价格"
onChange={value => setCurrentModel(prev => ({ ...prev, completionRatio: value }))}
/>
</Form>
</Modal>
</>
);
}