mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
feat(usage): add performance column and align Fast indicators (#354)
Co-authored-by: Zyy <zyycn@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -63,7 +63,16 @@ function itemValueClass(tone?: string, accent?: boolean) {
|
||||
{{ usageBillingText(record) }}
|
||||
</span>
|
||||
|
||||
<div v-if="billing" class="flex flex-col items-center gap-1">
|
||||
<div v-if="billing" class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="billing.serviceTierDisplay === 'Fast'"
|
||||
class="inline-flex size-4 items-center justify-center rounded-full bg-cp-warning-container text-cp-warning-on-container"
|
||||
title="Fast 模式"
|
||||
role="img"
|
||||
aria-label="Fast 模式"
|
||||
>
|
||||
<Zap class="size-3" stroke-width="2.2" aria-hidden="true" />
|
||||
</span>
|
||||
<UsageDetailPopover
|
||||
:title="billing.longContextBillingApplied ? '长上下文计费明细' : '计费明细'"
|
||||
:trigger-label="billing.longContextBillingApplied ? '查看长上下文计费明细' : '查看费用明细'"
|
||||
@@ -86,15 +95,6 @@ function itemValueClass(tone?: string, accent?: boolean) {
|
||||
</div>
|
||||
</div>
|
||||
</UsageDetailPopover>
|
||||
<span
|
||||
v-if="billing.serviceTierDisplay === 'Fast'"
|
||||
class="inline-flex size-4 items-center justify-center rounded-full bg-cp-warning-container text-cp-warning-on-container"
|
||||
title="Fast 加速"
|
||||
role="img"
|
||||
aria-label="Fast 加速"
|
||||
>
|
||||
<Zap class="size-3" stroke-width="2.2" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { UsagePerformanceRecord } from './shared/presenter'
|
||||
|
||||
import { computed } from 'vue'
|
||||
import { usagePerformanceDetails } from './shared/presenter'
|
||||
|
||||
const props = defineProps<{
|
||||
record: UsagePerformanceRecord
|
||||
}>()
|
||||
|
||||
const performance = computed(() => usagePerformanceDetails(props.record))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid justify-items-end gap-y-1.5 whitespace-nowrap font-mono text-cp-sm leading-none font-heavy tabular-nums">
|
||||
<span class="text-cp-text">{{ performance.throughputDisplay }}</span>
|
||||
<span class="text-cp-xs text-cp-text-secondary">
|
||||
<span title="首字时间" class="text-cp-text-quaternary">TTFT</span>
|
||||
{{ performance.firstTokenDisplay }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -10,6 +10,7 @@ import UsageBillingCell from '@/components/usage/UsageBillingCell.vue'
|
||||
import UsageClientIpCell from '@/components/usage/UsageClientIpCell.vue'
|
||||
import UsageLatencyCell from '@/components/usage/UsageLatencyCell.vue'
|
||||
import UsageModelCell from '@/components/usage/UsageModelCell.vue'
|
||||
import UsagePerformanceCell from '@/components/usage/UsagePerformanceCell.vue'
|
||||
import UsageReasoningEffortCell from '@/components/usage/UsageReasoningEffortCell.vue'
|
||||
import UsageTokenCell from '@/components/usage/UsageTokenCell.vue'
|
||||
import UsageTransportBadge from '@/components/usage/UsageTransportBadge.vue'
|
||||
@@ -134,6 +135,10 @@ withDefaults(
|
||||
<UsageLatencyCell :record="row" />
|
||||
</template>
|
||||
|
||||
<template #performance="{ row }">
|
||||
<UsagePerformanceCell :record="row" />
|
||||
</template>
|
||||
|
||||
<template v-if="$slots.actions" #actions="scope">
|
||||
<slot name="actions" v-bind="scope" />
|
||||
</template>
|
||||
|
||||
@@ -28,6 +28,7 @@ export const usageRecordColumns = defineTableColumns<UsageListRecord>([
|
||||
{ key: 'tokenDetails', label: 'TOKEN', kind: 'numeric', size: 'xl' },
|
||||
{ key: 'billing', label: '费用', kind: 'numeric', size: 'xl' },
|
||||
{ key: 'latency', label: '延迟', kind: 'numeric', size: 'xl' },
|
||||
{ key: 'performance', label: '性能', kind: 'numeric', size: 'lg' },
|
||||
{ key: 'createdAtDisplay', label: '时间', kind: 'datetime' },
|
||||
{ key: 'clientIp', label: 'IP', kind: 'custom', size: '3xl' },
|
||||
{ key: 'userAgent', label: 'User-Agent', kind: 'custom', size: '4xl' },
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { UsageListRecord, UsageRecordDetail } from '@/api'
|
||||
import { formatDuration } from '@/utils/format'
|
||||
import { formatCompactNumber, formatDuration } from '@/utils/format'
|
||||
|
||||
// 列表与详情使用独立读模型,展示函数只依赖两者的公共字段。
|
||||
type UsageCommonRecord = UsageListRecord | UsageRecordDetail
|
||||
type UsageLatencyRecord = Pick<UsageCommonRecord, 'latencyDetails' | 'firstTokenLatencyMs' | 'latencyMs'>
|
||||
|
||||
export type UsagePerformanceRecord = UsageLatencyRecord & {
|
||||
tokenDetails: Pick<UsageListRecord['tokenDetails'], 'outputTokens'> | null
|
||||
}
|
||||
|
||||
export function usageTransportType(transport?: string | null) {
|
||||
if (transport === 'websocket')
|
||||
@@ -78,11 +83,25 @@ export function usageModelDisplay(record: UsageCommonRecord) {
|
||||
return { primary, secondary, routes }
|
||||
}
|
||||
|
||||
export function usageLatencyDetails(record: Pick<UsageCommonRecord, 'latencyDetails' | 'firstTokenLatencyMs' | 'latencyMs'>) {
|
||||
export function usagePerformanceDetails(record: UsagePerformanceRecord) {
|
||||
const firstTokenMs = usageFirstTokenMs(record)
|
||||
const totalMs = durationValue(record.latencyMs)
|
||||
const outputTokens = record.tokenDetails?.outputTokens
|
||||
// 与性能统计使用同一吞吐口径,首个生命周期事件不能代替首字
|
||||
const throughput = typeof outputTokens === 'number' && Number.isFinite(outputTokens) && outputTokens > 0
|
||||
&& firstTokenMs !== null && totalMs !== null && totalMs > firstTokenMs
|
||||
? outputTokens * 1000 / (totalMs - firstTokenMs)
|
||||
: null
|
||||
|
||||
return {
|
||||
throughputDisplay: throughput === null ? '—' : `${formatCompactNumber(throughput)} tok/s`,
|
||||
firstTokenDisplay: formatDuration(firstTokenMs),
|
||||
}
|
||||
}
|
||||
|
||||
export function usageLatencyDetails(record: UsageLatencyRecord) {
|
||||
const latencyDetails = record.latencyDetails
|
||||
const firstTokenMs = durationValue(
|
||||
record.firstTokenLatencyMs ?? latencyDetails?.firstTokenMs,
|
||||
)
|
||||
const firstTokenMs = usageFirstTokenMs(record)
|
||||
const firstEventMs = durationValue(latencyDetails?.firstEventMs)
|
||||
const totalMs = durationValue(record.latencyMs)
|
||||
const firstReasoningMs = durationValue(latencyDetails?.firstReasoningMs)
|
||||
@@ -152,3 +171,7 @@ export function usageBillingText(record: Pick<UsageCommonRecord, 'billing'>) {
|
||||
function durationValue(value: unknown) {
|
||||
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null
|
||||
}
|
||||
|
||||
function usageFirstTokenMs(record: UsageLatencyRecord) {
|
||||
return durationValue(record.firstTokenLatencyMs ?? record.latencyDetails?.firstTokenMs)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { computed } from 'vue'
|
||||
import UsageBillingCell from '@/components/usage/UsageBillingCell.vue'
|
||||
import UsageClientIpCell from '@/components/usage/UsageClientIpCell.vue'
|
||||
import UsageLatencyCell from '@/components/usage/UsageLatencyCell.vue'
|
||||
import UsagePerformanceCell from '@/components/usage/UsagePerformanceCell.vue'
|
||||
import UsageTokenCell from '@/components/usage/UsageTokenCell.vue'
|
||||
import UsageTransportBadge from '@/components/usage/UsageTransportBadge.vue'
|
||||
|
||||
@@ -21,6 +22,7 @@ const columns = computed(() => defineTableColumns<KeyUsageRecord>([
|
||||
{ key: 'tokenDetails', label: 'TOKEN', kind: 'numeric', size: 'xl' },
|
||||
{ key: 'billing', label: '费用', kind: 'numeric', size: 'xl' },
|
||||
{ key: 'latency', label: '延迟', kind: 'numeric', size: 'xl' },
|
||||
{ key: 'performance', label: '性能', kind: 'numeric', size: 'lg' },
|
||||
...(kind.value === 'error' ? [{ key: 'statusCode', label: '状态', kind: 'status' as const }] : []),
|
||||
{ key: 'createdAt', label: '时间', kind: 'datetime' },
|
||||
{ key: 'clientIp', label: 'IP', kind: 'custom', size: '3xl' },
|
||||
@@ -60,6 +62,9 @@ const columns = computed(() => defineTableColumns<KeyUsageRecord>([
|
||||
<template #latency="{ row }">
|
||||
<UsageLatencyCell :record="row" />
|
||||
</template>
|
||||
<template #performance="{ row }">
|
||||
<UsagePerformanceCell :record="row" />
|
||||
</template>
|
||||
<template #statusCode="{ row }">
|
||||
<span class="font-mono text-cp-sm font-bold text-cp-error-text">{{ row.statusCode ?? '错误' }}</span>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user