mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-31 16:26:30 +00:00
fix: preserve zh locale in i18n cleanup
This commit is contained in:
@@ -4,32 +4,32 @@
|
||||
<!-- Token mode: context range + prices ($/MTok) -->
|
||||
<template v-if="mode === 'token'">
|
||||
<div class="w-20">
|
||||
<label class="text-xs text-gray-400">Min</label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.minTokens') }}</label>
|
||||
<input :value="interval.min_tokens" @input="emitField('min_tokens', toInt(($event.target as HTMLInputElement).value))"
|
||||
type="number" min="0" class="input mt-0.5 text-xs" />
|
||||
</div>
|
||||
<div class="w-20">
|
||||
<label class="text-xs text-gray-400">Max <span class="text-gray-300">(inclusive)</span></label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.maxTokens') }} <span class="text-gray-300">{{ t('admin.channels.form.inclusive') }}</span></label>
|
||||
<input :value="interval.max_tokens ?? ''" @input="emitField('max_tokens', toIntOrNull(($event.target as HTMLInputElement).value))"
|
||||
type="number" min="0" class="input mt-0.5 text-xs" :placeholder="'∞'" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.inputPrice', 'Input') }} <span v-if="isEmpty" class="text-red-500">*</span> <span class="text-gray-300">$/M</span></label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.inputPrice') }} <span v-if="isEmpty" class="text-red-500">*</span> <span class="text-gray-300">$/M</span></label>
|
||||
<input :value="interval.input_price" @input="emitField('input_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-xs" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.outputPrice', 'Output') }} <span v-if="isEmpty" class="text-red-500">*</span> <span class="text-gray-300">$/M</span></label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.outputPrice') }} <span v-if="isEmpty" class="text-red-500">*</span> <span class="text-gray-300">$/M</span></label>
|
||||
<input :value="interval.output_price" @input="emitField('output_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-xs" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.cacheWritePrice', 'Cache W') }} <span class="text-gray-300">$/M</span></label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.cacheWritePriceShort') }} <span class="text-gray-300">$/M</span></label>
|
||||
<input :value="interval.cache_write_price" @input="emitField('cache_write_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-xs" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.cacheReadPrice', 'Cache R') }} <span class="text-gray-300">$/M</span></label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.cacheReadPriceShort') }} <span class="text-gray-300">$/M</span></label>
|
||||
<input :value="interval.cache_read_price" @input="emitField('cache_read_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-xs" />
|
||||
</div>
|
||||
@@ -39,23 +39,23 @@
|
||||
<template v-else>
|
||||
<div class="w-24">
|
||||
<label class="text-xs text-gray-400">
|
||||
{{ mode === 'image' ? t('admin.channels.form.resolution', 'Resolution') : t('admin.channels.form.tierLabel', 'Tier') }}
|
||||
{{ mode === 'image' ? t('admin.channels.form.resolution') : t('admin.channels.form.tierLabel') }}
|
||||
</label>
|
||||
<input :value="interval.tier_label" @input="emitField('tier_label', ($event.target as HTMLInputElement).value)"
|
||||
type="text" class="input mt-0.5 text-xs" :placeholder="mode === 'image' ? '1K / 2K / 4K' : ''" />
|
||||
</div>
|
||||
<div class="w-20">
|
||||
<label class="text-xs text-gray-400">Min</label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.minTokens') }}</label>
|
||||
<input :value="interval.min_tokens" @input="emitField('min_tokens', toInt(($event.target as HTMLInputElement).value))"
|
||||
type="number" min="0" class="input mt-0.5 text-xs" />
|
||||
</div>
|
||||
<div class="w-20">
|
||||
<label class="text-xs text-gray-400">Max <span class="text-gray-300">(inclusive)</span></label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.maxTokens') }} <span class="text-gray-300">{{ t('admin.channels.form.inclusive') }}</span></label>
|
||||
<input :value="interval.max_tokens ?? ''" @input="emitField('max_tokens', toIntOrNull(($event.target as HTMLInputElement).value))"
|
||||
type="number" min="0" class="input mt-0.5 text-xs" :placeholder="'∞'" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.perRequestPrice', 'Price per Request') }} <span v-if="isEmpty" class="text-red-500">*</span> <span class="text-gray-300">$</span></label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.perRequestPrice') }} <span v-if="isEmpty" class="text-red-500">*</span> <span class="text-gray-300">$</span></label>
|
||||
<input :value="interval.per_request_price" @input="emitField('per_request_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-xs" />
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
v-if="entry.models.length === 0"
|
||||
class="text-xs italic text-gray-400"
|
||||
>
|
||||
{{ t('admin.channels.form.noModels', 'No models added') }}
|
||||
{{ t('admin.channels.form.noModels') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
<!-- Expanded: show the label "Pricing Entry" or similar -->
|
||||
<div v-else class="flex-1 text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.pricingEntry', 'Pricing Entry') }}
|
||||
{{ t('admin.channels.form.pricingEntry') }}
|
||||
</div>
|
||||
|
||||
<!-- Remove button (always visible, stop propagation) -->
|
||||
@@ -71,19 +71,19 @@
|
||||
<div class="mt-3 flex items-start gap-2">
|
||||
<div class="flex-1">
|
||||
<label class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.models', 'Models') }} <span class="text-red-500">*</span>
|
||||
{{ t('admin.channels.form.models') }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<ModelTagInput
|
||||
:models="entry.models"
|
||||
:platform="props.platform"
|
||||
@update:models="onModelsUpdate($event)"
|
||||
:placeholder="t('admin.channels.form.modelsPlaceholder', 'Type a model name and press Enter. Wildcard * is supported.')"
|
||||
:placeholder="t('admin.channels.form.modelsPlaceholder')"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<label class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.billingMode', 'Billing Mode') }}
|
||||
{{ t('admin.channels.form.billingMode') }}
|
||||
</label>
|
||||
<Select
|
||||
:modelValue="entry.billing_mode"
|
||||
@@ -98,34 +98,34 @@
|
||||
<div v-if="entry.billing_mode === 'token'">
|
||||
<!-- Default prices (fallback when no interval matches) -->
|
||||
<label class="mt-3 block text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.defaultPrices', 'Default prices (fallback when no interval matches)') }}
|
||||
{{ t('admin.channels.form.defaultPrices') }}
|
||||
<span class="ml-1 font-normal text-gray-400">$/MTok</span>
|
||||
</label>
|
||||
<div class="mt-1 grid grid-cols-2 gap-2 sm:grid-cols-5">
|
||||
<div>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.inputPrice', 'Input') }}</label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.inputPrice') }}</label>
|
||||
<input :value="entry.input_price" @input="emitField('input_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder', 'Default')" />
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder')" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.outputPrice', 'Output') }}</label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.outputPrice') }}</label>
|
||||
<input :value="entry.output_price" @input="emitField('output_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder', 'Default')" />
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder')" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.cacheWritePrice', 'Cache Write') }}</label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.cacheWritePrice') }}</label>
|
||||
<input :value="entry.cache_write_price" @input="emitField('cache_write_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder', 'Default')" />
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder')" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.cacheReadPrice', 'Cache Read') }}</label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.cacheReadPrice') }}</label>
|
||||
<input :value="entry.cache_read_price" @input="emitField('cache_read_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder', 'Default')" />
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder')" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.imageTokenPrice', 'Image Output') }}</label>
|
||||
<label class="text-xs text-gray-400">{{ t('admin.channels.form.imageTokenPrice') }}</label>
|
||||
<input :value="entry.image_output_price" @input="emitField('image_output_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder', 'Default')" />
|
||||
type="number" step="any" min="0" class="input mt-0.5 text-sm" :placeholder="t('admin.channels.form.pricePlaceholder')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -133,11 +133,11 @@
|
||||
<div 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', 'Context intervals (optional)') }}
|
||||
{{ t('admin.channels.form.intervals') }}
|
||||
<span class="ml-1 font-normal text-gray-400">(min, max]</span>
|
||||
</label>
|
||||
<button type="button" @click="addInterval" class="text-xs text-primary-600 hover:text-primary-700">
|
||||
+ {{ t('admin.channels.form.addInterval', 'Add interval') }}
|
||||
+ {{ t('admin.channels.form.addInterval') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="entry.intervals && entry.intervals.length > 0" class="mt-2 space-y-2">
|
||||
@@ -157,21 +157,21 @@
|
||||
<div v-else-if="entry.billing_mode === 'per_request'">
|
||||
<!-- Default per-request price -->
|
||||
<label class="mt-3 block text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.defaultPerRequestPrice', 'Default per-request price (fallback when no tier matches)') }}
|
||||
{{ t('admin.channels.form.defaultPerRequestPrice') }}
|
||||
<span class="ml-1 font-normal text-gray-400">$</span>
|
||||
</label>
|
||||
<div class="mt-1 w-48">
|
||||
<input :value="entry.per_request_price" @input="emitField('per_request_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input text-sm" :placeholder="t('admin.channels.form.pricePlaceholder', 'Default')" />
|
||||
type="number" step="any" min="0" class="input text-sm" :placeholder="t('admin.channels.form.pricePlaceholder')" />
|
||||
</div>
|
||||
|
||||
<!-- Tiers -->
|
||||
<div class="mt-3 flex items-center justify-between">
|
||||
<label class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.requestTiers', 'Request Tiers') }}
|
||||
{{ t('admin.channels.form.requestTiers') }}
|
||||
</label>
|
||||
<button type="button" @click="addInterval" class="text-xs text-primary-600 hover:text-primary-700">
|
||||
+ {{ t('admin.channels.form.addTier', 'Add tier') }}
|
||||
+ {{ t('admin.channels.form.addTier') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="entry.intervals && entry.intervals.length > 0" class="mt-2 space-y-2">
|
||||
@@ -185,7 +185,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="mt-2 rounded border border-dashed border-gray-300 p-3 text-center text-xs text-gray-400 dark:border-dark-500">
|
||||
{{ t('admin.channels.form.noTiersYet', 'No tiers yet. Click add to configure per-request pricing.') }}
|
||||
{{ t('admin.channels.form.noTiersYet') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -193,21 +193,21 @@
|
||||
<div v-else-if="entry.billing_mode === 'image'">
|
||||
<!-- Default image price (per-request, same as per_request mode) -->
|
||||
<label class="mt-3 block text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.defaultImagePrice', 'Default image price (fallback when no tier matches)') }}
|
||||
{{ t('admin.channels.form.defaultImagePrice') }}
|
||||
<span class="ml-1 font-normal text-gray-400">$</span>
|
||||
</label>
|
||||
<div class="mt-1 w-48">
|
||||
<input :value="entry.per_request_price" @input="emitField('per_request_price', ($event.target as HTMLInputElement).value)"
|
||||
type="number" step="any" min="0" class="input text-sm" :placeholder="t('admin.channels.form.pricePlaceholder', 'Default')" />
|
||||
type="number" step="any" min="0" class="input text-sm" :placeholder="t('admin.channels.form.pricePlaceholder')" />
|
||||
</div>
|
||||
|
||||
<!-- Image tiers -->
|
||||
<div class="mt-3 flex items-center justify-between">
|
||||
<label class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channels.form.imageTiers', 'Image tiers (per request)') }}
|
||||
{{ t('admin.channels.form.imageTiers') }}
|
||||
</label>
|
||||
<button type="button" @click="addImageTier" class="text-xs text-primary-600 hover:text-primary-700">
|
||||
+ {{ t('admin.channels.form.addTier', 'Add tier') }}
|
||||
+ {{ t('admin.channels.form.addTier') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="entry.intervals && entry.intervals.length > 0" class="mt-2 space-y-2">
|
||||
@@ -254,9 +254,9 @@ const emit = defineEmits<{
|
||||
const collapsed = ref(props.entry.models.length > 0)
|
||||
|
||||
const billingModeOptions = computed(() => [
|
||||
{ value: 'token', label: 'Token' },
|
||||
{ value: 'per_request', label: t('admin.channels.billingMode.perRequest', 'Per Request') },
|
||||
{ value: 'image', label: t('admin.channels.billingMode.image', 'Image (per request)') }
|
||||
{ value: 'token', label: t('admin.channels.billingMode.token') },
|
||||
{ value: 'per_request', label: t('admin.channels.billingMode.perRequest') },
|
||||
{ value: 'image', label: t('admin.channels.billingMode.image') }
|
||||
])
|
||||
|
||||
const billingModeLabel = computed(() => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { BillingMode, PricingInterval } from '@/api/admin/channels'
|
||||
|
||||
type TranslateFn = (key: string, params?: Record<string, unknown>) => string
|
||||
|
||||
export interface IntervalFormEntry {
|
||||
min_tokens: number
|
||||
max_tokens: number | null
|
||||
@@ -125,6 +127,7 @@ export function findModelConflict(models: string[]): [string, string] | null {
|
||||
export function validateIntervals(
|
||||
intervals: IntervalFormEntry[],
|
||||
mode: BillingMode = 'token',
|
||||
t?: TranslateFn,
|
||||
): string | null {
|
||||
if (!intervals || intervals.length === 0) return null
|
||||
|
||||
@@ -132,58 +135,104 @@ export function validateIntervals(
|
||||
const sorted = [...intervals].sort((a, b) => a.min_tokens - b.min_tokens)
|
||||
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
const err = validateSingleInterval(sorted[i], i)
|
||||
const err = validateSingleInterval(sorted[i], i, t)
|
||||
if (err) return err
|
||||
}
|
||||
|
||||
// per_request / image 模式按 tier_label 匹配,不做 token 区间重叠校验
|
||||
if (mode !== 'token') return null
|
||||
return checkIntervalOverlap(sorted)
|
||||
return checkIntervalOverlap(sorted, t)
|
||||
}
|
||||
|
||||
function validateSingleInterval(iv: IntervalFormEntry, idx: number): string | null {
|
||||
function intervalValidationMessage(
|
||||
t: TranslateFn | undefined,
|
||||
key: string,
|
||||
params: Record<string, unknown>,
|
||||
fallback: string,
|
||||
): string {
|
||||
return t ? t(`admin.channels.intervalValidation.${key}`, params) : fallback
|
||||
}
|
||||
|
||||
function intervalPriceLabel(t: TranslateFn | undefined, key: string, fallback: string): string {
|
||||
return t ? t(`admin.channels.intervalValidation.price.${key}`) : fallback
|
||||
}
|
||||
|
||||
function validateSingleInterval(iv: IntervalFormEntry, idx: number, t?: TranslateFn): string | null {
|
||||
const index = idx + 1
|
||||
if (iv.min_tokens < 0) {
|
||||
return `Interval #${idx + 1}: minimum token count (${iv.min_tokens}) cannot be negative`
|
||||
return intervalValidationMessage(
|
||||
t,
|
||||
'negativeMin',
|
||||
{ index, value: iv.min_tokens },
|
||||
`Interval #${index}: minimum token count (${iv.min_tokens}) cannot be negative`,
|
||||
)
|
||||
}
|
||||
if (iv.max_tokens != null) {
|
||||
if (iv.max_tokens <= 0) {
|
||||
return `Interval #${idx + 1}: maximum token count (${iv.max_tokens}) must be greater than 0`
|
||||
return intervalValidationMessage(
|
||||
t,
|
||||
'maxPositive',
|
||||
{ index, value: iv.max_tokens },
|
||||
`Interval #${index}: maximum token count (${iv.max_tokens}) must be greater than 0`,
|
||||
)
|
||||
}
|
||||
if (iv.max_tokens <= iv.min_tokens) {
|
||||
return `Interval #${idx + 1}: maximum token count (${iv.max_tokens}) must be greater than minimum token count (${iv.min_tokens})`
|
||||
return intervalValidationMessage(
|
||||
t,
|
||||
'maxGreaterThanMin',
|
||||
{ index, max: iv.max_tokens, min: iv.min_tokens },
|
||||
`Interval #${index}: maximum token count (${iv.max_tokens}) must be greater than minimum token count (${iv.min_tokens})`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return validateIntervalPrices(iv, idx)
|
||||
return validateIntervalPrices(iv, idx, t)
|
||||
}
|
||||
|
||||
function validateIntervalPrices(iv: IntervalFormEntry, idx: number): string | null {
|
||||
const prices: [string, number | string | null][] = [
|
||||
['input price', iv.input_price],
|
||||
['output price', iv.output_price],
|
||||
['cache write price', iv.cache_write_price],
|
||||
['cache read price', iv.cache_read_price],
|
||||
['per-request price', iv.per_request_price],
|
||||
function validateIntervalPrices(iv: IntervalFormEntry, idx: number, t?: TranslateFn): string | null {
|
||||
const index = idx + 1
|
||||
const prices: [string, string, number | string | null][] = [
|
||||
['inputPrice', 'input price', iv.input_price],
|
||||
['outputPrice', 'output price', iv.output_price],
|
||||
['cacheWritePrice', 'cache write price', iv.cache_write_price],
|
||||
['cacheReadPrice', 'cache read price', iv.cache_read_price],
|
||||
['perRequestPrice', 'per-request price', iv.per_request_price],
|
||||
]
|
||||
for (const [name, val] of prices) {
|
||||
for (const [key, fallbackName, val] of prices) {
|
||||
if (val != null && val !== '' && Number(val) < 0) {
|
||||
return `Interval #${idx + 1}: ${name} cannot be negative`
|
||||
const field = intervalPriceLabel(t, key, fallbackName)
|
||||
return intervalValidationMessage(
|
||||
t,
|
||||
'negativePrice',
|
||||
{ index, field },
|
||||
`Interval #${index}: ${field} cannot be negative`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function checkIntervalOverlap(sorted: IntervalFormEntry[]): string | null {
|
||||
function checkIntervalOverlap(sorted: IntervalFormEntry[], t?: TranslateFn): string | null {
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
// 无上限区间必须是最后一个
|
||||
if (sorted[i].max_tokens == null && i < sorted.length - 1) {
|
||||
return `Interval #${i + 1}: an unbounded interval (empty maximum token count) must be last`
|
||||
return intervalValidationMessage(
|
||||
t,
|
||||
'unboundedLast',
|
||||
{ index: i + 1 },
|
||||
`Interval #${i + 1}: an unbounded interval (empty maximum token count) must be last`,
|
||||
)
|
||||
}
|
||||
if (i === 0) continue
|
||||
const prev = sorted[i - 1]
|
||||
// (min, max] 语义:前一个区间上界 > 当前区间下界则重叠
|
||||
if (prev.max_tokens == null || prev.max_tokens > sorted[i].min_tokens) {
|
||||
const prevMax = prev.max_tokens == null ? '∞' : String(prev.max_tokens)
|
||||
return `Intervals #${i} and #${i + 1} overlap: previous upper bound (${prevMax}) is greater than current lower bound (${sorted[i].min_tokens})`
|
||||
return intervalValidationMessage(
|
||||
t,
|
||||
'overlap',
|
||||
{ previousIndex: i, currentIndex: i + 1, previousMax: prevMax, currentMin: sorted[i].min_tokens },
|
||||
`Intervals #${i} and #${i + 1} overlap: previous upper bound (${prevMax}) is greater than current lower bound (${sorted[i].min_tokens})`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user