From 1c15ee996dfd61224ea6d6e599e8a0f9af06f92c Mon Sep 17 00:00:00 2001 From: fengsilin Date: Thu, 4 Jun 2026 16:20:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(ratio):=20=E5=8D=87=E7=BA=A7=E6=9C=AA?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=80=8D=E7=8E=87=E6=A8=A1=E5=9E=8B=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=BC=B9=E7=AA=97=EF=BC=8C=E5=AF=B9=E9=BD=90=E5=8F=AF?= =?UTF-8?q?=E8=A7=86=E5=8C=96=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将操作列「高级比例」按钮改为编辑图标按钮,打开完整编辑弹窗 - 编辑弹窗新增定价模式切换(按量计费/按次计费) - 按量计费下支持按倍率设置和按价格设置两种子模式 - 按倍率:模型倍率、补全倍率 - 按价格:输入价格、输出价格($/1M tokens),自动计算倍率 - 高级比例区域:缓存读取、缓存创建、图片、音频输入、音频输出 - 修复 SubmitData 遗漏高级倍率字段保存的 bug(CacheRatio 等 5 个字段) - 清理死代码(未调用的转换函数、残留 console.log) --- .../Setting/Ratio/ModelRationNotSetEditor.jsx | 369 +++++++++++++++--- 1 file changed, 308 insertions(+), 61 deletions(-) diff --git a/web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx b/web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx index 98135a5..6ec2a3a 100644 --- a/web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx +++ b/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) => ( + type='primary' + icon={} + 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) { - {/* 高级比例编辑弹窗 */} + {/* 编辑模型弹窗 */} { 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) {
(editFormRef.current = api)} - initValues={{ - cacheRatio: editRecord?.cacheRatio ?? '', - cacheCreationRatio: editRecord?.cacheCreationRatio ?? '', - imageRatio: editRecord?.imageRatio ?? '', - audioRatio: editRecord?.audioRatio ?? '', - audioCompletionRatio: editRecord?.audioCompletionRatio ?? '', - }} > - - - - - + +
+ { + setPricingMode(e.target.value); + }} + > + {t('按量计费')} + {t('按次计费')} + +
+
+ + {pricingMode === 'per-token' && ( + <> + +
+ { + setPricingSubMode(e.target.value); + }} + > + {t('按倍率设置')} + + {t('按价格设置')} + + +
+
+ + {pricingSubMode === 'ratio' && ( + <> + + setEditRecord((prev) => + prev ? { ...prev, ratio: value } : prev, + ) + } + initValue={editRecord?.ratio || ''} + /> + + setEditRecord((prev) => + prev + ? { ...prev, completionRatio: value } + : prev, + ) + } + initValue={editRecord?.completionRatio || ''} + /> + + )} + + {pricingSubMode === 'token-price' && ( + <> + { + 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 || ''} + /> + { + 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 || ''} + /> + + )} + + + + + + + + + + )} + + {pricingMode === 'per-request' && ( + + setEditRecord((prev) => + prev ? { ...prev, price: value } : prev, + ) + } + initValue={editRecord?.price || ''} + /> + )}