mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-11 05:30:19 +08:00
fix(frontend): show service tier transitions
This commit is contained in:
@@ -253,12 +253,14 @@
|
||||
{{ getReasoningEffort(record) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="getFastBadge(record)"
|
||||
v-if="getServiceTierBadge(record)"
|
||||
variant="outline"
|
||||
class="h-4 rounded-full px-1.5 text-[10px] leading-4 text-foreground flex-shrink-0"
|
||||
:title="getFastBadgeTitle(record)"
|
||||
class="h-4 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4 flex-shrink-0"
|
||||
:class="getServiceTierBadge(record)?.className"
|
||||
:title="getServiceTierBadge(record)?.title"
|
||||
:aria-label="getServiceTierBadge(record)?.ariaLabel"
|
||||
>
|
||||
fast
|
||||
{{ getServiceTierBadge(record)?.label }}
|
||||
</Badge>
|
||||
<!-- 状态 Badge -->
|
||||
<Badge
|
||||
@@ -762,12 +764,14 @@
|
||||
{{ getReasoningEffort(record) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="getFastBadge(record)"
|
||||
v-if="getServiceTierBadge(record)"
|
||||
variant="outline"
|
||||
class="h-4 rounded-full px-1.5 text-[10px] leading-4 text-foreground flex-shrink-0"
|
||||
:title="getFastBadgeTitle(record)"
|
||||
class="h-4 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4 flex-shrink-0"
|
||||
:class="getServiceTierBadge(record)?.className"
|
||||
:title="getServiceTierBadge(record)?.title"
|
||||
:aria-label="getServiceTierBadge(record)?.ariaLabel"
|
||||
>
|
||||
fast
|
||||
{{ getServiceTierBadge(record)?.label }}
|
||||
</Badge>
|
||||
</div>
|
||||
<span class="text-muted-foreground truncate">{{ getActualModel(record) }}</span>
|
||||
@@ -786,12 +790,14 @@
|
||||
{{ getReasoningEffort(record) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="getFastBadge(record)"
|
||||
v-if="getServiceTierBadge(record)"
|
||||
variant="outline"
|
||||
class="h-4 rounded-full px-1.5 text-[10px] leading-4 text-foreground flex-shrink-0"
|
||||
:title="getFastBadgeTitle(record)"
|
||||
class="h-4 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4 flex-shrink-0"
|
||||
:class="getServiceTierBadge(record)?.className"
|
||||
:title="getServiceTierBadge(record)?.title"
|
||||
:aria-label="getServiceTierBadge(record)?.ariaLabel"
|
||||
>
|
||||
fast
|
||||
{{ getServiceTierBadge(record)?.label }}
|
||||
</Badge>
|
||||
</span>
|
||||
</TableCell>
|
||||
@@ -1610,40 +1616,137 @@ function getReasoningEffortTitle(record: UsageRecord): string {
|
||||
return effort ? `Reasoning: ${effort}` : ''
|
||||
}
|
||||
|
||||
type ServiceTierBadgeState = 'confirmed' | 'downgraded' | 'upgraded' | 'pending' | 'unconfirmed'
|
||||
|
||||
interface ServiceTierBadgePresentation {
|
||||
label: string
|
||||
state: ServiceTierBadgeState
|
||||
className: string
|
||||
title: string
|
||||
ariaLabel: string
|
||||
}
|
||||
|
||||
function normalizeServiceTier(value: string | null | undefined): string | null {
|
||||
const serviceTier = value?.trim().toLowerCase()
|
||||
return serviceTier || null
|
||||
}
|
||||
|
||||
function getRequestedServiceTier(record: UsageRecord): string | null {
|
||||
return normalizeServiceTier(record.service_tier)
|
||||
}
|
||||
|
||||
function getActualServiceTier(record: UsageRecord): string | null {
|
||||
return normalizeServiceTier(record.actual_service_tier)
|
||||
}
|
||||
|
||||
function getFastBadge(record: UsageRecord): boolean {
|
||||
return (getActualServiceTier(record) ?? getRequestedServiceTier(record)) === 'priority'
|
||||
}
|
||||
|
||||
function getFastBadgeTitle(record: UsageRecord): string {
|
||||
const requested = getRequestedServiceTier(record)
|
||||
const actual = getActualServiceTier(record)
|
||||
if (requested && actual) {
|
||||
return requested === actual
|
||||
? `Requested and actual service tier: ${actual}`
|
||||
: `Requested service tier: ${requested}\nActual service tier: ${actual}`
|
||||
function canonicalServiceTier(value: string | null): string | null {
|
||||
if (value === 'auto' || value === 'default' || value === 'standard') {
|
||||
return 'standard'
|
||||
}
|
||||
if (actual) return `Actual service tier: ${actual}`
|
||||
return requested ? `Requested service tier: ${requested}` : ''
|
||||
return value
|
||||
}
|
||||
|
||||
function serviceTierBadgeClass(state: ServiceTierBadgeState): string {
|
||||
switch (state) {
|
||||
case 'confirmed':
|
||||
return 'border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300'
|
||||
case 'downgraded':
|
||||
return 'border-amber-500/50 bg-amber-500/10 text-amber-700 dark:text-amber-300'
|
||||
case 'upgraded':
|
||||
return 'border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-300'
|
||||
case 'pending':
|
||||
return 'border-dashed border-muted-foreground/30 bg-muted/30 text-muted-foreground'
|
||||
case 'unconfirmed':
|
||||
return 'border-dashed border-amber-500/40 bg-amber-500/5 text-amber-700 dark:text-amber-300'
|
||||
}
|
||||
}
|
||||
|
||||
function buildServiceTierBadgePresentation(
|
||||
label: string,
|
||||
state: ServiceTierBadgeState,
|
||||
requestedRaw: string | null,
|
||||
actualRaw: string | null,
|
||||
billingTier: string | null,
|
||||
): ServiceTierBadgePresentation {
|
||||
const titleLines: string[] = []
|
||||
if (requestedRaw) titleLines.push(`请求档位:${requestedRaw}`)
|
||||
if (actualRaw) titleLines.push(`实际档位:${actualRaw}`)
|
||||
if (billingTier) {
|
||||
titleLines.push(`计费档位:${billingTier}`)
|
||||
} else {
|
||||
titleLines.push(`计费档位:${state === 'pending' ? '待上游确认' : '未确认'}`)
|
||||
}
|
||||
const title = titleLines.join('\n')
|
||||
return {
|
||||
label,
|
||||
state,
|
||||
className: serviceTierBadgeClass(state),
|
||||
title,
|
||||
ariaLabel: titleLines.join(','),
|
||||
}
|
||||
}
|
||||
|
||||
function getServiceTierBadge(record: UsageRecord): ServiceTierBadgePresentation | null {
|
||||
const requestedRaw = normalizeServiceTier(record.service_tier)
|
||||
const actualRaw = normalizeServiceTier(record.actual_service_tier)
|
||||
const requested = canonicalServiceTier(requestedRaw)
|
||||
const actual = canonicalServiceTier(actualRaw)
|
||||
const requestedFast = requested === 'priority'
|
||||
const actualFast = actual === 'priority'
|
||||
|
||||
if (actual) {
|
||||
if (requestedFast && !actualFast) {
|
||||
return buildServiceTierBadgePresentation(
|
||||
`fast → ${actual}`,
|
||||
'downgraded',
|
||||
requestedRaw,
|
||||
actualRaw,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
if (!requestedFast && actualFast) {
|
||||
const requestedLabel = requested ?? 'standard'
|
||||
return buildServiceTierBadgePresentation(
|
||||
requested ? `${requestedLabel} → fast` : 'fast',
|
||||
requested ? 'upgraded' : 'confirmed',
|
||||
requestedRaw,
|
||||
actualRaw,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
if (actualFast) {
|
||||
return buildServiceTierBadgePresentation(
|
||||
'fast',
|
||||
'confirmed',
|
||||
requestedRaw,
|
||||
actualRaw,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (!requestedFast) return null
|
||||
const displayStatus = getDisplayStatus(record)
|
||||
const isActive = displayStatus === 'pending' || displayStatus === 'streaming'
|
||||
return buildServiceTierBadgePresentation(
|
||||
isActive ? 'fast · 待确认' : 'fast · 未确认',
|
||||
isActive ? 'pending' : 'unconfirmed',
|
||||
requestedRaw,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
function getServiceTierTitle(record: UsageRecord): string {
|
||||
const badge = getServiceTierBadge(record)
|
||||
if (badge) return badge.title
|
||||
|
||||
const requested = normalizeServiceTier(record.service_tier)
|
||||
const actual = normalizeServiceTier(record.actual_service_tier)
|
||||
return [
|
||||
requested ? `请求档位:${requested}` : null,
|
||||
actual ? `实际档位:${actual}` : null,
|
||||
].filter((line): line is string => Boolean(line)).join('\n')
|
||||
}
|
||||
|
||||
// 获取模型列的 tooltip
|
||||
function getModelTooltip(record: UsageRecord): string {
|
||||
const actualModel = getActualModel(record)
|
||||
const reasoningEffort = getReasoningEffort(record)
|
||||
const serviceTierTitle = getFastBadgeTitle(record)
|
||||
const serviceTierTitle = getServiceTierTitle(record)
|
||||
const tierSuffix = serviceTierTitle ? `\n${serviceTierTitle}` : ''
|
||||
const suffix = `${reasoningEffort ? `\nReasoning: ${reasoningEffort}` : ''}${tierSuffix}`
|
||||
if (actualModel) {
|
||||
|
||||
@@ -162,6 +162,13 @@ function mountUsageRecordsTable(records: UsageRecord[], overrides: Record<string
|
||||
return root
|
||||
}
|
||||
|
||||
function expectServiceTierBadge(root: HTMLElement, label: string) {
|
||||
const labels = [...root.querySelectorAll('span')]
|
||||
.map((element) => element.textContent?.trim())
|
||||
|
||||
expect(labels).toContain(label)
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const { app, root } of mountedApps.splice(0)) {
|
||||
app.unmount()
|
||||
@@ -288,27 +295,63 @@ describe('UsageRecordsTable', () => {
|
||||
expect(root.textContent).toContain('xhigh')
|
||||
})
|
||||
|
||||
it('shows fast badge for priority service tier', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({ service_tier: 'priority' })])
|
||||
it('shows confirmed fast when requested and actual service tiers are priority', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
service_tier: 'priority',
|
||||
actual_service_tier: 'priority',
|
||||
})])
|
||||
|
||||
expect(root.textContent).toContain('gpt-5')
|
||||
expect(root.textContent).toContain('fast')
|
||||
expectServiceTierBadge(root, 'fast')
|
||||
})
|
||||
|
||||
it('uses the actual service tier when the provider changes processing class', () => {
|
||||
const downgraded = mountUsageRecordsTable([buildRecord({
|
||||
it('shows fast to standard when the provider downgrades a priority request', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
service_tier: 'priority',
|
||||
actual_service_tier: 'default',
|
||||
})])
|
||||
expect(downgraded.textContent).not.toContain('fast')
|
||||
expect(downgraded.querySelector('[title*="Requested service tier: priority"]')).not.toBeNull()
|
||||
expect(downgraded.querySelector('[title*="Actual service tier: default"]')).not.toBeNull()
|
||||
|
||||
const upgraded = mountUsageRecordsTable([buildRecord({
|
||||
service_tier: 'flex',
|
||||
expectServiceTierBadge(root, 'fast → standard')
|
||||
})
|
||||
|
||||
it('shows fast to flex when the provider moves a priority request to flex', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
service_tier: 'priority',
|
||||
actual_service_tier: 'flex',
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'fast → flex')
|
||||
})
|
||||
|
||||
it('shows standard to fast when the provider upgrades a default request', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
service_tier: 'default',
|
||||
actual_service_tier: 'priority',
|
||||
})])
|
||||
expect(upgraded.textContent).toContain('fast')
|
||||
|
||||
expectServiceTierBadge(root, 'standard → fast')
|
||||
})
|
||||
|
||||
it.each(['pending', 'streaming'] as const)(
|
||||
'shows fast as pending confirmation while a priority request is %s',
|
||||
(status) => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
service_tier: 'priority',
|
||||
actual_service_tier: null,
|
||||
status,
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'fast · 待确认')
|
||||
},
|
||||
)
|
||||
|
||||
it('shows fast as unconfirmed when a completed priority request has no actual tier', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
service_tier: 'priority',
|
||||
actual_service_tier: null,
|
||||
status: 'completed',
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'fast · 未确认')
|
||||
})
|
||||
|
||||
it('offers embedding API formats in the usage record filter', () => {
|
||||
|
||||
Reference in New Issue
Block a user