feat: add generation job cancel and retry queue

This commit is contained in:
2026-04-19 18:45:34 +08:00
parent 6fb128955f
commit b89ca96e4b
18 changed files with 756 additions and 51 deletions

View File

@@ -28,6 +28,7 @@ const jobHistory = ref<GenerationJobSummary[]>([])
const activeJob = ref<GenerationJobDetail | null>(null)
const providerStats = ref<GenerationProviderStats | null>(null)
const loading = ref(false)
const actionLoading = ref(false)
const error = ref('')
let refreshTimer: ReturnType<typeof setInterval> | null = null
@@ -79,6 +80,7 @@ const jobStatusClassMap: Record<string, string> = {
succeeded: 'border-emerald-200 bg-emerald-50 text-emerald-700',
completed: 'border-emerald-200 bg-emerald-50 text-emerald-700',
degraded_completed: 'border-orange-200 bg-orange-50 text-orange-700',
canceled: 'border-slate-200 bg-slate-100 text-slate-700',
failed: 'border-rose-200 bg-rose-50 text-rose-700',
}
@@ -93,6 +95,7 @@ function getJobStatusLabel(status?: string) {
succeeded: '成功',
completed: '已完成',
degraded_completed: '降级完成',
canceled: '已取消',
failed: '失败',
}
return labels[status ?? ''] ?? '未知'
@@ -102,6 +105,8 @@ function getEventLabel(eventType: string) {
const labels: Record<string, string> = {
request_accepted: '请求接收',
worker_started: '后台任务开始',
retry_queued: '重新排队',
cancel_requested: '已请求取消',
context_prepared: '上下文准备',
narrative_generated: '正文生成',
story_saved: '故事保存',
@@ -124,6 +129,7 @@ function getEventLabel(eventType: string) {
asset_retry_started: '资源重试开始',
asset_retry_completed: '资源重试完成',
asset_retry_failed: '资源重试失败',
generation_canceled: '任务已取消',
generation_completed: '生成完成',
generation_failed: '生成失败',
asset_generation_completed: '资源生成完成',
@@ -202,6 +208,36 @@ async function refresh() {
}
}
async function cancelActiveJob() {
if (!activeJob.value || actionLoading.value) return
actionLoading.value = true
error.value = ''
try {
await api.post(`/api/generations/jobs/${activeJob.value.id}/cancel`)
await refresh()
} catch (e) {
error.value = e instanceof Error ? e.message : '取消任务失败'
} finally {
actionLoading.value = false
}
}
async function retryActiveJob() {
if (!activeJob.value || actionLoading.value) return
actionLoading.value = true
error.value = ''
try {
await api.post(`/api/generations/jobs/${activeJob.value.id}/retry`)
await refresh()
} catch (e) {
error.value = e instanceof Error ? e.message : '重新排队失败'
} finally {
actionLoading.value = false
}
}
function stopAutoRefresh() {
if (refreshTimer) {
clearInterval(refreshTimer)
@@ -323,9 +359,29 @@ defineExpose({ refresh })
当前步骤{{ getEventLabel(activeJob.current_step) }}
</div>
</div>
<span class="rounded-full border px-3 py-1 text-xs font-medium" :class="getJobStatusClass(activeJob.status)">
{{ getJobStatusLabel(activeJob.status) }}
</span>
<div class="flex flex-wrap items-center justify-end gap-2">
<button
v-if="activeJob.can_cancel"
type="button"
class="rounded-full border border-amber-200 bg-amber-50 px-3 py-1 text-xs font-medium text-amber-700 transition hover:bg-amber-100 disabled:cursor-not-allowed disabled:opacity-60"
:disabled="actionLoading"
@click="cancelActiveJob"
>
{{ actionLoading ? '处理中...' : '取消任务' }}
</button>
<button
v-if="activeJob.can_retry"
type="button"
class="rounded-full border border-sky-200 bg-sky-50 px-3 py-1 text-xs font-medium text-sky-700 transition hover:bg-sky-100 disabled:cursor-not-allowed disabled:opacity-60"
:disabled="actionLoading"
@click="retryActiveJob"
>
{{ actionLoading ? '处理中...' : '重新排队' }}
</button>
<span class="rounded-full border px-3 py-1 text-xs font-medium" :class="getJobStatusClass(activeJob.status)">
{{ getJobStatusLabel(activeJob.status) }}
</span>
</div>
</div>
<div>