fix: 修正生成尝试状态展示 (#61)
This commit is contained in:
@@ -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 || '-' }}")
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user