fix: 长上下文默认保持开启,并与 OpenAI 账号开关取交集

迁移默认 false 会让存量分组静默丢掉 ≥200k 阶梯与渠道多档价。
OpenAI 路径传入 Group 后,账号级 openai_long_context_billing_enabled 被顶掉。

- 列默认 TRUE,并对存量行回填
- 分组价卡不再手填 token 区间,勾选走官方/预设阶梯
- 分组开关与账号开关 AND;价卡 JSON 损坏时打 warn
This commit is contained in:
IanShaw027
2026-08-13 08:38:10 +08:00
parent 8c4c3c09ce
commit b830bc14d6
12 changed files with 134 additions and 36 deletions
@@ -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: {
+11 -8
View File
@@ -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;