fix: 修正生成尝试状态展示 (#61)

This commit is contained in:
ila
2026-08-25 17:51:20 +08:00
parent 9bb713b1b9
commit dfeedb02f3
3 changed files with 83 additions and 2 deletions
@@ -0,0 +1,37 @@
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}` : '模型未记录'
return '系统事件'
}
export function attemptPresentation(value) {
const attempt = value && typeof value === 'object' ? value : {}
const presentation = {
title: eventTitle(attempt),
status: '进行中',
type: 'warning',
timestamp: attempt.finished_at || attempt.started_at || attempt.deferred_until || ''
}
if (attempt.error_code) {
presentation.status = attempt.error_code
presentation.type = 'danger'
return presentation
}
if (attempt.type === 'lease') {
presentation.status = '已认领'
presentation.type = 'primary'
return presentation
}
if (attempt.type === 'local_rate_limit') {
presentation.status = attempt.deferred_until ? '等待限流窗口' : '限流检查完成'
presentation.type = attempt.deferred_until ? 'warning' : 'info'
return presentation
}
if (attempt.finished_at) {
presentation.status = '成功'
presentation.type = 'success'
}
return presentation
}
@@ -11,7 +11,7 @@
<div v-if="!selected" class="empty-state"><i class="ri-file-search-line detail-icon" /><p>选择一条生成记录查看详情</p></div>
<template v-else><div class="detail-head"><h2>任务 #{{ selected.id }}</h2><el-tag :type="statusType(selected.status)">{{ statusText(selected.status) }}</el-tag></div>
<dl class="detail-list"><dt>用户</dt><dd>#{{ selected.user_id }}</dd><dt>生成类型</dt><dd>{{ selected.kind }}</dd><dt>尝试次数</dt><dd>{{ selected.provider_attempt_count }}</dd><dt>完成时间</dt><dd>{{ formatTime(selected.completed_at) }}</dd><dt>错误码</dt><dd :class="selected.error_code ? 'danger-text mono' : ''">{{ selected.error_code || '-' }}</dd><dt>错误摘要</dt><dd>{{ selected.error_message || '-' }}</dd><dt>渲染 Prompt</dt><dd class="prompt">{{ selected.rendered_prompt || '-' }}</dd></dl>
<h3>尝试记录</h3><el-timeline v-if="attempts.length"><el-timeline-item v-for="(attempt, index) in attempts" :key="index" :type="attempt.succeeded ? 'success' : 'danger'" :timestamp="attempt.finished_at || attempt.started_at"><strong>尝试 {{ index + 1 }} · 模型 #{{ attempt.provider_model_id || '-' }}</strong><p>{{ attempt.error_code || (attempt.succeeded ? '成功' : '失败') }}<span v-if="attempt.error_message"> · {{ attempt.error_message }}</span></p></el-timeline-item></el-timeline><p v-else class="muted">尚无尝试记录。</p>
<h3>尝试记录</h3><el-timeline v-if="attempts.length"><el-timeline-item v-for="(attempt, index) in attempts" :key="index" :type="attempt.presentation.type" :timestamp="attempt.presentation.timestamp"><strong>尝试 {{ index + 1 }} · {{ attempt.presentation.title }}</strong><p>{{ attempt.presentation.status }}<span v-if="attempt.error_message"> · {{ attempt.error_message }}</span></p></el-timeline-item></el-timeline><p v-else class="muted">尚无尝试记录。</p>
</template>
</section>
</div>
@@ -20,10 +20,11 @@
<script>
import { listGenerations } from '@/api/chorus'
import { contains, dataOf, formatTime } from '../shared'
import { attemptPresentation } from './helpers'
export default {
name: 'ChorusGenerations',
data() { return { loading: false, rows: [], selected: null, keyword: '', status: '', statuses: ['pending', 'running', 'succeeded', 'failed'] } },
computed: { filtered() { return this.rows.filter(row => (!this.keyword || [row.id, row.user_id, row.error_code].some(value => contains(value, this.keyword))) && (!this.status || row.status === this.status)) }, attempts() { return Array.isArray(this.selected && this.selected.attempts) ? this.selected.attempts : [] } },
computed: { filtered() { return this.rows.filter(row => (!this.keyword || [row.id, row.user_id, row.error_code].some(value => contains(value, this.keyword))) && (!this.status || row.status === this.status)) }, attempts() { return Array.isArray(this.selected && this.selected.attempts) ? this.selected.attempts.map(attempt => ({ ...attempt, presentation: attemptPresentation(attempt) })) : [] } },
created() { if (this.$route.query.user) this.keyword = String(this.$route.query.user); this.load() },
methods: { formatTime, async load() { this.loading = true; try { this.rows = dataOf(await listGenerations()); if (this.selected) this.selected = this.rows.find(row => row.id === this.selected.id) || null } finally { this.loading = false } }, reset() { this.keyword = ''; this.status = '' }, statusText(value) { return ({ pending: '等待中', running: '处理中', succeeded: '成功', failed: '失败' })[value] || value }, statusType(value) { return ({ pending: 'info', running: 'warning', succeeded: 'success', failed: 'danger' })[value] || 'info' } }
}
@@ -0,0 +1,43 @@
import fs from 'fs'
import path from 'path'
import { attemptPresentation } from '@/views/chorus/generations/helpers'
describe('Chorus generation attempt presentation', () => {
test('renders a lease as task acquisition without a fake model', () => {
expect(attemptPresentation({ type: 'lease', started_at: '2026-08-25T09:39:46Z' })).toEqual({
title: '任务认领',
status: '已认领',
type: 'primary',
timestamp: '2026-08-25T09:39:46Z'
})
})
test('renders an unfinished provider attempt as running', () => {
expect(attemptPresentation({ type: 'provider', provider_model_id: 4, started_at: '2026-08-25T09:39:47Z' })).toMatchObject({
title: '模型 #4', status: '进行中', type: 'warning'
})
})
test('renders a finished provider attempt without an error as succeeded', () => {
expect(attemptPresentation({ type: 'provider', provider_model_id: 4, started_at: 'start', finished_at: 'finish' })).toEqual({
title: '模型 #4', status: '成功', type: 'success', timestamp: 'finish'
})
})
test('renders an error as failed even when the event has no finished time', () => {
expect(attemptPresentation({ type: 'provider', provider_model_id: 4, error_code: 'route_unavailable', started_at: 'start' })).toEqual({
title: '模型 #4', status: 'route_unavailable', type: 'danger', timestamp: 'start'
})
})
test('uses a stable fallback for malformed or unknown events', () => {
expect(attemptPresentation(null)).toEqual({ title: '系统事件', status: '进行中', type: 'warning', timestamp: '' })
expect(attemptPresentation({ type: 'provider' }).title).toBe('模型未记录')
})
test('the page no longer depends on the nonexistent succeeded field', () => {
const source = fs.readFileSync(path.resolve(__dirname, '../../../src/views/chorus/generations/index.vue'), 'utf8')
expect(source).not.toContain('attempt.succeeded')
expect(source).not.toContain("模型 #{{ attempt.provider_model_id || '-' }}")
})
})