69 lines
2.5 KiB
TypeScript
69 lines
2.5 KiB
TypeScript
import { ANALYZE_COPY_MISSION_PIPELINE_STEPS } from '../lib/copyFlow'
|
||
import { jobStatusBadgeClass, jobStatusLabel } from '../lib/jobStatus'
|
||
import type { JobData } from '../types/api'
|
||
import { ProgressBar, StatusBadge } from './ui'
|
||
|
||
const STEP_STATUS_LABEL: Record<string, string> = {
|
||
pending: '等待',
|
||
running: '進行中',
|
||
succeeded: '完成',
|
||
failed: '失敗',
|
||
skipped: '略過',
|
||
cancelled: '取消',
|
||
}
|
||
|
||
function analyzeJobHint(job: JobData): string {
|
||
if (job.status === 'cancel_requested') {
|
||
return '研究地圖任務取消中,通常幾秒內完成。'
|
||
}
|
||
if (job.status === 'queued' || job.status === 'pending') {
|
||
return '任務已排隊,背景 worker 即將接手…'
|
||
}
|
||
return job.progress?.summary?.trim() || '研究地圖產生中,請稍候…'
|
||
}
|
||
|
||
export function CopyMissionAnalyzeJobPanel({ job }: { job: JobData }) {
|
||
const steps = job.progress?.steps ?? []
|
||
const stepMap = new Map(steps.map((s) => [s.id, s]))
|
||
const pct = job.progress?.percentage ?? 0
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<p className="text-sm leading-relaxed text-ink">{analyzeJobHint(job)}</p>
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<StatusBadge className={jobStatusBadgeClass(job.status)}>
|
||
{jobStatusLabel(job.status)}
|
||
</StatusBadge>
|
||
<span className="text-xs font-bold text-muted">{pct}%</span>
|
||
</div>
|
||
<ProgressBar value={pct} />
|
||
<ol className="flex flex-wrap gap-2 text-[11px]">
|
||
{ANALYZE_COPY_MISSION_PIPELINE_STEPS.map((step) => {
|
||
const live = stepMap.get(step.id)
|
||
const status = live?.status ?? 'pending'
|
||
const isDone = status === 'succeeded' || status === 'done'
|
||
const isRunning = status === 'running'
|
||
return (
|
||
<li
|
||
key={step.id}
|
||
className={`inline-flex items-center gap-1 rounded-[var(--radius-pill)] border-2 px-2.5 py-1 font-bold ${
|
||
isDone
|
||
? 'border-success/40 bg-success-soft text-success'
|
||
: isRunning
|
||
? 'border-brand/40 bg-brand-soft text-brand'
|
||
: 'border-line text-muted'
|
||
}`}
|
||
title={step.hint}
|
||
>
|
||
<span>{step.title}</span>
|
||
<span className="opacity-70">· {STEP_STATUS_LABEL[status] ?? status}</span>
|
||
</li>
|
||
)
|
||
})}
|
||
</ol>
|
||
{job.error ? (
|
||
<p className="text-xs font-semibold leading-relaxed text-danger">錯誤:{job.error}</p>
|
||
) : null}
|
||
</div>
|
||
)
|
||
} |