diff --git a/admin-ui/src/views/chorus/generations/helpers.js b/admin-ui/src/views/chorus/generations/helpers.js index 974426c..b997665 100644 --- a/admin-ui/src/views/chorus/generations/helpers.js +++ b/admin-ui/src/views/chorus/generations/helpers.js @@ -1,19 +1,70 @@ +const parseTime = value => { + if (!value) return null + const timestamp = new Date(value).getTime() + return Number.isFinite(timestamp) ? timestamp : null +} + +export function durationText(milliseconds) { + if (!Number.isFinite(milliseconds) || milliseconds < 0) return '时间异常' + if (milliseconds < 100) return '<0.1 秒' + const totalSeconds = milliseconds / 1000 + if (totalSeconds < 60) return `${totalSeconds.toFixed(1)} 秒` + const minutes = Math.floor(totalSeconds / 60) + const seconds = (totalSeconds - minutes * 60).toFixed(1).padStart(4, '0') + if (minutes < 60) return `${minutes} 分 ${seconds} 秒` + const hours = Math.floor(minutes / 60) + return `${hours} 小时 ${String(minutes % 60).padStart(2, '0')} 分 ${String(seconds).padStart(2, '0')} 秒` +} + +const between = (start, end) => { + const startTime = parseTime(start) + const endTime = parseTime(end) + if (startTime === null || endTime === null) return '-' + return durationText(endTime - startTime) +} + +export function generationTiming(generation, now = Date.now()) { + const row = generation || {} + const terminal = row.status === 'succeeded' || row.status === 'failed' + const end = terminal ? row.completed_at : new Date(now).toISOString() + const providerAttempts = Array.isArray(row.attempts) + ? row.attempts.filter(item => item && item.type === 'provider' && Number.isFinite(Number(item.latency_ms)) && Number(item.latency_ms) >= 0) + : [] + const upstreamMS = providerAttempts.reduce((sum, item) => sum + Number(item.latency_ms), 0) + return { + total: between(row.created_at, end), + queue: row.started_at ? between(row.created_at, row.started_at) : '-', + processing: row.started_at ? between(row.started_at, end) : '-', + upstream: providerAttempts.length ? durationText(upstreamMS) : '-' + } +} + +export function kindText(value) { + return ({ text: '文本', image: '图片' })[value] || '未知' +} + const eventTitle = attempt => { if (attempt.type === 'lease') return '任务认领' if (attempt.type === 'local_rate_limit') return '本地限流' - if (attempt.type === 'provider') return attempt.provider_model_id ? `模型 #${attempt.provider_model_id}` : '模型未记录' + if (attempt.type === 'provider') { + const provider = attempt.provider_name || 'Provider 未记录' + const model = attempt.model_name || (attempt.provider_model_id ? `模型 #${attempt.provider_model_id}` : '模型未记录') + return `${provider} · ${model}` + } return '系统事件' } export function attemptPresentation(value) { const attempt = value && typeof value === 'object' ? value : {} + const isProvider = attempt.type === 'provider' const presentation = { + label: isProvider ? (attempt.provider_ordinal ? `尝试 ${attempt.provider_ordinal}` : 'Provider 尝试') : '系统事件', title: eventTitle(attempt), status: '进行中', + duration: Number.isFinite(Number(attempt.latency_ms)) && Number(attempt.latency_ms) >= 0 ? durationText(Number(attempt.latency_ms)) : between(attempt.started_at, attempt.finished_at), type: 'warning', timestamp: attempt.finished_at || attempt.started_at || attempt.deferred_until || '' } - if (attempt.error_code) { presentation.status = attempt.error_code presentation.type = 'danger' diff --git a/admin-ui/src/views/chorus/generations/index.vue b/admin-ui/src/views/chorus/generations/index.vue index c1a1693..e5a8d7e 100644 --- a/admin-ui/src/views/chorus/generations/index.vue +++ b/admin-ui/src/views/chorus/generations/index.vue @@ -1,17 +1,18 @@