feat: 优化首字时间和 streaming 状态的记录时序

改进 streaming 状态更新机制:
- 统一在首次输出时记录 TTFB 并更新 streaming 状态
- 重构 CliMessageHandlerBase 中的状态更新逻辑,消除重复
- 确保 provider/key 信息在 streaming 状态更新时已可用

前端改进:
- 添加 first_byte_time_ms 字段支持
- 管理员接口支持返回 provider/api_key_name 字段
- 优化活跃请求轮询逻辑,更准确地判断是否需要刷新完整数据

数据库与 API:
- UsageService.get_active_requests_status 添加 include_admin_fields 参数
- 管理员接口调用时启用该参数以获取额外信息
This commit is contained in:
fawney19
2026-01-05 10:31:34 +08:00
parent 43f349d415
commit 4fa9a1303a
7 changed files with 97 additions and 72 deletions

View File

@@ -203,11 +203,12 @@ export const meApi = {
async getActiveRequests(ids?: string): Promise<{
requests: Array<{
id: string
status: string
status: 'pending' | 'streaming' | 'completed' | 'failed'
input_tokens: number
output_tokens: number
cost: number
response_time_ms: number | null
first_byte_time_ms: number | null
}>
}> {
const params = ids ? { ids } : {}

View File

@@ -193,6 +193,9 @@ export const usageApi = {
output_tokens: number
cost: number
response_time_ms: number | null
first_byte_time_ms: number | null
provider?: string | null
api_key_name?: string | null
}>
}> {
const params = ids?.length ? { ids: ids.join(',') } : {}

View File

@@ -259,27 +259,40 @@ async function pollActiveRequests() {
? await usageApi.getActiveRequests(activeRequestIds.value)
: await meApi.getActiveRequests(idsParam)
// 检查是否有状态变化
let hasChanges = false
let shouldRefresh = false
for (const update of requests) {
const record = currentRecords.value.find(r => r.id === update.id)
if (record && record.status !== update.status) {
hasChanges = true
// 如果状态变为 completed 或 failed需要刷新获取完整数据
if (update.status === 'completed' || update.status === 'failed') {
break
}
// 否则只更新状态和 token 信息
if (!record) {
// 后端返回了未知的活跃请求,触发刷新以获取完整数据
shouldRefresh = true
continue
}
// 状态变化completed/failed 需要刷新获取完整数据
if (record.status !== update.status) {
record.status = update.status
record.input_tokens = update.input_tokens
record.output_tokens = update.output_tokens
record.cost = update.cost
record.response_time_ms = update.response_time_ms ?? undefined
}
if (update.status === 'completed' || update.status === 'failed') {
shouldRefresh = true
}
// 进行中状态也需要持续更新provider/key/TTFB 可能在 streaming 后才落库)
record.input_tokens = update.input_tokens
record.output_tokens = update.output_tokens
record.cost = update.cost
record.response_time_ms = update.response_time_ms ?? undefined
record.first_byte_time_ms = update.first_byte_time_ms ?? undefined
// 管理员接口返回额外字段
if ('provider' in update && typeof update.provider === 'string') {
record.provider = update.provider
}
if ('api_key_name' in update) {
record.api_key_name = typeof update.api_key_name === 'string' ? update.api_key_name : undefined
}
}
// 如果有请求完成或失败,刷新整个列表获取完整数据
if (hasChanges && requests.some(r => r.status === 'completed' || r.status === 'failed')) {
if (shouldRefresh) {
await refreshData()
}
} catch (error) {