Просмотр исходного кода

feat(ratio): 升级未设置倍率模型编辑弹窗,对齐可视化编辑器

- 将操作列「高级比例」按钮改为编辑图标按钮,打开完整编辑弹窗
- 编辑弹窗新增定价模式切换(按量计费/按次计费)
- 按量计费下支持按倍率设置和按价格设置两种子模式
- 按倍率:模型倍率、补全倍率
- 按价格:输入价格、输出价格($/1M tokens),自动计算倍率
- 高级比例区域:缓存读取、缓存创建、图片、音频输入、音频输出
- 修复 SubmitData 遗漏高级倍率字段保存的 bug(CacheRatio 等 5 个字段)
- 清理死代码(未调用的转换函数、残留 console.log)
master
fengsilin 1 неделю назад
Родитель
Сommit
1c15ee996d
1 измененных файлов: 308 добавлений и 61 удалений
  1. +308
    -61
      web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx

+ 308
- 61
web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx Просмотреть файл

@@ -26,6 +26,7 @@ import {
Form,
Space,
Typography,
RadioGroup,
Radio,
Notification,
} from '@douyinfe/semi-ui';
@@ -34,6 +35,7 @@ import {
IconPlus,
IconSearch,
IconSave,
IconEdit,
IconBolt,
} from '@douyinfe/semi-icons';
import { API, showError, showSuccess } from '../../../helpers';
@@ -60,6 +62,8 @@ export default function ModelRatioNotSetEditor(props) {
const editFormRef = useRef(null);
const [editVisible, setEditVisible] = useState(false);
const [editRecord, setEditRecord] = useState(null);
const [pricingMode, setPricingMode] = useState('per-token');
const [pricingSubMode, setPricingSubMode] = useState('ratio');
const { Text } = Typography;
// 定义可选的每页显示条数
const pageSizeOptions = [10, 20, 50, 100];
@@ -197,6 +201,11 @@ export default function ModelRatioNotSetEditor(props) {
ModelPrice: JSON.stringify(output.ModelPrice, null, 2),
ModelRatio: JSON.stringify(output.ModelRatio, null, 2),
CompletionRatio: JSON.stringify(output.CompletionRatio, null, 2),
CacheRatio: JSON.stringify(output.CacheRatio, null, 2),
CreateCacheRatio: JSON.stringify(output.CreateCacheRatio, null, 2),
ImageRatio: JSON.stringify(output.ImageRatio, null, 2),
AudioRatio: JSON.stringify(output.AudioRatio, null, 2),
AudioCompletionRatio: JSON.stringify(output.AudioCompletionRatio, null, 2),
};

const requestQueue = Object.entries(finalOutput).map(([key, value]) => {
@@ -288,15 +297,10 @@ export default function ModelRatioNotSetEditor(props) {
key: 'action',
render: (_, record) => (
<Button
size='small'
icon={<IconBolt />}
onClick={() => {
setEditRecord(record);
setEditVisible(true);
}}
>
{t('高级比例')}
</Button>
type='primary'
icon={<IconEdit />}
onClick={() => openEditModal(record)}
/>
),
},
];
@@ -431,7 +435,6 @@ export default function ModelRatioNotSetEditor(props) {
};

const handleBatchTypeChange = (value) => {
console.log(t('Changing batch type to:'), value);
setBatchFillType(value);

// 切换类型时清空对应的值
@@ -443,6 +446,60 @@ export default function ModelRatioNotSetEditor(props) {
}
};

const calculateTokenPriceFromRatio = (ratio) => {
return ratio * 2;
};

const openEditModal = (record) => {
const initialPricingMode = record.price !== '' ? 'per-request' : 'per-token';
const initialPricingSubMode = 'ratio';

setPricingMode(initialPricingMode);
setPricingSubMode(initialPricingSubMode);

const modelCopy = { ...record };

// 计算tokenPrice(从ratio反推)
if (record.ratio) {
modelCopy.tokenPrice = calculateTokenPriceFromRatio(
parseFloat(record.ratio),
).toString();

if (record.completionRatio) {
modelCopy.completionTokenPrice = (
parseFloat(modelCopy.tokenPrice) *
parseFloat(record.completionRatio)
).toString();
}
}

setEditRecord(modelCopy);
setEditVisible(true);

// 使用 setTimeout 确保表单渲染后再设置值
setTimeout(() => {
if (editFormRef.current) {
const formValues = {};

if (initialPricingMode === 'per-request') {
formValues.priceInput = modelCopy.price;
} else if (initialPricingMode === 'per-token') {
formValues.ratioInput = modelCopy.ratio;
formValues.completionRatioInput = modelCopy.completionRatio;
formValues.modelTokenPrice = modelCopy.tokenPrice;
formValues.completionTokenPrice = modelCopy.completionTokenPrice;
}
formValues.cacheRatio = modelCopy.cacheRatio;
formValues.cacheCreationRatio = modelCopy.cacheCreationRatio;
formValues.imageRatio = modelCopy.imageRatio;
formValues.audioRatio = modelCopy.audioRatio;
formValues.audioCompletionRatio = modelCopy.audioCompletionRatio;

editFormRef.current.setValues(formValues);
}
}, 0);
};

const rowSelection = {
selectedRowKeys,
onChange: (selectedKeys) => {
@@ -715,27 +772,87 @@ export default function ModelRatioNotSetEditor(props) {
</Form>
</Modal>

{/* 高级比例编辑弹窗 */}
{/* 编辑模型弹窗 */}
<Modal
title={t('高级比例设置') + ' - ' + (editRecord?.name || '')}
title={t('编辑模型') + ' - ' + (editRecord?.name || '')}
visible={editVisible}
onCancel={() => {
setEditVisible(false);
setEditRecord(null);
setPricingMode('per-token');
setPricingSubMode('ratio');
}}
onOk={() => {
if (editRecord && editFormRef.current) {
const valuesToSave = { ...editRecord };

// 处理按价格设置模式
if (
pricingMode === 'per-token' &&
pricingSubMode === 'token-price' &&
editRecord.tokenPrice
) {
const tokenPrice = parseFloat(editRecord.tokenPrice);
valuesToSave.ratio = (tokenPrice / 2).toString();

if (editRecord.completionTokenPrice) {
const completionPrice = parseFloat(
editRecord.completionTokenPrice,
);
const modelPrice = parseFloat(editRecord.tokenPrice);
if (modelPrice > 0) {
valuesToSave.completionRatio = (
completionPrice / modelPrice
).toString();
}
}
}

// 根据定价模式清理字段
if (pricingMode === 'per-token') {
valuesToSave.price = '';
} else {
valuesToSave.ratio = '';
valuesToSave.completionRatio = '';
}

// 从表单读取高级倍率字段
const formValues = editFormRef.current.getValues();
valuesToSave.cacheRatio =
formValues.cacheRatio != null
? formValues.cacheRatio
: '';
valuesToSave.cacheCreationRatio =
formValues.cacheCreationRatio != null
? formValues.cacheCreationRatio
: '';
valuesToSave.imageRatio =
formValues.imageRatio != null
? formValues.imageRatio
: '';
valuesToSave.audioRatio =
formValues.audioRatio != null
? formValues.audioRatio
: '';
valuesToSave.audioCompletionRatio =
formValues.audioCompletionRatio != null
? formValues.audioCompletionRatio
: '';

setModels((prev) =>
prev.map((model) => {
if (model.name !== editRecord.name) return model;
return {
...model,
cacheRatio: formValues.cacheRatio != null ? formValues.cacheRatio : model.cacheRatio,
cacheCreationRatio: formValues.cacheCreationRatio != null ? formValues.cacheCreationRatio : model.cacheCreationRatio,
imageRatio: formValues.imageRatio != null ? formValues.imageRatio : model.imageRatio,
audioRatio: formValues.audioRatio != null ? formValues.audioRatio : model.audioRatio,
audioCompletionRatio: formValues.audioCompletionRatio != null ? formValues.audioCompletionRatio : model.audioCompletionRatio,
price: valuesToSave.price,
ratio: valuesToSave.ratio,
completionRatio: valuesToSave.completionRatio,
cacheRatio: valuesToSave.cacheRatio,
cacheCreationRatio: valuesToSave.cacheCreationRatio,
imageRatio: valuesToSave.imageRatio,
audioRatio: valuesToSave.audioRatio,
audioCompletionRatio:
valuesToSave.audioCompletionRatio,
};
}),
);
@@ -748,51 +865,181 @@ export default function ModelRatioNotSetEditor(props) {
<Form
key={editRecord?.name || 'empty'}
getFormApi={(api) => (editFormRef.current = api)}
initValues={{
cacheRatio: editRecord?.cacheRatio ?? '',
cacheCreationRatio: editRecord?.cacheCreationRatio ?? '',
imageRatio: editRecord?.imageRatio ?? '',
audioRatio: editRecord?.audioRatio ?? '',
audioCompletionRatio: editRecord?.audioCompletionRatio ?? '',
}}
>
<Form.InputNumber
field='cacheRatio'
label={t('缓存读取倍率')}
min={0}
step={0.01}
placeholder={t('全局默认值(1.0)')}
extraText={t('缓存命中时输入价格的倍率。0.5 = 缓存命中的 token 按输入价格的一半计费,留空使用全局默认值 1.0')}
/>
<Form.InputNumber
field='cacheCreationRatio'
label={t('缓存创建倍率(5分钟)')}
min={0}
step={0.01}
placeholder={t('全局默认值(1.25)')}
extraText={t('写入缓存的 token 按此倍率 × 输入价格计费。默认 1.25(5分钟 TTL);超过 1 小时自动按 1.6x 计费,留空使用全局默认值')}
/>
<Form.InputNumber
field='imageRatio'
label={t('图片倍率')}
min={0}
step={0.01}
placeholder={t('全局默认值')}
/>
<Form.InputNumber
field='audioRatio'
label={t('音频输入倍率')}
min={0}
step={0.01}
placeholder={t('全局默认值')}
/>
<Form.InputNumber
field='audioCompletionRatio'
label={t('音频输出倍率')}
min={0}
step={0.01}
placeholder={t('全局默认值')}
/>
<Form.Section text={t('定价模式')}>
<div style={{ marginBottom: '16px' }}>
<RadioGroup
type='button'
value={pricingMode}
onChange={(e) => {
setPricingMode(e.target.value);
}}
>
<Radio value='per-token'>{t('按量计费')}</Radio>
<Radio value='per-request'>{t('按次计费')}</Radio>
</RadioGroup>
</div>
</Form.Section>

{pricingMode === 'per-token' && (
<>
<Form.Section text={t('价格设置方式')}>
<div style={{ marginBottom: '16px' }}>
<RadioGroup
type='button'
value={pricingSubMode}
onChange={(e) => {
setPricingSubMode(e.target.value);
}}
>
<Radio value='ratio'>{t('按倍率设置')}</Radio>
<Radio value='token-price'>
{t('按价格设置')}
</Radio>
</RadioGroup>
</div>
</Form.Section>

{pricingSubMode === 'ratio' && (
<>
<Form.Input
field='ratioInput'
label={t('模型倍率')}
placeholder={t('输入模型倍率')}
onChange={(value) =>
setEditRecord((prev) =>
prev ? { ...prev, ratio: value } : prev,
)
}
initValue={editRecord?.ratio || ''}
/>
<Form.Input
field='completionRatioInput'
label={t('补全倍率')}
placeholder={t('输入补全倍率')}
onChange={(value) =>
setEditRecord((prev) =>
prev
? { ...prev, completionRatio: value }
: prev,
)
}
initValue={editRecord?.completionRatio || ''}
/>
</>
)}

{pricingSubMode === 'token-price' && (
<>
<Form.Input
field='modelTokenPrice'
label={t('输入价格')}
suffix={t('$/1M tokens')}
onChange={(value) => {
let updated = {
...(editRecord || {}),
tokenPrice: value,
ratio: 0,
};
if (!isNaN(value) && value !== '') {
const tokenPrice = parseFloat(value);
updated.ratio = (tokenPrice / 2).toString();
}
setEditRecord(updated);
}}
initValue={editRecord?.tokenPrice || ''}
/>
<Form.Input
field='completionTokenPrice'
label={t('输出价格')}
suffix={t('$/1M tokens')}
onChange={(value) => {
let updated = {
...(editRecord || {}),
completionTokenPrice: value,
completionRatio: 0,
};
if (
!isNaN(value) &&
value !== '' &&
editRecord?.tokenPrice
) {
const completionPrice = parseFloat(value);
const modelPrice = parseFloat(
editRecord.tokenPrice,
);
if (modelPrice > 0) {
updated.completionRatio = (
completionPrice / modelPrice
).toString();
}
}
setEditRecord(updated);
}}
initValue={editRecord?.completionTokenPrice || ''}
/>
</>
)}

<Form.Section text={t('高级比例(留空使用默认值)')}>
<Form.InputNumber
field='cacheRatio'
label={t('缓存读取倍率')}
min={0}
step={0.01}
placeholder={t('全局默认值(1.0)')}
extraText={t(
'缓存命中时输入价格的倍率。0.5 = 缓存命中的 token 按输入价格的一半计费,留空使用全局默认值 1.0',
)}
/>
<Form.InputNumber
field='cacheCreationRatio'
label={t('缓存创建倍率(5分钟)')}
min={0}
step={0.01}
placeholder={t('全局默认值(1.25)')}
extraText={t(
'写入缓存的 token 按此倍率 × 输入价格计费。默认 1.25(5分钟 TTL);超过 1 小时自动按 1.6x 计费,留空使用全局默认值',
)}
/>
<Form.InputNumber
field='imageRatio'
label={t('图片倍率')}
min={0}
step={0.01}
placeholder={t('全局默认值')}
/>
<Form.InputNumber
field='audioRatio'
label={t('音频输入倍率')}
min={0}
step={0.01}
placeholder={t('全局默认值')}
/>
<Form.InputNumber
field='audioCompletionRatio'
label={t('音频输出倍率')}
min={0}
step={0.01}
placeholder={t('全局默认值')}
/>
</Form.Section>
</>
)}

{pricingMode === 'per-request' && (
<Form.Input
field='priceInput'
label={t('固定价格(每次)')}
placeholder={t('输入每次价格')}
onChange={(value) =>
setEditRecord((prev) =>
prev ? { ...prev, price: value } : prev,
)
}
initValue={editRecord?.price || ''}
/>
)}
</Form>
</Modal>
</>


Загрузка…
Отмена
Сохранить