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) => ( } - onClick={() => { - setEditRecord(record); - setEditVisible(true); - }} - > - {t('高级比例')} - + 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 || ''} + /> + )} >