mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
fix frontend openai responses alias display
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import {
|
||||||
|
API_FORMATS,
|
||||||
|
formatApiFormat,
|
||||||
|
formatApiFormatShort,
|
||||||
|
groupApiFormats,
|
||||||
|
normalizeApiFormatAlias,
|
||||||
|
sortApiFormats,
|
||||||
|
} from '@/api/endpoints/types'
|
||||||
|
|
||||||
|
const openaiAlias = (kind: string) => ['openai', kind].join(':')
|
||||||
|
const legacyEnumAlias = (kind: string) => ['OPENAI', kind].join('_')
|
||||||
|
|
||||||
|
describe('api format display helpers', () => {
|
||||||
|
it('maps historical OpenAI response aliases to current display names', () => {
|
||||||
|
expect(normalizeApiFormatAlias(openaiAlias('cli'))).toBe(API_FORMATS.OPENAI_RESPONSES)
|
||||||
|
expect(formatApiFormat(openaiAlias('cli'))).toBe('OpenAI Responses')
|
||||||
|
expect(formatApiFormatShort(openaiAlias('cli'))).toBe('OR')
|
||||||
|
|
||||||
|
expect(normalizeApiFormatAlias(openaiAlias('compact'))).toBe(API_FORMATS.OPENAI_RESPONSES_COMPACT)
|
||||||
|
expect(formatApiFormat(openaiAlias('compact'))).toBe('OpenAI Responses Compact')
|
||||||
|
expect(formatApiFormatShort(openaiAlias('compact'))).toBe('ORC')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('maps historical uppercase enum aliases to current display names', () => {
|
||||||
|
expect(normalizeApiFormatAlias(legacyEnumAlias('CLI'))).toBe(API_FORMATS.OPENAI_RESPONSES)
|
||||||
|
expect(formatApiFormat(legacyEnumAlias('CLI'))).toBe('OpenAI Responses')
|
||||||
|
expect(formatApiFormatShort(legacyEnumAlias('CLI'))).toBe('OR')
|
||||||
|
|
||||||
|
expect(normalizeApiFormatAlias(legacyEnumAlias('COMPACT'))).toBe(API_FORMATS.OPENAI_RESPONSES_COMPACT)
|
||||||
|
expect(formatApiFormat(legacyEnumAlias('COMPACT'))).toBe('OpenAI Responses Compact')
|
||||||
|
expect(formatApiFormatShort(legacyEnumAlias('COMPACT'))).toBe('ORC')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sorts historical aliases in the same slot as their current formats', () => {
|
||||||
|
expect(sortApiFormats([
|
||||||
|
openaiAlias('compact'),
|
||||||
|
API_FORMATS.OPENAI,
|
||||||
|
openaiAlias('cli'),
|
||||||
|
])).toEqual([
|
||||||
|
API_FORMATS.OPENAI,
|
||||||
|
openaiAlias('cli'),
|
||||||
|
openaiAlias('compact'),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('groups uppercase historical aliases under OpenAI', () => {
|
||||||
|
expect(groupApiFormats([legacyEnumAlias('CLI')])).toEqual([{
|
||||||
|
family: 'openai',
|
||||||
|
label: 'OpenAI',
|
||||||
|
formats: [legacyEnumAlias('CLI')],
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -111,6 +111,27 @@ export function parseApiFormat(format: string): { family: string; kind: string }
|
|||||||
return { family: format.slice(0, idx).toLowerCase(), kind: format.slice(idx + 1).toLowerCase() }
|
return { family: format.slice(0, idx).toLowerCase(), kind: format.slice(idx + 1).toLowerCase() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeApiFormatAlias(format: string | null | undefined): string {
|
||||||
|
const raw = format?.trim() ?? ''
|
||||||
|
switch (raw.toLowerCase()) {
|
||||||
|
case 'openai:cli':
|
||||||
|
return API_FORMATS.OPENAI_RESPONSES
|
||||||
|
case 'openai:compact':
|
||||||
|
return API_FORMATS.OPENAI_RESPONSES_COMPACT
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (raw.toUpperCase()) {
|
||||||
|
case 'OPENAI_CLI':
|
||||||
|
return API_FORMATS.OPENAI_RESPONSES
|
||||||
|
case 'OPENAI_COMPACT':
|
||||||
|
return API_FORMATS.OPENAI_RESPONSES_COMPACT
|
||||||
|
default:
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 工具函数:按 family 分组并排序 API 格式数组
|
// 工具函数:按 family 分组并排序 API 格式数组
|
||||||
export interface ApiFormatGroup {
|
export interface ApiFormatGroup {
|
||||||
family: string
|
family: string
|
||||||
@@ -122,7 +143,7 @@ export function groupApiFormats(formats: string[]): ApiFormatGroup[] {
|
|||||||
const sorted = sortApiFormats(formats)
|
const sorted = sortApiFormats(formats)
|
||||||
const groups = new Map<string, string[]>()
|
const groups = new Map<string, string[]>()
|
||||||
for (const f of sorted) {
|
for (const f of sorted) {
|
||||||
const { family } = parseApiFormat(f)
|
const { family } = parseApiFormat(normalizeApiFormatAlias(f))
|
||||||
if (!groups.has(family)) groups.set(family, [])
|
if (!groups.has(family)) groups.set(family, [])
|
||||||
groups.get(family)?.push(f)
|
groups.get(family)?.push(f)
|
||||||
}
|
}
|
||||||
@@ -145,13 +166,26 @@ export function groupApiFormats(formats: string[]): ApiFormatGroup[] {
|
|||||||
// 工具函数:将 API 格式签名转为友好显示名称
|
// 工具函数:将 API 格式签名转为友好显示名称
|
||||||
export function formatApiFormat(format: string | null | undefined): string {
|
export function formatApiFormat(format: string | null | undefined): string {
|
||||||
if (!format) return '-'
|
if (!format) return '-'
|
||||||
const raw = format.trim()
|
const normalized = normalizeApiFormatAlias(format)
|
||||||
const upper = raw.toUpperCase()
|
if (!normalized) return '-'
|
||||||
return API_FORMAT_LABELS[raw]
|
const upper = normalized.toUpperCase()
|
||||||
|| API_FORMAT_LABELS[raw.toLowerCase()]
|
return API_FORMAT_LABELS[normalized]
|
||||||
|
|| API_FORMAT_LABELS[normalized.toLowerCase()]
|
||||||
|| API_FORMAT_LABELS[legacyUppercaseApiFormatKey(upper)]
|
|| API_FORMAT_LABELS[legacyUppercaseApiFormatKey(upper)]
|
||||||
|| API_FORMAT_LABELS[upper]
|
|| API_FORMAT_LABELS[upper]
|
||||||
|| raw
|
|| normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatApiFormatShort(format: string | null | undefined): string {
|
||||||
|
if (!format) return '-'
|
||||||
|
const normalized = normalizeApiFormatAlias(format)
|
||||||
|
if (!normalized) return '-'
|
||||||
|
const upper = normalized.toUpperCase()
|
||||||
|
return API_FORMAT_SHORT[normalized]
|
||||||
|
|| API_FORMAT_SHORT[normalized.toLowerCase()]
|
||||||
|
|| API_FORMAT_SHORT[legacyUppercaseApiFormatKey(upper)]
|
||||||
|
|| API_FORMAT_SHORT[upper]
|
||||||
|
|| normalized.substring(0, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
function legacyUppercaseApiFormatKey(value: string): string {
|
function legacyUppercaseApiFormatKey(value: string): string {
|
||||||
@@ -168,8 +202,8 @@ function legacyUppercaseApiFormatKey(value: string): string {
|
|||||||
// 工具函数:按标准顺序排序 API 格式数组
|
// 工具函数:按标准顺序排序 API 格式数组
|
||||||
export function sortApiFormats(formats: string[]): string[] {
|
export function sortApiFormats(formats: string[]): string[] {
|
||||||
return [...formats].sort((a, b) => {
|
return [...formats].sort((a, b) => {
|
||||||
const aIdx = API_FORMAT_ORDER.indexOf(a)
|
const aIdx = API_FORMAT_ORDER.indexOf(normalizeApiFormatAlias(a))
|
||||||
const bIdx = API_FORMAT_ORDER.indexOf(b)
|
const bIdx = API_FORMAT_ORDER.indexOf(normalizeApiFormatAlias(b))
|
||||||
if (aIdx === -1 && bIdx === -1) return 0
|
if (aIdx === -1 && bIdx === -1) return 0
|
||||||
if (aIdx === -1) return 1
|
if (aIdx === -1) return 1
|
||||||
if (bIdx === -1) return -1
|
if (bIdx === -1) return -1
|
||||||
|
|||||||
@@ -287,7 +287,7 @@
|
|||||||
:key="fmt"
|
:key="fmt"
|
||||||
class="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground"
|
class="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground"
|
||||||
>
|
>
|
||||||
{{ API_FORMAT_SHORT[fmt] || fmt }}
|
{{ formatApiFormatShort(fmt) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -375,7 +375,7 @@ import {
|
|||||||
type AllowedModels,
|
type AllowedModels,
|
||||||
} from '@/api/endpoints'
|
} from '@/api/endpoints'
|
||||||
import { useUpstreamModelsCache } from '../composables/useUpstreamModelsCache'
|
import { useUpstreamModelsCache } from '../composables/useUpstreamModelsCache'
|
||||||
import { API_FORMAT_SHORT, type UpstreamModel } from '@/api/endpoints/types'
|
import { formatApiFormatShort, type UpstreamModel } from '@/api/endpoints/types'
|
||||||
|
|
||||||
interface AvailableModel {
|
interface AvailableModel {
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
:key="fmt"
|
:key="fmt"
|
||||||
class="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground whitespace-nowrap"
|
class="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{{ API_FORMAT_SHORT[fmt] || fmt }}
|
{{ formatApiFormatShort(fmt) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -472,8 +472,14 @@ import { updateProvider, updateProviderKey } from '@/api/endpoints'
|
|||||||
import { getProvidersSummary, type ProviderWithEndpointsSummary } from '@/api/endpoints'
|
import { getProvidersSummary, type ProviderWithEndpointsSummary } from '@/api/endpoints'
|
||||||
import { adminApi } from '@/api/admin'
|
import { adminApi } from '@/api/admin'
|
||||||
import { batchQueryBalance, type ActionResultResponse, type BalanceInfo } from '@/api/providerOps'
|
import { batchQueryBalance, type ActionResultResponse, type BalanceInfo } from '@/api/providerOps'
|
||||||
import { API_FORMAT_SHORT } from '@/api/endpoints/types'
|
import {
|
||||||
import { sortApiFormats, groupApiFormats, parseApiFormat, API_FORMAT_KIND_LABELS } from '@/api/endpoints/types/api-format'
|
sortApiFormats,
|
||||||
|
groupApiFormats,
|
||||||
|
parseApiFormat,
|
||||||
|
API_FORMAT_KIND_LABELS,
|
||||||
|
formatApiFormatShort,
|
||||||
|
normalizeApiFormatAlias,
|
||||||
|
} from '@/api/endpoints/types/api-format'
|
||||||
import { log } from '@/utils/logger'
|
import { log } from '@/utils/logger'
|
||||||
|
|
||||||
interface KeyWithMeta {
|
interface KeyWithMeta {
|
||||||
@@ -893,7 +899,7 @@ const groupedFormats = computed(() => {
|
|||||||
|
|
||||||
// 获取格式的 kind 显示名称
|
// 获取格式的 kind 显示名称
|
||||||
function formatKind(format: string): string {
|
function formatKind(format: string): string {
|
||||||
const { kind } = parseApiFormat(format)
|
const { kind } = parseApiFormat(normalizeApiFormatAlias(format))
|
||||||
return API_FORMAT_KIND_LABELS[kind] || kind || format
|
return API_FORMAT_KIND_LABELS[kind] || kind || format
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -861,7 +861,7 @@
|
|||||||
class="text-muted-foreground/40"
|
class="text-muted-foreground/40"
|
||||||
>/</span>
|
>/</span>
|
||||||
<span :class="{ 'text-destructive': isFormatCircuitOpen(key, format) }">
|
<span :class="{ 'text-destructive': isFormatCircuitOpen(key, format) }">
|
||||||
{{ API_FORMAT_SHORT[format] || format }}
|
{{ formatApiFormatShort(format) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="editingMultiplierKey !== key.id || editingMultiplierFormat !== format"
|
v-if="editingMultiplierKey !== key.id || editingMultiplierFormat !== format"
|
||||||
@@ -1150,7 +1150,6 @@ import {
|
|||||||
type EndpointAPIKey,
|
type EndpointAPIKey,
|
||||||
type Model,
|
type Model,
|
||||||
API_FORMAT_ORDER,
|
API_FORMAT_ORDER,
|
||||||
API_FORMAT_SHORT,
|
|
||||||
sortApiFormats,
|
sortApiFormats,
|
||||||
} from '@/api/endpoints'
|
} from '@/api/endpoints'
|
||||||
import type {
|
import type {
|
||||||
@@ -1162,7 +1161,7 @@ import type {
|
|||||||
QuotaStatusSnapshot,
|
QuotaStatusSnapshot,
|
||||||
QuotaWindowSnapshot,
|
QuotaWindowSnapshot,
|
||||||
} from '@/api/endpoints/types'
|
} from '@/api/endpoints/types'
|
||||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
import { formatApiFormat, formatApiFormatShort } from '@/api/endpoints/types/api-format'
|
||||||
import { isOAuthAccountProviderType, isKeyManagedProviderType } from '../utils/providerTypeUtils'
|
import { isOAuthAccountProviderType, isKeyManagedProviderType } from '../utils/providerTypeUtils'
|
||||||
import {
|
import {
|
||||||
isProviderQuotaAutoRefreshCoolingDown,
|
isProviderQuotaAutoRefreshCoolingDown,
|
||||||
|
|||||||
@@ -200,7 +200,7 @@
|
|||||||
<!-- 上排:缩写 + 百分比 -->
|
<!-- 上排:缩写 + 百分比 -->
|
||||||
<div class="flex items-center justify-between text-[10px] leading-none">
|
<div class="flex items-center justify-between text-[10px] leading-none">
|
||||||
<span class="font-medium text-muted-foreground/80">
|
<span class="font-medium text-muted-foreground/80">
|
||||||
{{ API_FORMAT_SHORT[endpoint.api_format] || endpoint.api_format.substring(0,2) }}
|
{{ formatApiFormatShort(endpoint.api_format) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-medium text-muted-foreground/80">
|
<span class="font-medium text-muted-foreground/80">
|
||||||
{{ isEndpointAvailable(endpoint) ? `${(endpoint.health_score * 100).toFixed(0)}%` : '-' }}
|
{{ isEndpointAvailable(endpoint) ? `${(endpoint.health_score * 100).toFixed(0)}%` : '-' }}
|
||||||
@@ -236,7 +236,7 @@ import {
|
|||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import Button from '@/components/ui/button.vue'
|
import Button from '@/components/ui/button.vue'
|
||||||
import Badge from '@/components/ui/badge.vue'
|
import Badge from '@/components/ui/badge.vue'
|
||||||
import { type ProviderWithEndpointsSummary, API_FORMAT_SHORT } from '@/api/endpoints'
|
import { type ProviderWithEndpointsSummary, formatApiFormatShort } from '@/api/endpoints'
|
||||||
import { formatBillingType } from '@/utils/format'
|
import { formatBillingType } from '@/utils/format'
|
||||||
import { sortEndpoints, isEndpointAvailable, getEndpointDotColor, getEndpointTooltip } from '@/features/providers/composables/useEndpointStatus'
|
import { sortEndpoints, isEndpointAvailable, getEndpointDotColor, getEndpointTooltip } from '@/features/providers/composables/useEndpointStatus'
|
||||||
import { isKeyManagedProviderType } from '../utils/providerTypeUtils'
|
import { isKeyManagedProviderType } from '../utils/providerTypeUtils'
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
<!-- 上排:缩写 + 百分比 -->
|
<!-- 上排:缩写 + 百分比 -->
|
||||||
<div class="flex items-center justify-between text-[10px] leading-none">
|
<div class="flex items-center justify-between text-[10px] leading-none">
|
||||||
<span class="font-medium text-muted-foreground/80">
|
<span class="font-medium text-muted-foreground/80">
|
||||||
{{ API_FORMAT_SHORT[endpoint.api_format] || endpoint.api_format.substring(0,2) }}
|
{{ formatApiFormatShort(endpoint.api_format) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-medium text-muted-foreground/80">
|
<span class="font-medium text-muted-foreground/80">
|
||||||
{{ isEndpointAvailable(endpoint) ? `${(endpoint.health_score * 100).toFixed(0)}%` : '-' }}
|
{{ isEndpointAvailable(endpoint) ? `${(endpoint.health_score * 100).toFixed(0)}%` : '-' }}
|
||||||
@@ -209,7 +209,7 @@ import Badge from '@/components/ui/badge.vue'
|
|||||||
import TableRow from '@/components/ui/table-row.vue'
|
import TableRow from '@/components/ui/table-row.vue'
|
||||||
import TableCell from '@/components/ui/table-cell.vue'
|
import TableCell from '@/components/ui/table-cell.vue'
|
||||||
import ProviderBalanceCell from './ProviderBalanceCell.vue'
|
import ProviderBalanceCell from './ProviderBalanceCell.vue'
|
||||||
import { type ProviderWithEndpointsSummary, API_FORMAT_SHORT } from '@/api/endpoints'
|
import { type ProviderWithEndpointsSummary, formatApiFormatShort } from '@/api/endpoints'
|
||||||
import { sortEndpoints, isEndpointAvailable, getEndpointDotColor, getEndpointTooltip } from '@/features/providers/composables/useEndpointStatus'
|
import { sortEndpoints, isEndpointAvailable, getEndpointDotColor, getEndpointTooltip } from '@/features/providers/composables/useEndpointStatus'
|
||||||
import type { BalanceExtraItem } from '@/features/providers/auth-templates'
|
import type { BalanceExtraItem } from '@/features/providers/auth-templates'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user