mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
feat(gateway): add Codex Live and OpenAI Realtime
Implement preflighted Live/Realtime WebSocket transports, protocol-aware authentication, usage auditing, UI filtering, and legacy Codex permission migration.
This commit is contained in:
@@ -158,6 +158,9 @@
|
||||
<SelectItem value="standard">
|
||||
标准
|
||||
</SelectItem>
|
||||
<SelectItem value="websocket">
|
||||
WebSocket (WS)
|
||||
</SelectItem>
|
||||
<SelectItem value="active">
|
||||
活跃
|
||||
</SelectItem>
|
||||
@@ -281,9 +284,10 @@
|
||||
v-else-if="isUsageWebSocket(record)"
|
||||
variant="outline"
|
||||
data-usage-transport="websocket"
|
||||
:title="getWebSocketTransportTitle(record)"
|
||||
class="whitespace-nowrap border-sky-500/50 text-sky-600 dark:text-sky-400 text-[10px] px-1.5 h-4 leading-4 inline-flex items-center flex-shrink-0"
|
||||
>
|
||||
WS
|
||||
{{ getWebSocketTransportLabel(record) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-else-if="getStreamModeSegments(record).hasConversion"
|
||||
@@ -308,9 +312,24 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-end flex-shrink-0">
|
||||
<span class="text-sm text-primary font-semibold leading-5">{{ formatCurrency(record.cost || 0) }}</span>
|
||||
<span
|
||||
v-if="showActualCost && record.actual_cost !== undefined && record.rate_multiplier && record.rate_multiplier !== 1.0"
|
||||
v-if="record.usage_available !== false && record.usage_pricing_available !== false"
|
||||
class="text-sm text-primary font-semibold leading-5"
|
||||
>{{ formatCurrency(record.cost || 0) }}</span>
|
||||
<span
|
||||
v-else-if="record.usage_available === false"
|
||||
data-usage-unavailable="cost"
|
||||
class="text-sm text-muted-foreground font-medium leading-5"
|
||||
title="上游未提供可验证的 token/费用用量"
|
||||
>不可用</span>
|
||||
<span
|
||||
v-else
|
||||
data-usage-unpriced="cost"
|
||||
class="text-sm text-muted-foreground font-medium leading-5"
|
||||
title="token 用量可验证,但当前计价规则不支持该音频用量分项"
|
||||
>未计价</span>
|
||||
<span
|
||||
v-if="record.usage_available !== false && record.usage_pricing_available !== false && showActualCost && record.actual_cost !== undefined && record.rate_multiplier && record.rate_multiplier !== 1.0"
|
||||
class="text-[10px] text-muted-foreground"
|
||||
>{{ formatCurrency(record.actual_cost) }}</span>
|
||||
</div>
|
||||
@@ -380,11 +399,24 @@
|
||||
:title="hasRecordCacheTokens(record) ? getRecordCacheTokensTitle(record) : undefined"
|
||||
>
|
||||
<span class="text-muted-foreground">Tokens</span>
|
||||
<span class="ml-1">{{ formatTokens(getRecordEffectiveInputTokens(record)) }} / {{ formatTokens(record.output_tokens || 0) }}</span>
|
||||
<template v-if="hasRecordCacheTokens(record)">
|
||||
<span
|
||||
v-if="record.usage_available !== false"
|
||||
class="ml-1"
|
||||
>{{ formatTokens(getRecordEffectiveInputTokens(record)) }} / {{ formatTokens(record.output_tokens || 0) }}</span>
|
||||
<span
|
||||
v-else
|
||||
data-usage-unavailable="tokens"
|
||||
class="ml-1 text-muted-foreground"
|
||||
title="上游未提供可验证的 token/费用用量"
|
||||
>不可用</span>
|
||||
<template v-if="record.usage_available !== false && hasRecordCacheTokens(record)">
|
||||
<span class="text-muted-foreground"> | </span>
|
||||
<span>{{ formatOptionalTokens(getRecordCacheReadTokens(record)) }} / {{ formatOptionalTokens(getRecordCacheCreationTokens(record)) }}</span>
|
||||
</template>
|
||||
<template v-if="record.usage_available !== false && ((record.input_audio_tokens || 0) > 0 || (record.output_audio_tokens || 0) > 0)">
|
||||
<span class="text-muted-foreground"> | 音频 </span>
|
||||
<span>{{ formatOptionalTokens(record.input_audio_tokens) }} / {{ formatOptionalTokens(record.output_audio_tokens) }}</span>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -843,9 +875,10 @@
|
||||
v-else-if="isUsageWebSocket(record)"
|
||||
variant="outline"
|
||||
data-usage-transport="websocket"
|
||||
:title="getWebSocketTransportTitle(record)"
|
||||
class="whitespace-nowrap border-sky-500/50 text-sky-600 dark:text-sky-400"
|
||||
>
|
||||
WS
|
||||
{{ getWebSocketTransportLabel(record) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-else-if="getStreamModeSegments(record).hasConversion"
|
||||
@@ -872,44 +905,63 @@
|
||||
v-if="isColumnVisible('tokens')"
|
||||
class="py-4 w-[10%]"
|
||||
>
|
||||
<div class="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] gap-x-1 text-xs leading-tight tabular-nums">
|
||||
<span class="justify-self-end whitespace-nowrap text-right">
|
||||
{{ formatTokens(getRecordEffectiveInputTokens(record)) }}
|
||||
</span>
|
||||
<span class="justify-self-center text-muted-foreground">
|
||||
/
|
||||
</span>
|
||||
<span class="justify-self-start whitespace-nowrap text-left">
|
||||
{{ formatTokens(record.output_tokens || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-0.5 grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] gap-x-1 text-xs leading-tight tabular-nums text-muted-foreground">
|
||||
<span
|
||||
class="justify-self-end whitespace-nowrap text-right"
|
||||
:class="[
|
||||
hasPositiveTokens(getRecordCacheReadTokens(record)) ? 'text-foreground/70' : ''
|
||||
]"
|
||||
<template v-if="record.usage_available !== false">
|
||||
<div class="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] gap-x-1 text-xs leading-tight tabular-nums">
|
||||
<span class="justify-self-end whitespace-nowrap text-right">
|
||||
{{ formatTokens(getRecordEffectiveInputTokens(record)) }}
|
||||
</span>
|
||||
<span class="justify-self-center text-muted-foreground">
|
||||
/
|
||||
</span>
|
||||
<span class="justify-self-start whitespace-nowrap text-left">
|
||||
{{ formatTokens(record.output_tokens || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-0.5 grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] gap-x-1 text-xs leading-tight tabular-nums text-muted-foreground">
|
||||
<span
|
||||
class="justify-self-end whitespace-nowrap text-right"
|
||||
:class="[
|
||||
hasPositiveTokens(getRecordCacheReadTokens(record)) ? 'text-foreground/70' : ''
|
||||
]"
|
||||
>
|
||||
{{ formatOptionalTokens(getRecordCacheReadTokens(record)) }}
|
||||
</span>
|
||||
<span class="justify-self-center">
|
||||
/
|
||||
</span>
|
||||
<span
|
||||
class="justify-self-start whitespace-nowrap text-left"
|
||||
:class="[
|
||||
hasPositiveTokens(getRecordCacheCreationTokens(record)) ? 'text-foreground/70' : ''
|
||||
]"
|
||||
>
|
||||
{{ formatOptionalTokens(getRecordCacheCreationTokens(record)) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="(record.input_audio_tokens || 0) > 0 || (record.output_audio_tokens || 0) > 0"
|
||||
class="mt-0.5 text-right text-[10px] leading-tight tabular-nums text-muted-foreground"
|
||||
>
|
||||
{{ formatOptionalTokens(getRecordCacheReadTokens(record)) }}
|
||||
</span>
|
||||
<span class="justify-self-center">
|
||||
/
|
||||
</span>
|
||||
<span
|
||||
class="justify-self-start whitespace-nowrap text-left"
|
||||
:class="[
|
||||
hasPositiveTokens(getRecordCacheCreationTokens(record)) ? 'text-foreground/70' : ''
|
||||
]"
|
||||
>
|
||||
{{ formatOptionalTokens(getRecordCacheCreationTokens(record)) }}
|
||||
</span>
|
||||
音频 {{ formatOptionalTokens(record.input_audio_tokens) }} / {{ formatOptionalTokens(record.output_audio_tokens) }}
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-else
|
||||
data-usage-unavailable="tokens"
|
||||
class="text-right text-xs text-muted-foreground"
|
||||
title="上游未提供可验证的 token/费用用量"
|
||||
>
|
||||
不可用
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
v-if="isColumnVisible('cost')"
|
||||
class="text-right py-4 w-[6%]"
|
||||
>
|
||||
<div class="flex flex-col items-end text-xs gap-0.5">
|
||||
<div
|
||||
v-if="record.usage_available !== false && record.usage_pricing_available !== false"
|
||||
class="flex flex-col items-end text-xs gap-0.5"
|
||||
>
|
||||
<span class="text-primary font-medium">{{ formatCurrency(record.cost || 0) }}</span>
|
||||
<span
|
||||
v-if="showActualCost && record.actual_cost !== undefined && record.rate_multiplier && record.rate_multiplier !== 1.0"
|
||||
@@ -918,6 +970,22 @@
|
||||
{{ formatCurrency(record.actual_cost) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="record.usage_available === false"
|
||||
data-usage-unavailable="cost"
|
||||
class="text-xs text-muted-foreground"
|
||||
title="上游未提供可验证的 token/费用用量"
|
||||
>
|
||||
不可用
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
data-usage-unpriced="cost"
|
||||
class="text-xs text-muted-foreground"
|
||||
title="token 用量可验证,但当前计价规则不支持该音频用量分项"
|
||||
>
|
||||
未计价
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
v-if="isColumnVisible('performance')"
|
||||
@@ -1048,6 +1116,10 @@ import { API_FORMAT_ORDER, formatApiFormat } from '@/api/endpoints/types/api-for
|
||||
import { formatClientFamily } from '@/features/usage/utils/clientFamily'
|
||||
import { formatServiceTierFact } from '../utils/service-tier'
|
||||
import { isCyberPolicyError } from '../utils/cyberError'
|
||||
import {
|
||||
formatUsageWebSocketTransportLabel as getWebSocketTransportLabel,
|
||||
formatUsageWebSocketTransportTitle as getWebSocketTransportTitle,
|
||||
} from '../utils/websocketTransport'
|
||||
import type { DateRangeParams, UsageRecord } from '../types'
|
||||
import { MultiSelect, TimeRangePicker } from '@/components/common'
|
||||
import type { MultiSelectOption } from '@/components/common/MultiSelect.vue'
|
||||
@@ -1295,6 +1367,7 @@ const statusFilterOptions: FilterOption[] = [
|
||||
{ value: '__all__', label: '全部状态' },
|
||||
{ value: 'stream', label: '流式' },
|
||||
{ value: 'standard', label: '标准' },
|
||||
{ value: 'websocket', label: 'WebSocket (WS)' },
|
||||
{ value: 'active', label: '活跃' },
|
||||
{ value: 'failed', label: '失败' },
|
||||
{ value: 'cancelled', label: '已取消' },
|
||||
|
||||
Reference in New Issue
Block a user