修复模型广场图片模型价格展示与实收口径不一致

图片计费模型的广场展示价按实收口径计算:档位单价取
分组图片价 > 渠道档位价 > 渠道默认按次价;分组开启生图
独立倍率时实付倍率取独立倍率,不取分组/专属倍率。
This commit is contained in:
feeeei
2026-08-04 13:48:19 +08:00
parent 825ca7b1fc
commit 785b61d424
8 changed files with 280 additions and 50 deletions
+3
View File
@@ -40,6 +40,9 @@ export interface ModelPlazaGroup {
peak_end: string
peak_rate_multiplier: number
is_exclusive: boolean
/** 生图独立倍率:true 时图片计费模型的实付倍率取 image_rate_multiplier,不取分组/专属倍率。 */
image_rate_independent: boolean
image_rate_multiplier: number
models: PlazaModel[]
}
@@ -52,6 +52,8 @@
:platform="group.platform"
:rate-multiplier="group.rate_multiplier"
:user-rate-multiplier="group.user_rate_multiplier ?? null"
:image-rate-independent="group.image_rate_independent"
:image-rate-multiplier="group.image_rate_multiplier"
/>
<p v-else class="px-5 py-4 text-center text-sm text-gray-400 dark:text-dark-500">
{{ t('modelPlaza.detail.noModels') }}
@@ -134,13 +134,13 @@
class="inline-flex items-center gap-1 rounded-md bg-gray-100 px-2 py-0.5 font-mono text-xs text-gray-800 dark:bg-dark-700/60 dark:text-gray-200"
>
<span class="font-sans text-gray-400 dark:text-dark-500">{{ tierLabel(iv) }}</span>
{{ paidRequestPrice(iv.per_request_price)
{{ paidRequestPrice(m, iv.per_request_price)
}}<span class="font-sans text-gray-400 dark:text-dark-500">{{ perUnitSuffix(m) }}</span>
</span>
</div>
<template v-else-if="m.pricing?.per_request_price != null">
<span class="font-mono font-semibold text-gray-900 dark:text-gray-50">
{{ paidRequestPrice(m.pricing.per_request_price) }}
{{ paidRequestPrice(m, m.pricing.per_request_price) }}
</span>
<span class="ml-1 text-xs text-gray-400 dark:text-dark-500">{{ perUnitSuffix(m) }}</span>
</template>
@@ -178,11 +178,16 @@
<span v-else class="text-gray-400 dark:text-dark-500">-</span>
</td>
<!-- 折扣倍率(专属倍率划线展示原倍率) -->
<!-- 折扣倍率(生图独立倍率行展示独立倍率;专属倍率划线展示原倍率) -->
<td
class="border-l border-gray-100 py-2.5 pl-3 pr-5 text-right align-middle font-mono text-xs dark:border-dark-700/60"
>
<template v-if="hasCustomRate">
<span
v-if="usesIndependentImageRate(m)"
class="font-bold text-gray-700 dark:text-gray-300"
>{{ requestRate(m) }}x</span
>
<template v-else-if="hasCustomRate">
<span class="mr-1 text-gray-400 line-through dark:text-dark-500">{{ rateMultiplier }}x</span>
<span class="font-bold text-primary-600 dark:text-primary-400">{{ effectiveRate }}x</span>
</template>
@@ -215,6 +220,9 @@ const props = defineProps<{
rateMultiplier: number
/** 用户专属倍率;与默认不同,实付价按此计算并划线展示原倍率。 */
userRateMultiplier?: number | null
/** 生图独立倍率:true 时图片计费模型的实付倍率取 imageRateMultiplier,不取分组/专属倍率。 */
imageRateIndependent?: boolean
imageRateMultiplier?: number | null
}>()
const { t } = useI18n()
@@ -268,10 +276,20 @@ function paidPerMillion(value: number | null | undefined): string {
return formatScaled(value * effectiveRate.value, PER_MILLION, MIN_DECIMALS)
}
/** 按次 / 按图片单价(乘生效倍率,不换算 1M)。 */
function paidRequestPrice(value: number | null | undefined): string {
/** 图片计费模型且分组开启生图独立倍率:实付倍率取独立倍率,与计费口径一致。 */
function usesIndependentImageRate(m: PlazaModel): boolean {
return billingMode(m) === BILLING_MODE_IMAGE && props.imageRateIndependent === true
}
/** 按次/按图片行的生效倍率。 */
function requestRate(m: PlazaModel): number {
return usesIndependentImageRate(m) ? (props.imageRateMultiplier ?? 1) : effectiveRate.value
}
/** 按次 / 按图片单价(乘该行生效倍率,不换算 1M)。 */
function paidRequestPrice(m: PlazaModel, value: number | null | undefined): string {
if (value == null) return '-'
return formatScaled(value * effectiveRate.value, 1, MIN_DECIMALS)
return formatScaled(value * requestRate(m), 1, MIN_DECIMALS)
}
/** 官方参考价不乘倍率。 */
@@ -39,9 +39,14 @@ function tokenModel(overrides: Partial<PlazaModel> = {}): PlazaModel {
}
}
function mountTable(models: PlazaModel[], rateMultiplier: number, userRateMultiplier?: number | null) {
function mountTable(
models: PlazaModel[],
rateMultiplier: number,
userRateMultiplier?: number | null,
extraProps?: { imageRateIndependent?: boolean; imageRateMultiplier?: number | null }
) {
return mount(PlazaModelPricingTable, {
props: { models, rateMultiplier, userRateMultiplier: userRateMultiplier ?? null }
props: { models, rateMultiplier, userRateMultiplier: userRateMultiplier ?? null, ...extraProps }
})
}
@@ -265,6 +270,69 @@ describe('PlazaModelPricingTable', () => {
expect(text).toContain('$15.00')
})
it('生图独立倍率开启时,按图价格 × 独立倍率,不乘分组倍率;倍率列展示独立倍率', () => {
const model = tokenModel({
name: 'gpt-image-2',
pricing: {
billing_mode: 'image',
input_price: null,
output_price: null,
cache_write_price: null,
cache_read_price: null,
image_input_price: null,
image_output_price: null,
per_request_price: null,
intervals: [
{
min_tokens: 0,
max_tokens: null,
tier_label: '1K',
input_price: null,
output_price: null,
cache_write_price: null,
cache_read_price: null,
per_request_price: 0.02
}
]
},
official_pricing: null
})
const wrapper = mountTable([model], 0.1, null, {
imageRateIndependent: true,
imageRateMultiplier: 1
})
const text = wrapper.text()
// 0.02 × 1(独立倍率),而非 0.02 × 0.1
expect(text).toContain('$0.02')
expect(text).not.toContain('$0.002')
// 倍率列展示独立倍率 1x,而非分组倍率 0.1x
const rateCell = wrapper.findAll('tbody tr td').at(-1)!
expect(rateCell.text()).toBe('1x')
})
it('生图独立倍率关闭时,按图价格仍乘分组/专属生效倍率', () => {
const model = tokenModel({
name: 'gpt-image-2',
pricing: {
billing_mode: 'image',
input_price: null,
output_price: null,
cache_write_price: null,
cache_read_price: null,
image_input_price: null,
image_output_price: null,
per_request_price: 0.2,
intervals: []
},
official_pricing: null
})
const wrapper = mountTable([model], 0.1, null, { imageRateIndependent: false })
const text = wrapper.text()
expect(text).toContain('$0.02')
const rateCell = wrapper.findAll('tbody tr td').at(-1)!
expect(rateCell.text()).toBe('0.1x')
})
it('按图模型主行展示阶梯芯片,不把 image_output_price(每 token)当按次价', () => {
const model = tokenModel({
name: 'gpt-image-2',