mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 15:38:22 +08:00
Merge pull request #5258 from feeeei/fix/model_plaza
fix(model-plaza): Model Plaza image model price display is inconsistent with the actual price
This commit is contained in:
@@ -54,19 +54,23 @@ type modelPlazaModel struct {
|
||||
|
||||
// modelPlazaGroup 广场分组条目(白名单字段)。
|
||||
type modelPlazaGroup struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Platform string `json:"platform"`
|
||||
SubscriptionType string `json:"subscription_type"`
|
||||
RateMultiplier float64 `json:"rate_multiplier"`
|
||||
UserRateMultiplier *float64 `json:"user_rate_multiplier,omitempty"`
|
||||
PeakRateEnabled bool `json:"peak_rate_enabled"`
|
||||
PeakStart string `json:"peak_start"`
|
||||
PeakEnd string `json:"peak_end"`
|
||||
PeakRateMultiplier float64 `json:"peak_rate_multiplier"`
|
||||
IsExclusive bool `json:"is_exclusive"`
|
||||
Models []modelPlazaModel `json:"models"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Platform string `json:"platform"`
|
||||
SubscriptionType string `json:"subscription_type"`
|
||||
RateMultiplier float64 `json:"rate_multiplier"`
|
||||
UserRateMultiplier *float64 `json:"user_rate_multiplier,omitempty"`
|
||||
PeakRateEnabled bool `json:"peak_rate_enabled"`
|
||||
PeakStart string `json:"peak_start"`
|
||||
PeakEnd string `json:"peak_end"`
|
||||
PeakRateMultiplier float64 `json:"peak_rate_multiplier"`
|
||||
IsExclusive bool `json:"is_exclusive"`
|
||||
// 生图独立倍率:为 true 时图片计费模型的实付倍率取 ImageRateMultiplier,
|
||||
// 不取分组/用户专属倍率。
|
||||
ImageRateIndependent bool `json:"image_rate_independent"`
|
||||
ImageRateMultiplier float64 `json:"image_rate_multiplier"`
|
||||
Models []modelPlazaModel `json:"models"`
|
||||
}
|
||||
|
||||
// modelPlazaResponse 广场页响应。
|
||||
@@ -164,18 +168,20 @@ func toModelPlazaGroupDTO(g *service.PlazaGroup, userRates map[int64]float64) mo
|
||||
})
|
||||
}
|
||||
dto := modelPlazaGroup{
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
Description: g.Description,
|
||||
Platform: g.Platform,
|
||||
SubscriptionType: g.SubscriptionType,
|
||||
RateMultiplier: g.RateMultiplier,
|
||||
PeakRateEnabled: g.PeakRateEnabled,
|
||||
PeakStart: g.PeakStart,
|
||||
PeakEnd: g.PeakEnd,
|
||||
PeakRateMultiplier: g.PeakRateMultiplier,
|
||||
IsExclusive: g.IsExclusive,
|
||||
Models: models,
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
Description: g.Description,
|
||||
Platform: g.Platform,
|
||||
SubscriptionType: g.SubscriptionType,
|
||||
RateMultiplier: g.RateMultiplier,
|
||||
PeakRateEnabled: g.PeakRateEnabled,
|
||||
PeakStart: g.PeakStart,
|
||||
PeakEnd: g.PeakEnd,
|
||||
PeakRateMultiplier: g.PeakRateMultiplier,
|
||||
IsExclusive: g.IsExclusive,
|
||||
ImageRateIndependent: g.ImageRateIndependent,
|
||||
ImageRateMultiplier: g.ImageRateMultiplier,
|
||||
Models: models,
|
||||
}
|
||||
if rate, ok := userRates[g.ID]; ok {
|
||||
dto.UserRateMultiplier = &rate
|
||||
|
||||
@@ -91,6 +91,7 @@ func TestToModelPlazaGroupDTO_UserRateAndFieldWhitelist(t *testing.T) {
|
||||
"id", "name", "description", "platform", "subscription_type",
|
||||
"rate_multiplier", "user_rate_multiplier", "is_exclusive", "models",
|
||||
"peak_rate_enabled", "peak_start", "peak_end", "peak_rate_multiplier",
|
||||
"image_rate_independent", "image_rate_multiplier",
|
||||
} {
|
||||
_, exists := decoded[key]
|
||||
require.Truef(t, exists, "plaza group DTO must expose %q", key)
|
||||
|
||||
@@ -41,7 +41,11 @@ type PlazaGroup struct {
|
||||
PeakEnd string
|
||||
PeakRateMultiplier float64
|
||||
IsExclusive bool
|
||||
Models []PlazaModel
|
||||
// 图片按次实付倍率:ImageRateIndependent 为 true 时,图片计费模型的实付
|
||||
// = 档位价 × ImageRateMultiplier,不乘分组/用户专属倍率(与计费口径一致)。
|
||||
ImageRateIndependent bool
|
||||
ImageRateMultiplier float64
|
||||
Models []PlazaModel
|
||||
}
|
||||
|
||||
// ListPlazaGroups 返回模型广场数据:每个活跃分组附带其可用模型与定价。
|
||||
@@ -50,6 +54,8 @@ type PlazaGroup struct {
|
||||
// 平台隔离),仅把顶层从渠道换成分组:
|
||||
// - 渠道按 lower(name) 排序后遍历,保证同名模型去重结果确定;
|
||||
// - 同分组同名模型「先见者胜」,仅当已存条目无定价而新条目有定价时升级替换;
|
||||
// - 图片计费模型的档位价按实收口径合成(分组图片价 > 渠道档位价 > 渠道默认按次价,
|
||||
// 见 plazaImageDisplayPricing);
|
||||
// - 每个模型附带 LiteLLM 官方参考价(查不到为 nil);
|
||||
// - 只返回 Models 非空的分组;分组按 RateMultiplier 升序(同倍率按名称),
|
||||
// 组内模型按名称排序。
|
||||
@@ -70,22 +76,26 @@ func (s *ChannelService) ListPlazaGroups(ctx context.Context) ([]PlazaGroup, err
|
||||
})
|
||||
|
||||
byGroup := make(map[int64]*PlazaGroup, len(groups))
|
||||
groupEnt := make(map[int64]*Group, len(groups))
|
||||
order := make([]int64, 0, len(groups))
|
||||
for i := range groups {
|
||||
g := groups[i]
|
||||
g := &groups[i]
|
||||
byGroup[g.ID] = &PlazaGroup{
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
Description: g.Description,
|
||||
Platform: g.Platform,
|
||||
SubscriptionType: g.SubscriptionType,
|
||||
RateMultiplier: g.RateMultiplier,
|
||||
PeakRateEnabled: g.PeakRateEnabled,
|
||||
PeakStart: g.PeakStart,
|
||||
PeakEnd: g.PeakEnd,
|
||||
PeakRateMultiplier: g.PeakRateMultiplier,
|
||||
IsExclusive: g.IsExclusive,
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
Description: g.Description,
|
||||
Platform: g.Platform,
|
||||
SubscriptionType: g.SubscriptionType,
|
||||
RateMultiplier: g.RateMultiplier,
|
||||
PeakRateEnabled: g.PeakRateEnabled,
|
||||
PeakStart: g.PeakStart,
|
||||
PeakEnd: g.PeakEnd,
|
||||
PeakRateMultiplier: g.PeakRateMultiplier,
|
||||
IsExclusive: g.IsExclusive,
|
||||
ImageRateIndependent: g.ImageRateIndependent,
|
||||
ImageRateMultiplier: g.ImageRateMultiplier,
|
||||
}
|
||||
groupEnt[g.ID] = g
|
||||
order = append(order, g.ID)
|
||||
}
|
||||
|
||||
@@ -115,10 +125,11 @@ func (s *ChannelService) ListPlazaGroups(ctx context.Context) ([]PlazaGroup, err
|
||||
if m.Platform != pg.Platform {
|
||||
continue
|
||||
}
|
||||
pricing := plazaImageDisplayPricing(m.Pricing, groupEnt[gid])
|
||||
if at, seen := idx[m.Name]; seen {
|
||||
// 先见者胜;仅当已存条目无定价而新条目有定价时升级。
|
||||
if pg.Models[at].Pricing == nil && m.Pricing != nil {
|
||||
pg.Models[at].Pricing = m.Pricing
|
||||
if pg.Models[at].Pricing == nil && pricing != nil {
|
||||
pg.Models[at].Pricing = pricing
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -126,7 +137,7 @@ func (s *ChannelService) ListPlazaGroups(ctx context.Context) ([]PlazaGroup, err
|
||||
pg.Models = append(pg.Models, PlazaModel{
|
||||
Name: m.Name,
|
||||
Platform: m.Platform,
|
||||
Pricing: m.Pricing,
|
||||
Pricing: pricing,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -155,6 +166,53 @@ func (s *ChannelService) ListPlazaGroups(ctx context.Context) ([]PlazaGroup, err
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// plazaImageDisplayPricing 为图片计费模型合成展示定价,使档位价与实收口径一致:
|
||||
// 每档(1K/2K/4K)单价 = 分组图片价 > 渠道同档位价 > 渠道默认按次价,无价的档不展示。
|
||||
// 分组未配任何图片价、或定价非图片模式时原样返回。返回克隆,不修改入参
|
||||
// (渠道定价指针指向缓存共享数据)。
|
||||
func plazaImageDisplayPricing(p *ChannelModelPricing, g *Group) *ChannelModelPricing {
|
||||
if p == nil || g == nil || p.BillingMode != BillingModeImage {
|
||||
return p
|
||||
}
|
||||
if g.ImagePrice1K == nil && g.ImagePrice2K == nil && g.ImagePrice4K == nil {
|
||||
return p
|
||||
}
|
||||
channelTierPrice := func(label string) *float64 {
|
||||
for i := range p.Intervals {
|
||||
if p.Intervals[i].TierLabel == label && p.Intervals[i].PerRequestPrice != nil {
|
||||
return p.Intervals[i].PerRequestPrice
|
||||
}
|
||||
}
|
||||
return p.PerRequestPrice
|
||||
}
|
||||
tiers := []struct {
|
||||
label string
|
||||
groupPrice *float64
|
||||
}{
|
||||
{"1K", g.ImagePrice1K},
|
||||
{"2K", g.ImagePrice2K},
|
||||
{"4K", g.ImagePrice4K},
|
||||
}
|
||||
clone := *p
|
||||
clone.Intervals = make([]PricingInterval, 0, len(tiers))
|
||||
for i, t := range tiers {
|
||||
price := t.groupPrice
|
||||
if price == nil {
|
||||
price = channelTierPrice(t.label)
|
||||
}
|
||||
if price == nil {
|
||||
continue
|
||||
}
|
||||
v := *price
|
||||
clone.Intervals = append(clone.Intervals, PricingInterval{
|
||||
TierLabel: t.label,
|
||||
PerRequestPrice: &v,
|
||||
SortOrder: i,
|
||||
})
|
||||
}
|
||||
return &clone
|
||||
}
|
||||
|
||||
// lookupOfficialPricing 查询模型的 LiteLLM 官方参考价,带 memo 避免同名模型重复转换。
|
||||
// pricingService 为 nil(测试场景)或查不到时返回 nil。
|
||||
func (s *ChannelService) lookupOfficialPricing(modelName string, memo map[string]*PlazaOfficialPricing) *PlazaOfficialPricing {
|
||||
|
||||
@@ -173,6 +173,80 @@ func TestListPlazaGroups_OfficialPricingFill(t *testing.T) {
|
||||
require.Nil(t, byName["token-absent"].OfficialPricing)
|
||||
}
|
||||
|
||||
func TestListPlazaGroups_GroupImagePriceOverridesChannelPricing(t *testing.T) {
|
||||
// 图片计费模型:档位价按实收口径合成(分组图片价 > 渠道档位价 > 渠道默认按次价),
|
||||
// 分组独立倍率字段透传;未配图片价的分组保持渠道定价原样。
|
||||
perReq := 0.2
|
||||
tier4K := 0.3
|
||||
imgPrice := 0.02
|
||||
channels := []Channel{{
|
||||
ID: 1, Name: "img-ch", Status: StatusActive, GroupIDs: []int64{10, 20},
|
||||
ModelPricing: []ChannelModelPricing{{
|
||||
Platform: "openai",
|
||||
Models: []string{"gpt-image-2"},
|
||||
BillingMode: BillingModeImage,
|
||||
PerRequestPrice: &perReq,
|
||||
Intervals: []PricingInterval{{TierLabel: "4K", PerRequestPrice: &tier4K}},
|
||||
}},
|
||||
}}
|
||||
groups := []Group{
|
||||
{ID: 10, Name: "g-media", Platform: "openai", RateMultiplier: 1,
|
||||
ImagePrice1K: &imgPrice, ImageRateIndependent: true, ImageRateMultiplier: 1},
|
||||
{ID: 20, Name: "g-plain", Platform: "openai", RateMultiplier: 0.1},
|
||||
}
|
||||
svc := newPlazaChannelService(channels, groups, nil)
|
||||
out, err := svc.ListPlazaGroups(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, out, 2)
|
||||
byName := map[string]PlazaGroup{}
|
||||
for _, g := range out {
|
||||
byName[g.Name] = g
|
||||
}
|
||||
|
||||
media := byName["g-media"]
|
||||
require.True(t, media.ImageRateIndependent)
|
||||
require.InDelta(t, 1.0, media.ImageRateMultiplier, 1e-9)
|
||||
require.Len(t, media.Models, 1)
|
||||
p := media.Models[0].Pricing
|
||||
require.NotNil(t, p)
|
||||
require.Len(t, p.Intervals, 3)
|
||||
tierPrices := map[string]float64{}
|
||||
for _, iv := range p.Intervals {
|
||||
require.NotNil(t, iv.PerRequestPrice)
|
||||
tierPrices[iv.TierLabel] = *iv.PerRequestPrice
|
||||
}
|
||||
require.InDelta(t, 0.02, tierPrices["1K"], 1e-9, "1K 用分组图片价")
|
||||
require.InDelta(t, 0.2, tierPrices["2K"], 1e-9, "2K 分组未配,回落渠道默认按次价")
|
||||
require.InDelta(t, 0.3, tierPrices["4K"], 1e-9, "4K 分组未配,回落渠道档位价")
|
||||
|
||||
plain := byName["g-plain"]
|
||||
require.False(t, plain.ImageRateIndependent)
|
||||
require.Len(t, plain.Models, 1)
|
||||
pp := plain.Models[0].Pricing
|
||||
require.NotNil(t, pp)
|
||||
require.Len(t, pp.Intervals, 1, "未配分组图片价:渠道定价原样")
|
||||
require.InDelta(t, 0.2, *pp.PerRequestPrice, 1e-9)
|
||||
|
||||
// 合成为克隆,渠道原始定价不被修改
|
||||
require.Len(t, channels[0].ModelPricing[0].Intervals, 1)
|
||||
}
|
||||
|
||||
func TestListPlazaGroups_GroupImagePriceIgnoredForNonImageModes(t *testing.T) {
|
||||
// token 模式定价不受分组图片价影响。
|
||||
imgPrice := 0.02
|
||||
channels := []Channel{plazaPricedChannel(1, "ch", []int64{10}, "openai", "gpt-5")}
|
||||
groups := []Group{{ID: 10, Name: "g", Platform: "openai", RateMultiplier: 1, ImagePrice1K: &imgPrice}}
|
||||
svc := newPlazaChannelService(channels, groups, nil)
|
||||
out, err := svc.ListPlazaGroups(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, out, 1)
|
||||
p := out[0].Models[0].Pricing
|
||||
require.NotNil(t, p)
|
||||
require.Empty(t, p.Intervals)
|
||||
require.NotNil(t, p.InputPrice)
|
||||
require.Nil(t, p.PerRequestPrice)
|
||||
}
|
||||
|
||||
func TestListPlazaGroups_RepoErrorsPropagate(t *testing.T) {
|
||||
sentinel := errors.New("boom")
|
||||
repo := &mockChannelRepository{
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user