feat(frontend): Responses WebSocket 配置与用量展示

provider 表单支持开启 Responses WebSocket;用量列表、状态与详情
区分 WebSocket 请求。
This commit is contained in:
AAEE86
2026-08-17 14:52:25 +08:00
committed by ZheFox
parent 621a528083
commit 1353d76e07
13 changed files with 97 additions and 0 deletions
@@ -277,6 +277,14 @@
>
取消
</Badge>
<Badge
v-else-if="isUsageWebSocket(record)"
variant="outline"
data-usage-transport="websocket"
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
</Badge>
<Badge
v-else-if="getStreamModeSegments(record).hasConversion"
:variant="streamBadgeVariant(getStreamModeSegments(record).client === '流式')"
@@ -831,6 +839,14 @@
>
已取消
</Badge>
<Badge
v-else-if="isUsageWebSocket(record)"
variant="outline"
data-usage-transport="websocket"
class="whitespace-nowrap border-sky-500/50 text-sky-600 dark:text-sky-400"
>
WS
</Badge>
<Badge
v-else-if="getStreamModeSegments(record).hasConversion"
:variant="streamBadgeVariant(getStreamModeSegments(record).client === '流式')"
@@ -1022,6 +1038,7 @@ import {
formatUsageStreamLabel,
isUsageRecordFailed,
isUsageUpstreamStream,
isUsageWebSocket,
resolveDisplayRequestStatus,
resolveUsageStreamLabelSegments
} from '../utils/status'
@@ -342,6 +342,17 @@ describe('UsageRecordsTable', () => {
expect(root.textContent).toContain('gpt-5')
})
it('shows WS in the type column for completed WebSocket usage records', () => {
const root = mountUsageRecordsTable([buildRecord({
is_websocket: true,
status: 'completed',
})])
const badges = [...root.querySelectorAll<HTMLElement>('[data-usage-transport="websocket"]')]
expect(badges.length).toBeGreaterThan(0)
expect(badges.every(badge => badge.textContent?.trim() === 'WS')).toBe(true)
})
it('shows reasoning effort next to the model name', () => {
const root = mountUsageRecordsTable([buildRecord({
requested_reasoning_effort: 'xhigh',
@@ -647,6 +647,7 @@ export function useUsageData(options: UseUsageDataOptions) {
? (record.image_progress ?? existing.image_progress)
: existing.image_progress,
is_stream: upstreamIsStream,
is_websocket: mergeBooleanTrueWins(existing.is_websocket, record.is_websocket),
upstream_is_stream: upstreamIsStream,
client_requested_stream: clientRequestedStream,
client_is_stream: clientIsStream,
+1
View File
@@ -120,6 +120,7 @@ export interface UsageRecord {
end_to_end_time_ms?: number | null // 客户端从请求进入网关到完成的总耗时
end_to_end_first_byte_time_ms?: number | null // 客户端从请求进入网关到首字节的耗时
is_stream: boolean
is_websocket?: boolean
upstream_is_stream?: boolean
client_requested_stream?: boolean
client_is_stream?: boolean
@@ -6,6 +6,7 @@ import {
hasUsageRetry,
isUsageRecordFailed,
isUsageRecordSuccessful,
isUsageWebSocket,
mapRequestStatusToTimelineStatus,
normalizeRequestStatus,
resolveDisplayRequestStatus,
@@ -177,6 +178,12 @@ describe('usage status helpers', () => {
expect(hasUsageRetry(buildUsageRecord({ has_retry: undefined }))).toBe(false)
})
it('recognizes persisted WebSocket usage records', () => {
expect(isUsageWebSocket(buildUsageRecord({ is_websocket: true }))).toBe(true)
expect(isUsageWebSocket(buildUsageRecord({ is_websocket: false }))).toBe(false)
expect(isUsageWebSocket(buildUsageRecord({ is_websocket: undefined }))).toBe(false)
})
it('prefers symmetric stream aliases when present', () => {
expect(formatUsageStreamLabel(buildUsageRecord({
is_stream: true,
@@ -49,6 +49,12 @@ export function hasUsageRetry(
return record.has_retry === true
}
export function isUsageWebSocket(
record: Pick<UsageRecord, 'is_websocket'>
): boolean {
return record.is_websocket === true
}
export function resolveUsageStreamModes(
record: Pick<
UsageRecord,