Summary • Added new Ratio tab in Settings for managing all ratio-related configurations (group & model multipliers). • Created `RatioSetting` component to host GroupRatio, ModelRatio, Visual Editor and Unset-Models panels. • Moved ratio components to `web/src/pages/Setting/Ratio/` directory: – `GroupRatioSettings.js` – `ModelRatioSettings.js` – `ModelSettingsVisualEditor.js` – `ModelRationNotSetEditor.js` • Updated imports in `RatioSetting.js` to use the new path. • Updated main Settings router (`web/src/pages/Setting/index.js`) to include the new “Ratio Settings” tab. • Pruned `OperationSetting.js`: – Removed ratio-specific cards, tabs and unused imports. – Reduced state to only the keys required by its child components. – Deleted obsolete fields (`StreamCacheQueueLength`, `CheckSensitiveOnCompletionEnabled`, `StopOnSensitiveEnabled`). • Added boolean handling simplification in `OperationSetting.js`. • Adjusted helper import list and removed unused translation hook. Why Separating ratio-related settings improves UX clarity, reduces cognitive load in the Operation Settings panel and keeps the codebase modular and easier to maintain. BREAKING CHANGE The file paths for ratio components have changed. Any external imports referencing the old `Operation` directory must update to the new `Ratio` path.
226 lines
6.9 KiB
JavaScript
226 lines
6.9 KiB
JavaScript
import React, { useEffect, useState, useRef } from 'react';
|
||
import {
|
||
Button,
|
||
Col,
|
||
Form,
|
||
Popconfirm,
|
||
Row,
|
||
Space,
|
||
Spin,
|
||
} from '@douyinfe/semi-ui';
|
||
import {
|
||
compareObjects,
|
||
API,
|
||
showError,
|
||
showSuccess,
|
||
showWarning,
|
||
verifyJSON,
|
||
} from '../../../helpers';
|
||
import { useTranslation } from 'react-i18next';
|
||
|
||
export default function ModelRatioSettings(props) {
|
||
const [loading, setLoading] = useState(false);
|
||
const [inputs, setInputs] = useState({
|
||
ModelPrice: '',
|
||
ModelRatio: '',
|
||
CacheRatio: '',
|
||
CompletionRatio: '',
|
||
});
|
||
const refForm = useRef();
|
||
const [inputsRow, setInputsRow] = useState(inputs);
|
||
const { t } = useTranslation();
|
||
|
||
async function onSubmit() {
|
||
try {
|
||
await refForm.current
|
||
.validate()
|
||
.then(() => {
|
||
const updateArray = compareObjects(inputs, inputsRow);
|
||
if (!updateArray.length)
|
||
return showWarning(t('你似乎并没有修改什么'));
|
||
|
||
const requestQueue = updateArray.map((item) => {
|
||
const value =
|
||
typeof inputs[item.key] === 'boolean'
|
||
? String(inputs[item.key])
|
||
: inputs[item.key];
|
||
return API.put('/api/option/', { key: item.key, value });
|
||
});
|
||
|
||
setLoading(true);
|
||
Promise.all(requestQueue)
|
||
.then((res) => {
|
||
if (res.includes(undefined)) {
|
||
return showError(
|
||
requestQueue.length > 1
|
||
? t('部分保存失败,请重试')
|
||
: t('保存失败'),
|
||
);
|
||
}
|
||
|
||
for (let i = 0; i < res.length; i++) {
|
||
if (!res[i].data.success) {
|
||
return showError(res[i].data.message);
|
||
}
|
||
}
|
||
|
||
showSuccess(t('保存成功'));
|
||
props.refresh();
|
||
})
|
||
.catch((error) => {
|
||
console.error('Unexpected error:', error);
|
||
showError(t('保存失败,请重试'));
|
||
})
|
||
.finally(() => {
|
||
setLoading(false);
|
||
});
|
||
})
|
||
.catch(() => {
|
||
showError(t('请检查输入'));
|
||
});
|
||
} catch (error) {
|
||
showError(t('请检查输入'));
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
async function resetModelRatio() {
|
||
try {
|
||
let res = await API.post(`/api/option/rest_model_ratio`);
|
||
if (res.data.success) {
|
||
showSuccess(res.data.message);
|
||
props.refresh();
|
||
} else {
|
||
showError(res.data.message);
|
||
}
|
||
} catch (error) {
|
||
showError(error);
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
const currentInputs = {};
|
||
for (let key in props.options) {
|
||
if (Object.keys(inputs).includes(key)) {
|
||
currentInputs[key] = props.options[key];
|
||
}
|
||
}
|
||
setInputs(currentInputs);
|
||
setInputsRow(structuredClone(currentInputs));
|
||
refForm.current.setValues(currentInputs);
|
||
}, [props.options]);
|
||
|
||
return (
|
||
<Spin spinning={loading}>
|
||
<Form
|
||
values={inputs}
|
||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||
style={{ marginBottom: 15 }}
|
||
>
|
||
<Form.Section>
|
||
<Row gutter={16}>
|
||
<Col xs={24} sm={16}>
|
||
<Form.TextArea
|
||
label={t('模型固定价格')}
|
||
extraText={t('一次调用消耗多少刀,优先级大于模型倍率')}
|
||
placeholder={t(
|
||
'为一个 JSON 文本,键为模型名称,值为一次调用消耗多少刀,比如 "gpt-4-gizmo-*": 0.1,一次消耗0.1刀',
|
||
)}
|
||
field={'ModelPrice'}
|
||
autosize={{ minRows: 6, maxRows: 12 }}
|
||
trigger='blur'
|
||
stopValidateWithError
|
||
rules={[
|
||
{
|
||
validator: (rule, value) => verifyJSON(value),
|
||
message: '不是合法的 JSON 字符串',
|
||
},
|
||
]}
|
||
onChange={(value) =>
|
||
setInputs({ ...inputs, ModelPrice: value })
|
||
}
|
||
/>
|
||
</Col>
|
||
</Row>
|
||
<Row gutter={16}>
|
||
<Col xs={24} sm={16}>
|
||
<Form.TextArea
|
||
label={t('模型倍率')}
|
||
placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率')}
|
||
field={'ModelRatio'}
|
||
autosize={{ minRows: 6, maxRows: 12 }}
|
||
trigger='blur'
|
||
stopValidateWithError
|
||
rules={[
|
||
{
|
||
validator: (rule, value) => verifyJSON(value),
|
||
message: '不是合法的 JSON 字符串',
|
||
},
|
||
]}
|
||
onChange={(value) =>
|
||
setInputs({ ...inputs, ModelRatio: value })
|
||
}
|
||
/>
|
||
</Col>
|
||
</Row>
|
||
<Row gutter={16}>
|
||
<Col xs={24} sm={16}>
|
||
<Form.TextArea
|
||
label={t('提示缓存倍率')}
|
||
placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率')}
|
||
field={'CacheRatio'}
|
||
autosize={{ minRows: 6, maxRows: 12 }}
|
||
trigger='blur'
|
||
stopValidateWithError
|
||
rules={[
|
||
{
|
||
validator: (rule, value) => verifyJSON(value),
|
||
message: '不是合法的 JSON 字符串',
|
||
},
|
||
]}
|
||
onChange={(value) =>
|
||
setInputs({ ...inputs, CacheRatio: value })
|
||
}
|
||
/>
|
||
</Col>
|
||
</Row>
|
||
<Row gutter={16}>
|
||
<Col xs={24} sm={16}>
|
||
<Form.TextArea
|
||
label={t('模型补全倍率(仅对自定义模型有效)')}
|
||
extraText={t('仅对自定义模型有效')}
|
||
placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率')}
|
||
field={'CompletionRatio'}
|
||
autosize={{ minRows: 6, maxRows: 12 }}
|
||
trigger='blur'
|
||
stopValidateWithError
|
||
rules={[
|
||
{
|
||
validator: (rule, value) => verifyJSON(value),
|
||
message: '不是合法的 JSON 字符串',
|
||
},
|
||
]}
|
||
onChange={(value) =>
|
||
setInputs({ ...inputs, CompletionRatio: value })
|
||
}
|
||
/>
|
||
</Col>
|
||
</Row>
|
||
</Form.Section>
|
||
</Form>
|
||
<Space>
|
||
<Button onClick={onSubmit}>{t('保存模型倍率设置')}</Button>
|
||
<Popconfirm
|
||
title={t('确定重置模型倍率吗?')}
|
||
content={t('此修改将不可逆')}
|
||
okType={'danger'}
|
||
position={'top'}
|
||
onConfirm={resetModelRatio}
|
||
>
|
||
<Button type={'danger'}>{t('重置模型倍率')}</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
</Spin>
|
||
);
|
||
}
|