assistant/renderer/components/TaskCard.tsx

73 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { ChevronDown, ChevronRight, FileText } from './Icons';
import { CATEGORY_LABEL, STATUS_LABEL, type LearnedEntry, type Task } from '../types';
function screenshotUrl(path: string): string {
return `file:///${path.replace(/\\/g, '/')}?t=${Date.now()}`;
}
export function TaskCard({ task, timeline, learned, operatorMode, embedded, onOpenDoc, onLearned }: {
task: Task;
timeline: string[];
learned: LearnedEntry[];
operatorMode: boolean;
embedded: boolean;
onOpenDoc: () => void;
onLearned: (id: number, action: 'verify' | 'reject') => void;
}) {
const [showTimeline, setShowTimeline] = useState(false);
const tracking = task.artifacts.tracking;
const last = tracking?.history?.[tracking.history.length - 1];
const reasonLabel = task.status === 'failed' ? '原因' : task.status === 'blocked' ? '阻断' : task.status === 'rejected' ? '驳回' : null;
return (
<article className="task-card" data-status={task.status} data-testid="task-card">
<header className="task-card-head">
<strong>{task.business_name ?? '任务'}</strong>
<span className={`status-chip ${task.status}`}>{STATUS_LABEL[task.status] ?? task.status}</span>
</header>
<dl className="task-facts">
<div><dt>步骤</dt><dd>{task.current_step ?? '-'}</dd></div>
<div><dt>创建</dt><dd>{task.created_at}</dd></div>
{task.status === 'submitted' && (
<div><dt>审批</dt><dd>{last ? `${last.status}(${last.at})` : '待回查'}{task.next_check_at ? `,下次回查 ${task.next_check_at}` : ''}</dd></div>
)}
{reasonLabel && task.pause_reason && <div><dt>{reasonLabel}</dt><dd>{task.pause_reason.slice(0, 240)}</dd></div>}
</dl>
{!embedded && task.latest_screenshot && (
<img className="task-shot" src={screenshotUrl(task.latest_screenshot)} alt="Agent 最近一步的页面截图" />
)}
<div className="task-card-tools">
<button type="button" className="text-action" onClick={() => setShowTimeline(v => !v)} aria-expanded={showTimeline}>
{showTimeline ? <ChevronDown size={14} /> : <ChevronRight size={14} />}办理记录 · {timeline.length}
</button>
<button type="button" className="text-action" onClick={onOpenDoc} disabled={!task.progress_doc_path}>
<FileText size={14} />打开 progress.md
</button>
</div>
{showTimeline && (
<ol className="task-timeline">
{timeline.slice(-12).map((line, index) => <li key={`${index}-${line.slice(0, 24)}`}>{line}</li>)}
</ol>
)}
{operatorMode && learned.length > 0 && (
<section className="learned-block" aria-label="沉淀草稿">
<div className="section-label">沉淀草稿 · 运维模式</div>
<ul className="learned-list">
{learned.map(entry => (
<li key={entry.id} className="learned-item">
<div className="learned-key">{entry.key} <span className="muted">{CATEGORY_LABEL[entry.category] ?? entry.category} · 观察 {entry.observations} 次</span></div>
<div className="learned-value">{entry.value}</div>
<div className="learned-actions">
<button type="button" className="primary-button small" onClick={() => onLearned(entry.id, 'verify')}>采纳</button>
<button type="button" className="text-action" onClick={() => onLearned(entry.id, 'reject')}>忽略</button>
</div>
</li>
))}
</ul>
</section>
)}
</article>
);
}