mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 02:25:00 +00:00
feat: normalize number handling in model pricing editor #3246
This commit is contained in:
@@ -59,6 +59,11 @@ const formatNumber = (value) => {
|
|||||||
return parseFloat(num.toFixed(12)).toString();
|
return parseFloat(num.toFixed(12)).toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toNormalizedNumber = (value) => {
|
||||||
|
const formatted = formatNumber(value);
|
||||||
|
return formatted === '' ? null : Number(formatted);
|
||||||
|
};
|
||||||
|
|
||||||
const parseOptionJSON = (rawValue) => {
|
const parseOptionJSON = (rawValue) => {
|
||||||
if (!rawValue || rawValue.trim() === '') {
|
if (!rawValue || rawValue.trim() === '') {
|
||||||
return {};
|
return {};
|
||||||
@@ -123,7 +128,11 @@ const buildModelState = (name, sourceMaps) => {
|
|||||||
lockedCompletionRatio: completionRatioMeta.ratio,
|
lockedCompletionRatio: completionRatioMeta.ratio,
|
||||||
completionPrice:
|
completionPrice:
|
||||||
inputPriceNumber !== null &&
|
inputPriceNumber !== null &&
|
||||||
hasValue(completionRatioMeta.locked ? completionRatioMeta.ratio : completionRatio)
|
hasValue(
|
||||||
|
completionRatioMeta.locked
|
||||||
|
? completionRatioMeta.ratio
|
||||||
|
: completionRatio,
|
||||||
|
)
|
||||||
? formatNumber(
|
? formatNumber(
|
||||||
inputPriceNumber *
|
inputPriceNumber *
|
||||||
Number(
|
Number(
|
||||||
@@ -192,7 +201,9 @@ export const getModelWarnings = (model, t) => {
|
|||||||
].some(hasValue);
|
].some(hasValue);
|
||||||
|
|
||||||
if (model.hasConflict) {
|
if (model.hasConflict) {
|
||||||
warnings.push(t('当前模型同时存在按次价格和倍率配置,保存时会按当前计费方式覆盖。'));
|
warnings.push(
|
||||||
|
t('当前模型同时存在按次价格和倍率配置,保存时会按当前计费方式覆盖。'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -207,11 +218,17 @@ export const getModelWarnings = (model, t) => {
|
|||||||
].some(hasValue)
|
].some(hasValue)
|
||||||
) {
|
) {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
t('当前模型存在未显式设置输入倍率的扩展倍率;填写输入价格后会自动换算为价格字段。'),
|
t(
|
||||||
|
'当前模型存在未显式设置输入倍率的扩展倍率;填写输入价格后会自动换算为价格字段。',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.billingMode === 'per-token' && hasDerivedPricing && !hasValue(model.inputPrice)) {
|
if (
|
||||||
|
model.billingMode === 'per-token' &&
|
||||||
|
hasDerivedPricing &&
|
||||||
|
!hasValue(model.inputPrice)
|
||||||
|
) {
|
||||||
warnings.push(t('按量计费下需要先填写输入价格,才能保存其它价格项。'));
|
warnings.push(t('按量计费下需要先填写输入价格,才能保存其它价格项。'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +266,8 @@ export const buildSummaryText = (model, t) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const buildOptionalFieldToggles = (model) => ({
|
export const buildOptionalFieldToggles = (model) => ({
|
||||||
completionPrice: model.completionRatioLocked || hasValue(model.completionPrice),
|
completionPrice:
|
||||||
|
model.completionRatioLocked || hasValue(model.completionPrice),
|
||||||
cachePrice: hasValue(model.cachePrice),
|
cachePrice: hasValue(model.cachePrice),
|
||||||
createCachePrice: hasValue(model.createCachePrice),
|
createCachePrice: hasValue(model.createCachePrice),
|
||||||
imagePrice: hasValue(model.imagePrice),
|
imagePrice: hasValue(model.imagePrice),
|
||||||
@@ -271,7 +289,7 @@ const serializeModel = (model, t) => {
|
|||||||
|
|
||||||
if (model.billingMode === 'per-request') {
|
if (model.billingMode === 'per-request') {
|
||||||
if (hasValue(model.fixedPrice)) {
|
if (hasValue(model.fixedPrice)) {
|
||||||
result.ModelPrice = Number(model.fixedPrice);
|
result.ModelPrice = toNormalizedNumber(model.fixedPrice);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -296,57 +314,68 @@ const serializeModel = (model, t) => {
|
|||||||
if (inputPrice === null) {
|
if (inputPrice === null) {
|
||||||
if (hasDependentPrice) {
|
if (hasDependentPrice) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
t('模型 {{name}} 缺少输入价格,无法计算补全/缓存/图片/音频价格对应的倍率', {
|
t(
|
||||||
name: model.name,
|
'模型 {{name}} 缺少输入价格,无法计算补全/缓存/图片/音频价格对应的倍率',
|
||||||
}),
|
{
|
||||||
|
name: model.name,
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasValue(model.rawRatios.modelRatio)) {
|
if (hasValue(model.rawRatios.modelRatio)) {
|
||||||
result.ModelRatio = Number(model.rawRatios.modelRatio);
|
result.ModelRatio = toNormalizedNumber(model.rawRatios.modelRatio);
|
||||||
}
|
}
|
||||||
if (hasValue(model.rawRatios.completionRatio)) {
|
if (hasValue(model.rawRatios.completionRatio)) {
|
||||||
result.CompletionRatio = Number(model.rawRatios.completionRatio);
|
result.CompletionRatio = toNormalizedNumber(
|
||||||
|
model.rawRatios.completionRatio,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (hasValue(model.rawRatios.cacheRatio)) {
|
if (hasValue(model.rawRatios.cacheRatio)) {
|
||||||
result.CacheRatio = Number(model.rawRatios.cacheRatio);
|
result.CacheRatio = toNormalizedNumber(model.rawRatios.cacheRatio);
|
||||||
}
|
}
|
||||||
if (hasValue(model.rawRatios.createCacheRatio)) {
|
if (hasValue(model.rawRatios.createCacheRatio)) {
|
||||||
result.CreateCacheRatio = Number(model.rawRatios.createCacheRatio);
|
result.CreateCacheRatio = toNormalizedNumber(
|
||||||
|
model.rawRatios.createCacheRatio,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (hasValue(model.rawRatios.imageRatio)) {
|
if (hasValue(model.rawRatios.imageRatio)) {
|
||||||
result.ImageRatio = Number(model.rawRatios.imageRatio);
|
result.ImageRatio = toNormalizedNumber(model.rawRatios.imageRatio);
|
||||||
}
|
}
|
||||||
if (hasValue(model.rawRatios.audioRatio)) {
|
if (hasValue(model.rawRatios.audioRatio)) {
|
||||||
result.AudioRatio = Number(model.rawRatios.audioRatio);
|
result.AudioRatio = toNormalizedNumber(model.rawRatios.audioRatio);
|
||||||
}
|
}
|
||||||
if (hasValue(model.rawRatios.audioCompletionRatio)) {
|
if (hasValue(model.rawRatios.audioCompletionRatio)) {
|
||||||
result.AudioCompletionRatio = Number(model.rawRatios.audioCompletionRatio);
|
result.AudioCompletionRatio = toNormalizedNumber(
|
||||||
|
model.rawRatios.audioCompletionRatio,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.ModelRatio = inputPrice / 2;
|
result.ModelRatio = toNormalizedNumber(inputPrice / 2);
|
||||||
|
|
||||||
if (!model.completionRatioLocked && completionPrice !== null) {
|
if (!model.completionRatioLocked && completionPrice !== null) {
|
||||||
result.CompletionRatio = completionPrice / inputPrice;
|
result.CompletionRatio = toNormalizedNumber(completionPrice / inputPrice);
|
||||||
} else if (
|
} else if (
|
||||||
model.completionRatioLocked &&
|
model.completionRatioLocked &&
|
||||||
hasValue(model.rawRatios.completionRatio)
|
hasValue(model.rawRatios.completionRatio)
|
||||||
) {
|
) {
|
||||||
result.CompletionRatio = Number(model.rawRatios.completionRatio);
|
result.CompletionRatio = toNormalizedNumber(
|
||||||
|
model.rawRatios.completionRatio,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (cachePrice !== null) {
|
if (cachePrice !== null) {
|
||||||
result.CacheRatio = cachePrice / inputPrice;
|
result.CacheRatio = toNormalizedNumber(cachePrice / inputPrice);
|
||||||
}
|
}
|
||||||
if (createCachePrice !== null) {
|
if (createCachePrice !== null) {
|
||||||
result.CreateCacheRatio = createCachePrice / inputPrice;
|
result.CreateCacheRatio = toNormalizedNumber(createCachePrice / inputPrice);
|
||||||
}
|
}
|
||||||
if (imagePrice !== null) {
|
if (imagePrice !== null) {
|
||||||
result.ImageRatio = imagePrice / inputPrice;
|
result.ImageRatio = toNormalizedNumber(imagePrice / inputPrice);
|
||||||
}
|
}
|
||||||
if (audioInputPrice !== null) {
|
if (audioInputPrice !== null) {
|
||||||
result.AudioRatio = audioInputPrice / inputPrice;
|
result.AudioRatio = toNormalizedNumber(audioInputPrice / inputPrice);
|
||||||
}
|
}
|
||||||
if (audioOutputPrice !== null) {
|
if (audioOutputPrice !== null) {
|
||||||
if (audioInputPrice === null || audioInputPrice === 0) {
|
if (audioInputPrice === null || audioInputPrice === 0) {
|
||||||
@@ -356,7 +385,9 @@ const serializeModel = (model, t) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
result.AudioCompletionRatio = audioOutputPrice / audioInputPrice;
|
result.AudioCompletionRatio = toNormalizedNumber(
|
||||||
|
audioOutputPrice / audioInputPrice,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -455,7 +486,8 @@ export const buildPreviewRows = (model, t) => {
|
|||||||
{
|
{
|
||||||
key: 'CacheRatio',
|
key: 'CacheRatio',
|
||||||
label: 'CacheRatio',
|
label: 'CacheRatio',
|
||||||
value: cachePrice !== null ? formatNumber(cachePrice / inputPrice) : t('空'),
|
value:
|
||||||
|
cachePrice !== null ? formatNumber(cachePrice / inputPrice) : t('空'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'CreateCacheRatio',
|
key: 'CreateCacheRatio',
|
||||||
@@ -468,7 +500,8 @@ export const buildPreviewRows = (model, t) => {
|
|||||||
{
|
{
|
||||||
key: 'ImageRatio',
|
key: 'ImageRatio',
|
||||||
label: 'ImageRatio',
|
label: 'ImageRatio',
|
||||||
value: imagePrice !== null ? formatNumber(imagePrice / inputPrice) : t('空'),
|
value:
|
||||||
|
imagePrice !== null ? formatNumber(imagePrice / inputPrice) : t('空'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'AudioRatio',
|
key: 'AudioRatio',
|
||||||
@@ -482,7 +515,9 @@ export const buildPreviewRows = (model, t) => {
|
|||||||
key: 'AudioCompletionRatio',
|
key: 'AudioCompletionRatio',
|
||||||
label: 'AudioCompletionRatio',
|
label: 'AudioCompletionRatio',
|
||||||
value:
|
value:
|
||||||
audioOutputPrice !== null && audioInputPrice !== null && audioInputPrice !== 0
|
audioOutputPrice !== null &&
|
||||||
|
audioInputPrice !== null &&
|
||||||
|
audioInputPrice !== 0
|
||||||
? formatNumber(audioOutputPrice / audioInputPrice)
|
? formatNumber(audioOutputPrice / audioInputPrice)
|
||||||
: t('空'),
|
: t('空'),
|
||||||
},
|
},
|
||||||
@@ -585,7 +620,8 @@ export function useModelPricingEditorState({
|
|||||||
}, [currentPage, filteredModels]);
|
}, [currentPage, filteredModels]);
|
||||||
|
|
||||||
const selectedModel = useMemo(
|
const selectedModel = useMemo(
|
||||||
() => visibleModels.find((model) => model.name === selectedModelName) || null,
|
() =>
|
||||||
|
visibleModels.find((model) => model.name === selectedModelName) || null,
|
||||||
[selectedModelName, visibleModels],
|
[selectedModelName, visibleModels],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -605,7 +641,9 @@ export function useModelPricingEditorState({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedModelNames((previous) =>
|
setSelectedModelNames((previous) =>
|
||||||
previous.filter((name) => visibleModels.some((model) => model.name === name)),
|
previous.filter((name) =>
|
||||||
|
visibleModels.some((model) => model.name === name),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}, [visibleModels]);
|
}, [visibleModels]);
|
||||||
|
|
||||||
@@ -779,7 +817,9 @@ export function useModelPricingEditorState({
|
|||||||
delete next[name];
|
delete next[name];
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
setSelectedModelNames((previous) => previous.filter((item) => item !== name));
|
setSelectedModelNames((previous) =>
|
||||||
|
previous.filter((item) => item !== name),
|
||||||
|
);
|
||||||
if (selectedModelName === name) {
|
if (selectedModelName === name) {
|
||||||
setSelectedModelName(nextModels[0]?.name || '');
|
setSelectedModelName(nextModels[0]?.name || '');
|
||||||
}
|
}
|
||||||
@@ -823,7 +863,8 @@ export function useModelPricingEditorState({
|
|||||||
hasValue(nextModel.lockedCompletionRatio)
|
hasValue(nextModel.lockedCompletionRatio)
|
||||||
) {
|
) {
|
||||||
nextModel.completionPrice = formatNumber(
|
nextModel.completionPrice = formatNumber(
|
||||||
Number(nextModel.inputPrice) * Number(nextModel.lockedCompletionRatio),
|
Number(nextModel.inputPrice) *
|
||||||
|
Number(nextModel.lockedCompletionRatio),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user