mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
fix: 长上下文默认保持开启,并与 OpenAI 账号开关取交集
迁移默认 false 会让存量分组静默丢掉 ≥200k 阶梯与渠道多档价。 OpenAI 路径传入 Group 后,账号级 openai_long_context_billing_enabled 被顶掉。 - 列默认 TRUE,并对存量行回填 - 分组价卡不再手填 token 区间,勾选走官方/预设阶梯 - 分组开关与账号开关 AND;价卡 JSON 损坏时打 warn
This commit is contained in:
@@ -934,7 +934,7 @@ var (
|
||||
{Name: "audio_realtime_price_per_min", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "audio_tts_price_per_million_chars", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "audio_stt_price_per_hour", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "long_context_pricing_enabled", Type: field.TypeBool, Default: false},
|
||||
{Name: "long_context_pricing_enabled", Type: field.TypeBool, Default: true},
|
||||
{Name: "model_pricing", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||
{Name: "claude_code_only", Type: field.TypeBool, Default: false},
|
||||
{Name: "fallback_group_id", Type: field.TypeInt64, Nullable: true},
|
||||
|
||||
@@ -188,8 +188,8 @@ func (Group) Fields() []ent.Field {
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||
Comment("STT 每小时价格(USD)"),
|
||||
field.Bool("long_context_pricing_enabled").
|
||||
Default(false).
|
||||
Comment("是否按上下文长度应用模型阶梯价格"),
|
||||
Default(true).
|
||||
Comment("是否按上下文长度应用模型阶梯价格;默认开启以保持官方/渠道长上下文价"),
|
||||
field.JSON("model_pricing", json.RawMessage{}).
|
||||
Optional().
|
||||
SchemaType(map[string]string{dialect.Postgres: "jsonb"}).
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -951,7 +952,11 @@ func groupEntityToService(g *dbent.Group) *service.Group {
|
||||
}
|
||||
var modelPricing []service.ChannelModelPricing
|
||||
if len(g.ModelPricing) > 0 {
|
||||
_ = json.Unmarshal(g.ModelPricing, &modelPricing)
|
||||
if err := json.Unmarshal(g.ModelPricing, &modelPricing); err != nil {
|
||||
slog.Warn("group model_pricing unmarshal failed; falling back to channel/builtin pricing",
|
||||
"group_id", g.ID, "error", err)
|
||||
modelPricing = nil
|
||||
}
|
||||
}
|
||||
return &service.Group{
|
||||
ID: g.ID,
|
||||
|
||||
@@ -71,11 +71,15 @@ type PricingInput struct {
|
||||
func (r *ModelPricingResolver) Resolve(ctx context.Context, input PricingInput) *ResolvedPricing {
|
||||
longContextPricingEnabled := input.Group == nil || input.Group.LongContextPricingEnabled
|
||||
if groupPricing := matchGroupModelPricing(input.Group, input.Model); groupPricing != nil {
|
||||
// Group token cards only override the first-tier / flat rates.
|
||||
// Long-context ladders come from official presets, gated by the checkbox.
|
||||
if groupPricing.BillingMode == "" || groupPricing.BillingMode == BillingModeToken {
|
||||
stripped := groupPricing.Clone()
|
||||
stripped.Intervals = nil
|
||||
groupPricing = &stripped
|
||||
}
|
||||
resolved := r.resolveConfiguredPricing(groupPricing, input.Model, PricingSourceGroup)
|
||||
resolved.longContextPricingEnabled = longContextPricingEnabled
|
||||
if resolved.Mode == BillingModeToken && !longContextPricingEnabled {
|
||||
r.applyFirstTokenTier(resolved, groupPricing)
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
@@ -119,7 +123,7 @@ func (r *ModelPricingResolver) Resolve(ctx context.Context, input PricingInput)
|
||||
if !longContextPricingEnabled {
|
||||
r.applyFirstTokenTier(resolved, chPricing)
|
||||
}
|
||||
} else if input.GroupID != nil {
|
||||
} else if input.GroupID != nil && r.channelService != nil {
|
||||
r.applyChannelOverrides(ctx, *input.GroupID, input.Model, resolved)
|
||||
if resolved.Source == PricingSourceChannel && !longContextPricingEnabled {
|
||||
r.applyFirstTokenTier(resolved, resolved.channelPricing)
|
||||
|
||||
@@ -850,24 +850,35 @@ func TestResolve_GroupPricingOverridesChannel(t *testing.T) {
|
||||
require.InDelta(t, 2e-6, resolved.BasePricing.OutputPricePerToken, 1e-12)
|
||||
}
|
||||
|
||||
func TestResolve_GroupLongContextToggleControlsIntervals(t *testing.T) {
|
||||
r := NewModelPricingResolver(nil, newTestBillingServiceForResolver())
|
||||
func TestResolve_GroupLongContextUsesPresetNotCustomIntervals(t *testing.T) {
|
||||
bs := newTestBillingServiceForResolver()
|
||||
bs.fallbackPrices["claude-sonnet-4"].LongContextInputThreshold = 200000
|
||||
bs.fallbackPrices["claude-sonnet-4"].LongContextThresholdInclusive = true
|
||||
bs.fallbackPrices["claude-sonnet-4"].LongContextInputMultiplier = 2
|
||||
bs.fallbackPrices["claude-sonnet-4"].LongContextOutputMultiplier = 2
|
||||
r := NewModelPricingResolver(nil, bs)
|
||||
group := &Group{ID: 100, ModelPricing: []ChannelModelPricing{{
|
||||
Models: []string{"claude-sonnet-4"}, BillingMode: BillingModeToken,
|
||||
InputPrice: testPtrFloat64(1e-6), OutputPrice: testPtrFloat64(2e-6),
|
||||
Intervals: []PricingInterval{
|
||||
{MinTokens: 0, MaxTokens: testPtrInt(200000), InputPrice: testPtrFloat64(1e-6)},
|
||||
{MinTokens: 200000, InputPrice: testPtrFloat64(2e-6)},
|
||||
{MinTokens: 0, MaxTokens: testPtrInt(200000), InputPrice: testPtrFloat64(9e-6)},
|
||||
{MinTokens: 200000, InputPrice: testPtrFloat64(18e-6)},
|
||||
},
|
||||
}}}
|
||||
|
||||
resolved := r.Resolve(context.Background(), PricingInput{Model: "claude-sonnet-4", Group: group})
|
||||
require.Empty(t, resolved.Intervals)
|
||||
require.False(t, resolved.longContextPricingEnabled)
|
||||
require.Empty(t, resolved.Intervals, "group token intervals are not a user-facing long-context ladder")
|
||||
require.InDelta(t, 1e-6, r.GetIntervalPricing(resolved, 300000).InputPricePerToken, 1e-12)
|
||||
require.Equal(t, 200000, resolved.BasePricing.LongContextInputThreshold)
|
||||
|
||||
group.LongContextPricingEnabled = true
|
||||
resolved = r.Resolve(context.Background(), PricingInput{Model: "claude-sonnet-4", Group: group})
|
||||
require.Len(t, resolved.Intervals, 2)
|
||||
require.InDelta(t, 2e-6, r.GetIntervalPricing(resolved, 300000).InputPricePerToken, 1e-12)
|
||||
require.True(t, resolved.longContextPricingEnabled)
|
||||
require.Empty(t, resolved.Intervals)
|
||||
require.InDelta(t, 1e-6, r.GetIntervalPricing(resolved, 300000).InputPricePerToken, 1e-12)
|
||||
require.Equal(t, 200000, resolved.BasePricing.LongContextInputThreshold)
|
||||
require.InDelta(t, 2.0, resolved.BasePricing.LongContextInputMultiplier, 1e-12)
|
||||
}
|
||||
|
||||
func TestCalculateCostUnified_UsesContinuousMediaUnits(t *testing.T) {
|
||||
|
||||
@@ -256,6 +256,17 @@ func newOpenAIRecordUsageServiceForTest(usageRepo UsageLogRepository, userRepo U
|
||||
return svc
|
||||
}
|
||||
|
||||
func openAIRecordUsageAPIKeyWithGroup(svc *OpenAIGatewayService, id int64, groupLongContext bool) *APIKey {
|
||||
svc.resolver = NewModelPricingResolver(nil, svc.billingService)
|
||||
return &APIKey{
|
||||
ID: id,
|
||||
Group: &Group{
|
||||
ID: 1,
|
||||
LongContextPricingEnabled: groupLongContext,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newOpenAIRecordUsageServiceWithBillingRepoForTest(usageRepo UsageLogRepository, billingRepo UsageBillingRepository, userRepo UserRepository, subRepo UserSubscriptionRepository, rateRepo UserGroupRateRepository) *OpenAIGatewayService {
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, userRepo, subRepo, rateRepo)
|
||||
svc.usageBillingRepo = billingRepo
|
||||
@@ -1088,7 +1099,7 @@ func TestOpenAIGatewayServiceRecordUsage_Gpt54LongContextBillingDisabledByDefaul
|
||||
Model: "gpt-5.4-2026-03-05",
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{ID: 1014},
|
||||
APIKey: openAIRecordUsageAPIKeyWithGroup(svc, 1014, true),
|
||||
User: &User{ID: 2014},
|
||||
Account: &Account{ID: 3014, Platform: PlatformOpenAI},
|
||||
})
|
||||
@@ -1122,7 +1133,7 @@ func TestOpenAIGatewayServiceRecordUsage_Gpt54LongContextBillingEnabledPerAccoun
|
||||
Model: "gpt-5.4-2026-03-05",
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{ID: 1015},
|
||||
APIKey: openAIRecordUsageAPIKeyWithGroup(svc, 1015, true),
|
||||
User: &User{ID: 2015},
|
||||
Account: &Account{
|
||||
ID: 3015,
|
||||
@@ -1143,6 +1154,62 @@ func TestOpenAIGatewayServiceRecordUsage_Gpt54LongContextBillingEnabledPerAccoun
|
||||
require.True(t, usageRepo.lastLog.LongContextBillingApplied)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_GroupAndAccountLongContextMustBothAllow(t *testing.T) {
|
||||
tokens := OpenAIUsage{InputTokens: 300000, OutputTokens: 2000}
|
||||
baseInput := 300000 * 2.5e-6
|
||||
baseOutput := 2000 * 15e-6
|
||||
|
||||
t.Run("group on account off", func(t *testing.T) {
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{RequestID: "resp_and_off", Usage: tokens, Model: "gpt-5.4-2026-03-05", Duration: time.Second},
|
||||
APIKey: openAIRecordUsageAPIKeyWithGroup(svc, 1020, true),
|
||||
User: &User{ID: 2020},
|
||||
Account: &Account{ID: 3020, Platform: PlatformOpenAI},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.False(t, usageRepo.lastLog.LongContextBillingApplied)
|
||||
require.InDelta(t, baseInput, usageRepo.lastLog.InputCost, 1e-10)
|
||||
require.InDelta(t, baseOutput, usageRepo.lastLog.OutputCost, 1e-10)
|
||||
})
|
||||
|
||||
t.Run("group off account on", func(t *testing.T) {
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{RequestID: "resp_and_group_off", Usage: tokens, Model: "gpt-5.4-2026-03-05", Duration: time.Second},
|
||||
APIKey: openAIRecordUsageAPIKeyWithGroup(svc, 1021, false),
|
||||
User: &User{ID: 2021},
|
||||
Account: &Account{
|
||||
ID: 3021, Platform: PlatformOpenAI,
|
||||
Extra: map[string]any{"openai_long_context_billing_enabled": true},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.False(t, usageRepo.lastLog.LongContextBillingApplied)
|
||||
require.InDelta(t, baseInput, usageRepo.lastLog.InputCost, 1e-10)
|
||||
})
|
||||
|
||||
t.Run("group on account on", func(t *testing.T) {
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{RequestID: "resp_and_on", Usage: tokens, Model: "gpt-5.4-2026-03-05", Duration: time.Second},
|
||||
APIKey: openAIRecordUsageAPIKeyWithGroup(svc, 1022, true),
|
||||
User: &User{ID: 2022},
|
||||
Account: &Account{
|
||||
ID: 3022, Platform: PlatformOpenAI,
|
||||
Extra: map[string]any{"openai_long_context_billing_enabled": true},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, usageRepo.lastLog.LongContextBillingApplied)
|
||||
require.InDelta(t, baseInput*2, usageRepo.lastLog.InputCost, 1e-10)
|
||||
require.InDelta(t, baseOutput*1.5, usageRepo.lastLog.OutputCost, 1e-10)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_SparkShadowUsesCurrentParentBillingSetting(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1177,7 +1244,7 @@ func TestOpenAIGatewayServiceRecordUsage_SparkShadowUsesCurrentParentBillingSett
|
||||
Model: "gpt-5.4-2026-03-05",
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{ID: 1016},
|
||||
APIKey: openAIRecordUsageAPIKeyWithGroup(svc, 1016, true),
|
||||
User: &User{ID: 2016},
|
||||
Account: &Account{
|
||||
ID: 3016,
|
||||
|
||||
@@ -640,6 +640,7 @@ func (s *OpenAIGatewayService) calculateOpenAIRecordUsageTokenCost(
|
||||
Ctx: ctx, Model: billingModel, GroupID: &gid, Group: apiKey.Group,
|
||||
Tokens: tokens, RequestCount: 1, RateMultiplier: multiplier,
|
||||
ServiceTier: serviceTier, Resolver: s.resolver,
|
||||
LongContextBillingEnabled: &longContextBillingEnabled,
|
||||
})
|
||||
}
|
||||
return s.billingService.calculateCostWithServiceTierPolicy(
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
ALTER TABLE groups
|
||||
ADD COLUMN IF NOT EXISTS long_context_pricing_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN IF NOT EXISTS long_context_pricing_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
ADD COLUMN IF NOT EXISTS model_pricing JSONB;
|
||||
|
||||
UPDATE groups
|
||||
SET long_context_pricing_enabled = TRUE
|
||||
WHERE long_context_pricing_enabled IS DISTINCT FROM TRUE;
|
||||
|
||||
COMMENT ON COLUMN groups.long_context_pricing_enabled IS
|
||||
'Whether token pricing selects long-context tiers; false uses the lowest tier only';
|
||||
'Whether token pricing selects official/preset long-context tiers; default true preserves existing long-context billing';
|
||||
COMMENT ON COLUMN groups.model_pricing IS
|
||||
'Per-model group pricing overrides channel and built-in model pricing';
|
||||
|
||||
@@ -134,8 +134,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Token intervals -->
|
||||
<div class="mt-3">
|
||||
<!-- Token intervals (channel-only; group long-context uses official presets) -->
|
||||
<div v-if="!hideTokenIntervals" class="mt-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.intervals') }}
|
||||
@@ -245,10 +245,13 @@ import channelsAPI from '@/api/admin/channels'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
entry: PricingFormEntry
|
||||
platform?: string
|
||||
}>()
|
||||
hideTokenIntervals?: boolean
|
||||
}>(), {
|
||||
hideTokenIntervals: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
update: [entry: PricingFormEntry]
|
||||
|
||||
@@ -1007,9 +1007,9 @@ export default {
|
||||
},
|
||||
modelPricing: {
|
||||
title: 'Per-model group pricing',
|
||||
description: 'Overrides channel and built-in prices for matching models. Use per-request tiers such as realtime, tts, and stt for audio.',
|
||||
description: 'Overrides channel and built-in prices for matching models. Long-context tiers come from official presets — do not enter custom intervals. Use per-request tiers such as realtime, tts, and stt for audio.',
|
||||
longContext: 'Enable long-context tier pricing',
|
||||
longContextHint: 'When disabled, token models always use the lowest configured tier.',
|
||||
longContextHint: 'When checked, official/preset long-context tiers apply. When unchecked, token models stay on the first-tier base rate.',
|
||||
add: 'Add model price'
|
||||
},
|
||||
voicePricing: {
|
||||
|
||||
@@ -1004,9 +1004,9 @@ export default {
|
||||
},
|
||||
modelPricing: {
|
||||
title: '分组逐模型定价',
|
||||
description: '匹配模型后覆盖渠道和内置价格。音频可用按次层级配置 realtime、tts、stt。',
|
||||
description: '匹配模型后覆盖渠道和内置价格。长上下文阶梯沿用官方/预设价卡,无需再手填区间。音频可用按次层级配置 realtime、tts、stt。',
|
||||
longContext: '启用长上下文阶梯定价',
|
||||
longContextHint: '关闭时,Token 模型始终只使用配置的最低档。',
|
||||
longContextHint: '勾选后按官方/预设阶梯计费;关闭则始终按第一档基础价。',
|
||||
add: '添加模型价格'
|
||||
},
|
||||
voicePricing: {
|
||||
|
||||
@@ -1499,7 +1499,7 @@
|
||||
<span><span class="block text-sm text-gray-700 dark:text-gray-300">{{ t("admin.groups.modelPricing.longContext") }}</span><span class="block text-xs text-gray-500">{{ t("admin.groups.modelPricing.longContextHint") }}</span></span>
|
||||
</label>
|
||||
<div class="mt-3 space-y-2">
|
||||
<PricingEntryCard v-for="(entry, index) in createForm.model_pricing" :key="index" :entry="entry" :platform="createForm.platform" @update="createForm.model_pricing[index] = $event" @remove="createForm.model_pricing.splice(index, 1)" />
|
||||
<PricingEntryCard v-for="(entry, index) in createForm.model_pricing" :key="index" :entry="entry" :platform="createForm.platform" hide-token-intervals @update="createForm.model_pricing[index] = $event" @remove="createForm.model_pricing.splice(index, 1)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3221,7 +3221,7 @@
|
||||
<span><span class="block text-sm text-gray-700 dark:text-gray-300">{{ t("admin.groups.modelPricing.longContext") }}</span><span class="block text-xs text-gray-500">{{ t("admin.groups.modelPricing.longContextHint") }}</span></span>
|
||||
</label>
|
||||
<div class="mt-3 space-y-2">
|
||||
<PricingEntryCard v-for="(entry, index) in editForm.model_pricing" :key="index" :entry="entry" :platform="editForm.platform" @update="editForm.model_pricing[index] = $event" @remove="editForm.model_pricing.splice(index, 1)" />
|
||||
<PricingEntryCard v-for="(entry, index) in editForm.model_pricing" :key="index" :entry="entry" :platform="editForm.platform" hide-token-intervals @update="editForm.model_pricing[index] = $event" @remove="editForm.model_pricing.splice(index, 1)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4527,7 +4527,10 @@ const groupPricingToAPI = (
|
||||
image_input_price: mTokToPerToken(entry.image_input_price),
|
||||
image_output_price: mTokToPerToken(entry.image_output_price),
|
||||
per_request_price: toNullableNumber(entry.per_request_price),
|
||||
intervals: formIntervalsToAPI(entry.intervals || []),
|
||||
intervals:
|
||||
entry.billing_mode === "token"
|
||||
? []
|
||||
: formIntervalsToAPI(entry.intervals || []),
|
||||
}));
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -5013,7 +5016,7 @@ const createForm = reactive({
|
||||
daily_limit_usd: null as number | null,
|
||||
weekly_limit_usd: null as number | null,
|
||||
monthly_limit_usd: null as number | null,
|
||||
long_context_pricing_enabled: false,
|
||||
long_context_pricing_enabled: true,
|
||||
model_pricing: [] as PricingFormEntry[],
|
||||
// 图片生成计费配置
|
||||
allow_image_generation: false,
|
||||
@@ -5374,7 +5377,7 @@ const editForm = reactive({
|
||||
daily_limit_usd: null as number | null,
|
||||
weekly_limit_usd: null as number | null,
|
||||
monthly_limit_usd: null as number | null,
|
||||
long_context_pricing_enabled: false,
|
||||
long_context_pricing_enabled: true,
|
||||
model_pricing: [] as PricingFormEntry[],
|
||||
// 图片生成计费配置
|
||||
allow_image_generation: false,
|
||||
@@ -5848,7 +5851,7 @@ const closeCreateModal = () => {
|
||||
createForm.video_price_720p = null;
|
||||
createForm.video_price_1080p = null;
|
||||
createForm.video_model_prices = createVideoModelPricesForm();
|
||||
createForm.long_context_pricing_enabled = false;
|
||||
createForm.long_context_pricing_enabled = true;
|
||||
createForm.model_pricing = [];
|
||||
createForm.web_search_price_per_call = null;
|
||||
createForm.search_price_per_1k = null;
|
||||
@@ -6076,7 +6079,7 @@ const handleEdit = async (group: AdminGroup) => {
|
||||
editForm.weekly_limit_usd = group.weekly_limit_usd;
|
||||
editForm.monthly_limit_usd = group.monthly_limit_usd;
|
||||
editForm.long_context_pricing_enabled =
|
||||
group.long_context_pricing_enabled ?? false;
|
||||
group.long_context_pricing_enabled ?? true;
|
||||
editForm.model_pricing = groupPricingFromAPI(group.model_pricing);
|
||||
editForm.allow_image_generation = group.allow_image_generation ?? false;
|
||||
editForm.allow_batch_image_generation =
|
||||
@@ -6182,7 +6185,7 @@ const closeEditModal = () => {
|
||||
editForm.video_price_720p = null;
|
||||
editForm.video_price_1080p = null;
|
||||
editForm.video_model_prices = createVideoModelPricesForm();
|
||||
editForm.long_context_pricing_enabled = false;
|
||||
editForm.long_context_pricing_enabled = true;
|
||||
editForm.model_pricing = [];
|
||||
editForm.web_search_price_per_call = null;
|
||||
editForm.search_price_per_1k = null;
|
||||
|
||||
Reference in New Issue
Block a user