|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useEffect, useState, useRef } from 'react'; |
|
|
import { |
|
|
Table, |
|
|
Button, |
|
|
Input, |
|
|
Modal, |
|
|
Form, |
|
|
Space, |
|
|
RadioGroup, |
|
|
Radio, |
|
|
Checkbox, |
|
|
Tag, |
|
|
} from '@douyinfe/semi-ui'; |
|
|
import { |
|
|
IconDelete, |
|
|
IconPlus, |
|
|
IconSearch, |
|
|
IconSave, |
|
|
IconEdit, |
|
|
} from '@douyinfe/semi-icons'; |
|
|
import { API, showError, showSuccess, getQuotaPerUnit } from '../../../helpers'; |
|
|
import { useTranslation } from 'react-i18next'; |
|
|
|
|
|
export default function ModelSettingsVisualEditor(props) { |
|
|
const { t } = useTranslation(); |
|
|
const [models, setModels] = useState([]); |
|
|
const [visible, setVisible] = useState(false); |
|
|
const [isEditMode, setIsEditMode] = useState(false); |
|
|
const [currentModel, setCurrentModel] = useState(null); |
|
|
const [searchText, setSearchText] = useState(''); |
|
|
const [currentPage, setCurrentPage] = useState(1); |
|
|
const [loading, setLoading] = useState(false); |
|
|
const [pricingMode, setPricingMode] = useState('per-token'); |
|
|
const [pricingSubMode, setPricingSubMode] = useState('ratio'); |
|
|
const [conflictOnly, setConflictOnly] = useState(false); |
|
|
const formRef = useRef(null); |
|
|
const pageSize = 10; |
|
|
const quotaPerUnit = getQuotaPerUnit(); |
|
|
|
|
|
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) => { |
|
|
const price = modelPrice[name] === undefined ? '' : modelPrice[name]; |
|
|
const ratio = modelRatio[name] === undefined ? '' : modelRatio[name]; |
|
|
const comp = |
|
|
completionRatio[name] === undefined ? '' : completionRatio[name]; |
|
|
|
|
|
return { |
|
|
name, |
|
|
price, |
|
|
ratio, |
|
|
completionRatio: comp, |
|
|
hasConflict: price !== '' && (ratio !== '' || comp !== ''), |
|
|
}; |
|
|
}); |
|
|
|
|
|
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); |
|
|
}; |
|
|
|
|
|
|
|
|
const filteredModels = models.filter((model) => { |
|
|
const keywordMatch = searchText ? model.name.includes(searchText) : true; |
|
|
const conflictMatch = conflictOnly ? model.hasConflict : true; |
|
|
return keywordMatch && conflictMatch; |
|
|
}); |
|
|
|
|
|
|
|
|
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, |
|
|
); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
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: t('模型名称'), |
|
|
dataIndex: 'name', |
|
|
key: 'name', |
|
|
render: (text, record) => ( |
|
|
<span> |
|
|
{text} |
|
|
{record.hasConflict && ( |
|
|
<Tag color='red' shape='circle' className='ml-2'> |
|
|
{t('矛盾')} |
|
|
</Tag> |
|
|
)} |
|
|
</span> |
|
|
), |
|
|
}, |
|
|
{ |
|
|
title: t('模型固定价格'), |
|
|
dataIndex: 'price', |
|
|
key: 'price', |
|
|
render: (text, record) => ( |
|
|
<Input |
|
|
value={text} |
|
|
placeholder={t('按量计费')} |
|
|
onChange={(value) => updateModel(record.name, 'price', value)} |
|
|
/> |
|
|
), |
|
|
}, |
|
|
{ |
|
|
title: t('模型倍率'), |
|
|
dataIndex: 'ratio', |
|
|
key: 'ratio', |
|
|
render: (text, record) => ( |
|
|
<Input |
|
|
value={text} |
|
|
placeholder={record.price !== '' ? t('模型倍率') : t('默认补全倍率')} |
|
|
disabled={record.price !== ''} |
|
|
onChange={(value) => updateModel(record.name, 'ratio', value)} |
|
|
/> |
|
|
), |
|
|
}, |
|
|
{ |
|
|
title: t('补全倍率'), |
|
|
dataIndex: 'completionRatio', |
|
|
key: 'completionRatio', |
|
|
render: (text, record) => ( |
|
|
<Input |
|
|
value={text} |
|
|
placeholder={record.price !== '' ? t('补全倍率') : t('默认补全倍率')} |
|
|
disabled={record.price !== ''} |
|
|
onChange={(value) => |
|
|
updateModel(record.name, 'completionRatio', value) |
|
|
} |
|
|
/> |
|
|
), |
|
|
}, |
|
|
{ |
|
|
title: t('操作'), |
|
|
key: 'action', |
|
|
render: (_, record) => ( |
|
|
<Space> |
|
|
<Button |
|
|
type='primary' |
|
|
icon={<IconEdit />} |
|
|
onClick={() => editModel(record)} |
|
|
></Button> |
|
|
<Button |
|
|
icon={<IconDelete />} |
|
|
type='danger' |
|
|
onClick={() => deleteModel(record.name)} |
|
|
/> |
|
|
</Space> |
|
|
), |
|
|
}, |
|
|
]; |
|
|
|
|
|
const updateModel = (name, field, value) => { |
|
|
if (isNaN(value)) { |
|
|
showError('请输入数字'); |
|
|
return; |
|
|
} |
|
|
setModels((prev) => |
|
|
prev.map((model) => { |
|
|
if (model.name !== name) return model; |
|
|
const updated = { ...model, [field]: value }; |
|
|
updated.hasConflict = |
|
|
updated.price !== '' && |
|
|
(updated.ratio !== '' || updated.completionRatio !== ''); |
|
|
return updated; |
|
|
}), |
|
|
); |
|
|
}; |
|
|
|
|
|
const deleteModel = (name) => { |
|
|
setModels((prev) => prev.filter((model) => model.name !== name)); |
|
|
}; |
|
|
|
|
|
const calculateRatioFromTokenPrice = (tokenPrice) => { |
|
|
return tokenPrice / 2; |
|
|
}; |
|
|
|
|
|
const calculateCompletionRatioFromPrices = ( |
|
|
modelTokenPrice, |
|
|
completionTokenPrice, |
|
|
) => { |
|
|
if (!modelTokenPrice || modelTokenPrice === '0') { |
|
|
showError('模型价格不能为0'); |
|
|
return ''; |
|
|
} |
|
|
return completionTokenPrice / modelTokenPrice; |
|
|
}; |
|
|
|
|
|
const handleTokenPriceChange = (value) => { |
|
|
|
|
|
let newState = { |
|
|
...(currentModel || {}), |
|
|
tokenPrice: value, |
|
|
ratio: 0, |
|
|
}; |
|
|
|
|
|
if (!isNaN(value) && value !== '') { |
|
|
const tokenPrice = parseFloat(value); |
|
|
const ratio = calculateRatioFromTokenPrice(tokenPrice); |
|
|
newState.ratio = ratio; |
|
|
} |
|
|
|
|
|
|
|
|
setCurrentModel(newState); |
|
|
}; |
|
|
|
|
|
const handleCompletionTokenPriceChange = (value) => { |
|
|
|
|
|
let newState = { |
|
|
...(currentModel || {}), |
|
|
completionTokenPrice: value, |
|
|
completionRatio: 0, |
|
|
}; |
|
|
|
|
|
if (!isNaN(value) && value !== '' && currentModel?.tokenPrice) { |
|
|
const completionTokenPrice = parseFloat(value); |
|
|
const modelTokenPrice = parseFloat(currentModel.tokenPrice); |
|
|
|
|
|
if (modelTokenPrice > 0) { |
|
|
const completionRatio = calculateCompletionRatioFromPrices( |
|
|
modelTokenPrice, |
|
|
completionTokenPrice, |
|
|
); |
|
|
newState.completionRatio = completionRatio; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
setCurrentModel(newState); |
|
|
}; |
|
|
|
|
|
const addOrUpdateModel = (values) => { |
|
|
|
|
|
const existingModelIndex = models.findIndex( |
|
|
(model) => model.name === values.name, |
|
|
); |
|
|
|
|
|
if (existingModelIndex >= 0) { |
|
|
|
|
|
setModels((prev) => |
|
|
prev.map((model, index) => { |
|
|
if (index !== existingModelIndex) return model; |
|
|
const updated = { |
|
|
name: values.name, |
|
|
price: values.price || '', |
|
|
ratio: values.ratio || '', |
|
|
completionRatio: values.completionRatio || '', |
|
|
}; |
|
|
updated.hasConflict = |
|
|
updated.price !== '' && |
|
|
(updated.ratio !== '' || updated.completionRatio !== ''); |
|
|
return updated; |
|
|
}), |
|
|
); |
|
|
setVisible(false); |
|
|
showSuccess(t('更新成功')); |
|
|
} else { |
|
|
|
|
|
|
|
|
if (models.some((model) => model.name === values.name)) { |
|
|
showError(t('模型名称已存在')); |
|
|
return; |
|
|
} |
|
|
|
|
|
setModels((prev) => { |
|
|
const newModel = { |
|
|
name: values.name, |
|
|
price: values.price || '', |
|
|
ratio: values.ratio || '', |
|
|
completionRatio: values.completionRatio || '', |
|
|
}; |
|
|
newModel.hasConflict = |
|
|
newModel.price !== '' && |
|
|
(newModel.ratio !== '' || newModel.completionRatio !== ''); |
|
|
return [newModel, ...prev]; |
|
|
}); |
|
|
setVisible(false); |
|
|
showSuccess(t('添加成功')); |
|
|
} |
|
|
}; |
|
|
|
|
|
const calculateTokenPriceFromRatio = (ratio) => { |
|
|
return ratio * 2; |
|
|
}; |
|
|
|
|
|
const resetModalState = () => { |
|
|
setCurrentModel(null); |
|
|
setPricingMode('per-token'); |
|
|
setPricingSubMode('ratio'); |
|
|
setIsEditMode(false); |
|
|
}; |
|
|
|
|
|
const editModel = (record) => { |
|
|
setIsEditMode(true); |
|
|
|
|
|
let initialPricingMode = 'per-token'; |
|
|
let initialPricingSubMode = 'ratio'; |
|
|
|
|
|
if (record.price !== '') { |
|
|
initialPricingMode = 'per-request'; |
|
|
} else { |
|
|
initialPricingMode = 'per-token'; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
setPricingMode(initialPricingMode); |
|
|
setPricingSubMode(initialPricingSubMode); |
|
|
|
|
|
|
|
|
const modelCopy = { ...record }; |
|
|
|
|
|
|
|
|
if (record.ratio) { |
|
|
modelCopy.tokenPrice = calculateTokenPriceFromRatio( |
|
|
parseFloat(record.ratio), |
|
|
).toString(); |
|
|
|
|
|
if (record.completionRatio) { |
|
|
modelCopy.completionTokenPrice = ( |
|
|
parseFloat(modelCopy.tokenPrice) * parseFloat(record.completionRatio) |
|
|
).toString(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
setCurrentModel(modelCopy); |
|
|
|
|
|
|
|
|
setVisible(true); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
if (formRef.current) { |
|
|
|
|
|
const formValues = { |
|
|
name: modelCopy.name, |
|
|
}; |
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
formRef.current.setValues(formValues); |
|
|
} |
|
|
}, 0); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<> |
|
|
<Space vertical align='start' style={{ width: '100%' }}> |
|
|
<Space className='mt-2'> |
|
|
<Button |
|
|
icon={<IconPlus />} |
|
|
onClick={() => { |
|
|
resetModalState(); |
|
|
setVisible(true); |
|
|
}} |
|
|
> |
|
|
{t('添加模型')} |
|
|
</Button> |
|
|
<Button type='primary' icon={<IconSave />} onClick={SubmitData}> |
|
|
{t('应用更改')} |
|
|
</Button> |
|
|
<Input |
|
|
prefix={<IconSearch />} |
|
|
placeholder={t('搜索模型名称')} |
|
|
value={searchText} |
|
|
onChange={(value) => { |
|
|
setSearchText(value); |
|
|
setCurrentPage(1); |
|
|
}} |
|
|
style={{ width: 200 }} |
|
|
showClear |
|
|
/> |
|
|
<Checkbox |
|
|
checked={conflictOnly} |
|
|
onChange={(e) => { |
|
|
setConflictOnly(e.target.checked); |
|
|
setCurrentPage(1); |
|
|
}} |
|
|
> |
|
|
{t('仅显示矛盾倍率')} |
|
|
</Checkbox> |
|
|
</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={isEditMode ? t('编辑模型') : t('添加模型')} |
|
|
visible={visible} |
|
|
onCancel={() => { |
|
|
resetModalState(); |
|
|
setVisible(false); |
|
|
}} |
|
|
onOk={() => { |
|
|
if (currentModel) { |
|
|
// If we're in token price mode, make sure ratio values are properly set |
|
|
const valuesToSave = { ...currentModel }; |
|
|
|
|
|
if ( |
|
|
pricingMode === 'per-token' && |
|
|
pricingSubMode === 'token-price' && |
|
|
currentModel.tokenPrice |
|
|
) { |
|
|
// Calculate and set ratio from token price |
|
|
const tokenPrice = parseFloat(currentModel.tokenPrice); |
|
|
valuesToSave.ratio = (tokenPrice / 2).toString(); |
|
|
|
|
|
// Calculate and set completion ratio if both token prices are available |
|
|
if ( |
|
|
currentModel.completionTokenPrice && |
|
|
currentModel.tokenPrice |
|
|
) { |
|
|
const completionPrice = parseFloat( |
|
|
currentModel.completionTokenPrice, |
|
|
); |
|
|
const modelPrice = parseFloat(currentModel.tokenPrice); |
|
|
if (modelPrice > 0) { |
|
|
valuesToSave.completionRatio = ( |
|
|
completionPrice / modelPrice |
|
|
).toString(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// Clear price if we're in per-token mode |
|
|
if (pricingMode === 'per-token') { |
|
|
valuesToSave.price = ''; |
|
|
} else { |
|
|
// Clear ratios if we're in per-request mode |
|
|
valuesToSave.ratio = ''; |
|
|
valuesToSave.completionRatio = ''; |
|
|
} |
|
|
|
|
|
addOrUpdateModel(valuesToSave); |
|
|
} |
|
|
}} |
|
|
> |
|
|
<Form getFormApi={(api) => (formRef.current = api)}> |
|
|
<Form.Input |
|
|
field='name' |
|
|
label={t('模型名称')} |
|
|
placeholder='strawberry' |
|
|
required |
|
|
disabled={isEditMode} |
|
|
onChange={(value) => |
|
|
setCurrentModel((prev) => ({ ...prev, name: value })) |
|
|
} |
|
|
/> |
|
|
|
|
|
<Form.Section text={t('定价模式')}> |
|
|
<div style={{ marginBottom: '16px' }}> |
|
|
<RadioGroup |
|
|
type='button' |
|
|
value={pricingMode} |
|
|
onChange={(e) => { |
|
|
const newMode = e.target.value; |
|
|
const oldMode = pricingMode; |
|
|
setPricingMode(newMode); |
|
|
|
|
|
// Instead of resetting all values, convert between modes |
|
|
if (currentModel) { |
|
|
const updatedModel = { ...currentModel }; |
|
|
|
|
|
// Update formRef with converted values |
|
|
if (formRef.current) { |
|
|
const formValues = { |
|
|
name: updatedModel.name, |
|
|
}; |
|
|
|
|
|
if (newMode === 'per-request') { |
|
|
formValues.priceInput = updatedModel.price || ''; |
|
|
} else if (newMode === 'per-token') { |
|
|
formValues.ratioInput = updatedModel.ratio || ''; |
|
|
formValues.completionRatioInput = |
|
|
updatedModel.completionRatio || ''; |
|
|
formValues.modelTokenPrice = |
|
|
updatedModel.tokenPrice || ''; |
|
|
formValues.completionTokenPrice = |
|
|
updatedModel.completionTokenPrice || ''; |
|
|
} |
|
|
|
|
|
formRef.current.setValues(formValues); |
|
|
} |
|
|
|
|
|
// Update the model state |
|
|
setCurrentModel(updatedModel); |
|
|
} |
|
|
}} |
|
|
> |
|
|
<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) => { |
|
|
const newSubMode = e.target.value; |
|
|
const oldSubMode = pricingSubMode; |
|
|
setPricingSubMode(newSubMode); |
|
|
|
|
|
// Handle conversion between submodes |
|
|
if (currentModel) { |
|
|
const updatedModel = { ...currentModel }; |
|
|
|
|
|
// Convert between ratio and token price |
|
|
if ( |
|
|
oldSubMode === 'ratio' && |
|
|
newSubMode === 'token-price' |
|
|
) { |
|
|
if (updatedModel.ratio) { |
|
|
updatedModel.tokenPrice = |
|
|
calculateTokenPriceFromRatio( |
|
|
parseFloat(updatedModel.ratio), |
|
|
).toString(); |
|
|
|
|
|
if (updatedModel.completionRatio) { |
|
|
updatedModel.completionTokenPrice = ( |
|
|
parseFloat(updatedModel.tokenPrice) * |
|
|
parseFloat(updatedModel.completionRatio) |
|
|
).toString(); |
|
|
} |
|
|
} |
|
|
} else if ( |
|
|
oldSubMode === 'token-price' && |
|
|
newSubMode === 'ratio' |
|
|
) { |
|
|
// Ratio values should already be calculated by the handlers |
|
|
} |
|
|
|
|
|
// Update the form values |
|
|
if (formRef.current) { |
|
|
const formValues = {}; |
|
|
|
|
|
if (newSubMode === 'ratio') { |
|
|
formValues.ratioInput = updatedModel.ratio || ''; |
|
|
formValues.completionRatioInput = |
|
|
updatedModel.completionRatio || ''; |
|
|
} else if (newSubMode === 'token-price') { |
|
|
formValues.modelTokenPrice = |
|
|
updatedModel.tokenPrice || ''; |
|
|
formValues.completionTokenPrice = |
|
|
updatedModel.completionTokenPrice || ''; |
|
|
} |
|
|
|
|
|
formRef.current.setValues(formValues); |
|
|
} |
|
|
|
|
|
setCurrentModel(updatedModel); |
|
|
} |
|
|
}} |
|
|
> |
|
|
<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) => |
|
|
setCurrentModel((prev) => ({ |
|
|
...(prev || {}), |
|
|
ratio: value, |
|
|
})) |
|
|
} |
|
|
initValue={currentModel?.ratio || ''} |
|
|
/> |
|
|
<Form.Input |
|
|
field='completionRatioInput' |
|
|
label={t('补全倍率')} |
|
|
placeholder={t('输入补全倍率')} |
|
|
onChange={(value) => |
|
|
setCurrentModel((prev) => ({ |
|
|
...(prev || {}), |
|
|
completionRatio: value, |
|
|
})) |
|
|
} |
|
|
initValue={currentModel?.completionRatio || ''} |
|
|
/> |
|
|
</> |
|
|
)} |
|
|
|
|
|
{pricingSubMode === 'token-price' && ( |
|
|
<> |
|
|
<Form.Input |
|
|
field='modelTokenPrice' |
|
|
label={t('输入价格')} |
|
|
onChange={(value) => { |
|
|
handleTokenPriceChange(value); |
|
|
}} |
|
|
initValue={currentModel?.tokenPrice || ''} |
|
|
suffix={t('$/1M tokens')} |
|
|
/> |
|
|
<Form.Input |
|
|
field='completionTokenPrice' |
|
|
label={t('输出价格')} |
|
|
onChange={(value) => { |
|
|
handleCompletionTokenPriceChange(value); |
|
|
}} |
|
|
initValue={currentModel?.completionTokenPrice || ''} |
|
|
suffix={t('$/1M tokens')} |
|
|
/> |
|
|
</> |
|
|
)} |
|
|
</> |
|
|
)} |
|
|
|
|
|
{pricingMode === 'per-request' && ( |
|
|
<Form.Input |
|
|
field='priceInput' |
|
|
label={t('固定价格(每次)')} |
|
|
placeholder={t('输入每次价格')} |
|
|
onChange={(value) => |
|
|
setCurrentModel((prev) => ({ |
|
|
...(prev || {}), |
|
|
price: value, |
|
|
})) |
|
|
} |
|
|
initValue={currentModel?.price || ''} |
|
|
/> |
|
|
)} |
|
|
</Form> |
|
|
</Modal> |
|
|
</> |
|
|
); |
|
|
} |
|
|
|