mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
fix(usage): streaming 状态在首字节到达前显示为 pending
新增 resolveDisplayRequestStatus 函数,当记录状态为 streaming 但 first_byte_time_ms 为空时回退显示为 pending,避免误导用户。
This commit is contained in:
@@ -212,14 +212,14 @@
|
||||
失败
|
||||
</Badge>
|
||||
<Badge
|
||||
v-else-if="record.status === 'pending'"
|
||||
v-else-if="getDisplayStatus(record) === 'pending'"
|
||||
variant="outline"
|
||||
class="whitespace-nowrap animate-pulse border-muted-foreground/30 text-muted-foreground text-[10px] px-1.5 h-4 leading-4 inline-flex items-center"
|
||||
>
|
||||
等待
|
||||
</Badge>
|
||||
<Badge
|
||||
v-else-if="record.status === 'streaming'"
|
||||
v-else-if="getDisplayStatus(record) === 'streaming'"
|
||||
variant="outline"
|
||||
class="whitespace-nowrap animate-pulse border-primary/50 text-primary text-[10px] px-1.5 h-4 leading-4 inline-flex items-center"
|
||||
>
|
||||
@@ -256,11 +256,11 @@
|
||||
<div class="flex items-center gap-1.5">
|
||||
<!-- 耗时 -->
|
||||
<span
|
||||
v-if="record.status === 'pending' || record.status === 'streaming'"
|
||||
v-if="getDisplayStatus(record) === 'pending' || getDisplayStatus(record) === 'streaming'"
|
||||
class="text-primary tabular-nums"
|
||||
><ElapsedTimeText
|
||||
:created-at="record.created_at"
|
||||
:status="record.status"
|
||||
:status="getDisplayStatus(record)"
|
||||
:response-time-ms="record.response_time_ms ?? null"
|
||||
/></span>
|
||||
<span
|
||||
@@ -507,14 +507,14 @@
|
||||
<TableCell class="text-center py-4 w-[70px]">
|
||||
<!-- 优先显示请求状态 -->
|
||||
<Badge
|
||||
v-if="record.status === 'pending'"
|
||||
v-if="getDisplayStatus(record) === 'pending'"
|
||||
variant="outline"
|
||||
class="whitespace-nowrap animate-pulse border-muted-foreground/30 text-muted-foreground"
|
||||
>
|
||||
等待中
|
||||
</Badge>
|
||||
<Badge
|
||||
v-else-if="record.status === 'streaming'"
|
||||
v-else-if="getDisplayStatus(record) === 'streaming'"
|
||||
variant="outline"
|
||||
class="whitespace-nowrap animate-pulse border-primary/50 text-primary"
|
||||
>
|
||||
@@ -577,19 +577,19 @@
|
||||
<TableCell class="text-right py-4 w-[70px]">
|
||||
<!-- pending 状态:只显示增长的总时间 -->
|
||||
<div
|
||||
v-if="record.status === 'pending'"
|
||||
v-if="getDisplayStatus(record) === 'pending'"
|
||||
class="flex flex-col items-end text-xs gap-0.5"
|
||||
>
|
||||
<span class="text-muted-foreground">-</span>
|
||||
<span class="text-primary tabular-nums"><ElapsedTimeText
|
||||
:created-at="record.created_at"
|
||||
:status="record.status"
|
||||
:status="getDisplayStatus(record)"
|
||||
:response-time-ms="record.response_time_ms ?? null"
|
||||
/></span>
|
||||
</div>
|
||||
<!-- streaming 状态:首字固定 + 总时间增长 -->
|
||||
<div
|
||||
v-else-if="record.status === 'streaming'"
|
||||
v-else-if="getDisplayStatus(record) === 'streaming'"
|
||||
class="flex flex-col items-end text-xs gap-0.5"
|
||||
>
|
||||
<span
|
||||
@@ -602,7 +602,7 @@
|
||||
>-</span>
|
||||
<span class="text-primary tabular-nums"><ElapsedTimeText
|
||||
:created-at="record.created_at"
|
||||
:status="record.status"
|
||||
:status="getDisplayStatus(record)"
|
||||
:response-time-ms="record.response_time_ms ?? null"
|
||||
/></span>
|
||||
</div>
|
||||
@@ -678,7 +678,7 @@ import { RefreshCcw, Search } from 'lucide-vue-next'
|
||||
import { formatTokens, formatCurrency } from '@/utils/format'
|
||||
import { formatDateTime } from '../composables'
|
||||
import { getEffectiveInputTokens } from '../token-normalization'
|
||||
import { isUsageRecordFailed } from '../utils/status'
|
||||
import { isUsageRecordFailed, resolveDisplayRequestStatus } from '../utils/status'
|
||||
import { useRowClick } from '@/composables/useRowClick'
|
||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
||||
import type { DateRangeParams, UsageRecord } from '../types'
|
||||
@@ -759,6 +759,10 @@ const emitSearchDebounced = useDebounceFn((value: string) => {
|
||||
emit('update:filterSearch', value)
|
||||
}, 300)
|
||||
|
||||
function getDisplayStatus(record: UsageRecord) {
|
||||
return resolveDisplayRequestStatus(record)
|
||||
}
|
||||
|
||||
watch(() => props.filterSearch, (value) => {
|
||||
if (value !== localSearch.value) {
|
||||
localSearch.value = value
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
isUsageRecordSuccessful,
|
||||
mapRequestStatusToTimelineStatus,
|
||||
normalizeRequestStatus,
|
||||
resolveDisplayRequestStatus,
|
||||
resolveTimelineFinalStatus,
|
||||
} from '../status'
|
||||
import type { UsageRecord } from '../../types'
|
||||
@@ -64,6 +65,18 @@ describe('usage status helpers', () => {
|
||||
expect(mapRequestStatusToTimelineStatus('failed')).toBe('failed')
|
||||
})
|
||||
|
||||
it('shows streaming as pending until first byte is recorded', () => {
|
||||
expect(resolveDisplayRequestStatus(buildUsageRecord({
|
||||
status: 'streaming',
|
||||
first_byte_time_ms: undefined,
|
||||
}))).toBe('pending')
|
||||
|
||||
expect(resolveDisplayRequestStatus(buildUsageRecord({
|
||||
status: 'streaming',
|
||||
first_byte_time_ms: 320,
|
||||
}))).toBe('streaming')
|
||||
})
|
||||
|
||||
it('treats explicit success status code as authoritative for the timeline', () => {
|
||||
expect(resolveTimelineFinalStatus({
|
||||
traceFinalStatus: 'success',
|
||||
|
||||
@@ -76,6 +76,16 @@ export function normalizeRequestStatus(status: RequestStatusLike): RequestStatus
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDisplayRequestStatus(
|
||||
record: Pick<UsageRecord, 'status' | 'first_byte_time_ms'>
|
||||
): RequestStatus | undefined {
|
||||
const status = normalizeRequestStatus(record.status)
|
||||
if (status === 'streaming' && record.first_byte_time_ms == null) {
|
||||
return 'pending'
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
export function mapRequestStatusToTimelineStatus(
|
||||
status: RequestStatusLike
|
||||
): TimelineFinalStatus | undefined {
|
||||
|
||||
Reference in New Issue
Block a user