diff --git a/src/frontend/src/components/TaskModal.tsx b/src/frontend/src/components/TaskModal.tsx index 1b6f990..2bd06a9 100644 --- a/src/frontend/src/components/TaskModal.tsx +++ b/src/frontend/src/components/TaskModal.tsx @@ -1,9 +1,9 @@ /** - * TaskModal v2.0 — 任务详情面板 - * 从 store 读取 v2taskDetail(expand=all API 返回) + * TaskModal v2.0 — 任务详情面板(真实 API 对接版) + * 数据来源:store.v2taskDetail(通过 loadV2TaskDetail 从后端 expand=all API 加载) */ -import { useState, useEffect } from 'react'; -import { useStore, type V2Task } from '../store'; +import { useEffect } from 'react'; +import { useStore } from '../store'; // ── 常量 ── const STATUS_META: Record = { @@ -17,6 +17,7 @@ const STATUS_META: Record = { cancelled: { color: '#6b7280', label: '已取消' }, }; +// 对齐后端 VALID_TRANSITIONS const VALID_TRANSITIONS: Record = { pending: ['claimed', 'cancelled'], claimed: ['working', 'pending', 'cancelled'], @@ -52,30 +53,27 @@ const COMMENT_TYPE_LABEL: Record = { - task_created: '📋', task_claimed: '👤', task_working: '⚔️', - task_review: '🔍', task_done: '✅', task_failed: '❌', task_blocked: '🚧', - comment_added: '💬', output_written: '📦', task_reviewed: '🔍', - decision_recorded: '🧭', observation_added: '👁️', daemon_tick: '⏱️', + task_created: '📋', task_claimed: '👤', task_started: '⚔️', task_completed: '✅', + task_failed: '❌', comment_added: '💬', output_written: '📦', review_submitted: '🔍', + review_approved: '✅', review_rejected: '❌', observation_added: '👁️', decision_made: '🧭', + status_changed: '🔄', task_blocked: '🚧', }; function fmtTime(iso: string): string { - if (!iso) return '—'; - const d = new Date(iso); - return `${d.getMonth()+1}/${d.getDate()} ${d.getHours()}:${String(d.getMinutes()).padStart(2,'0')}`; + try { + const d = new Date(iso.includes('T') ? iso : iso.replace(' ', 'T') + 'Z'); + return `${d.getMonth()+1}/${d.getDate()} ${d.getHours()}:${String(d.getMinutes()).padStart(2,'0')}`; + } catch { return iso; } } function fmtTimeFull(iso: string): string { - if (!iso) return '—'; - const d = new Date(iso); - return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${d.getHours()}:${String(d.getMinutes()).padStart(2,'0')}`; -} - -function parseDetail(raw: string): any { - try { return JSON.parse(raw); } catch { return {}; } + try { + const d = new Date(iso.includes('T') ? iso : iso.replace(' ', 'T') + 'Z'); + return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${d.getHours()}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`; + } catch { return iso; } } // ── 子组件 ── - function SectionLabel({ icon, title, count }: { icon: string; title: string; count?: number }) { return (
@@ -85,12 +83,8 @@ function SectionLabel({ icon, title, count }: { icon: string; title: string; cou ); } -function StatusButtons({ task }: { task: V2Task }) { - const selectedProjectId = useStore(s => s.selectedProjectId); - const loadV2Tasks = useStore(s => s.loadV2Tasks); - const toast = useStore(s => s.toast); - const transitions = VALID_TRANSITIONS[task.status] || []; - +function StatusButtons({ status }: { status: string }) { + const transitions = VALID_TRANSITIONS[status] || []; const btnMap: Record = { claimed: { label: '认领任务', icon: '👤', bg: '#a07aff22', color: '#a07aff', border: '#a07aff44' }, working: { label: '开始执行', icon: '⚔️', bg: '#2ecc8a22', color: '#2ecc8a', border: '#2ecc8a44' }, @@ -104,30 +98,13 @@ function StatusButtons({ task }: { task: V2Task }) { if (transitions.length === 0) return null; - const handleClick = async (newStatus: string) => { - if (!selectedProjectId) return; - try { - const res = await fetch(`/api/projects/${selectedProjectId}/tasks/${task.id}/status`, { - method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ status: newStatus }), - }); - const data = await res.json(); - if (data.ok) { - toast(`✅ 状态已更新为 ${STATUS_META[newStatus]?.label || newStatus}`, 'ok'); - loadV2Tasks(); - } else { - toast(`❌ 状态更新失败`, 'err'); - } - } catch { toast('❌ 网络错误', 'err'); } - }; - return (
{transitions.map(t => { const b = btnMap[t]; if (!b) return null; return ( - @@ -138,30 +115,25 @@ function StatusButtons({ task }: { task: V2Task }) { } function EventTimeline({ events }: { events: any[] }) { - if (!events || events.length === 0) return
暂无事件
; - const sorted = [...events].sort((a, b) => (b.created_at || '').localeCompare(a.created_at || '')); + if (!events || events.length === 0) return
暂无事件记录
; + const sorted = [...events].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); return (
- {sorted.map((ev, i) => { - const detail = parseDetail(ev.detail || '{}'); - return ( -
-
- {EVENT_TYPE_ICON[ev.event_type] || '📌'} - {i < sorted.length - 1 &&
} -
-
-
- - {detail.from && detail.to ? `${STATUS_META[detail.from]?.label || detail.from} → ${STATUS_META[detail.to]?.label || detail.to}` : ev.event_type} - - {fmtTime(ev.created_at)} -
- {ev.agent && {AGENT_EMOJI[ev.agent] || ''} {ev.agent}} -
+ {sorted.map((ev, i) => ( +
+
+ {EVENT_TYPE_ICON[ev.event_type] || '📌'} + {i < sorted.length - 1 &&
}
- ); - })} +
+
+ {ev.detail || ev.event_type} + {ev.created_at && {fmtTime(ev.created_at)}} +
+ {ev.agent && {AGENT_EMOJI[ev.agent] || ''} {ev.agent}} +
+
+ ))}
); } @@ -170,11 +142,11 @@ function EventTimeline({ events }: { events: any[] }) { export default function TaskModal() { const modalTaskId = useStore(s => s.modalTaskId); const setModalTaskId = useStore(s => s.setModalTaskId); - const v2taskDetail = useStore(s => s.v2taskDetail); const loadV2TaskDetail = useStore(s => s.loadV2TaskDetail); + const taskDetail = useStore(s => s.v2taskDetail); const [activeTab, setActiveTab] = useState<'overview' | 'outputs' | 'reviews' | 'experience'>('overview'); - // 加载详情 + // 打开时加载详情 useEffect(() => { if (modalTaskId) { setActiveTab('overview'); @@ -184,34 +156,47 @@ export default function TaskModal() { if (!modalTaskId) return null; - const task = v2taskDetail; - if (!task) return ( -
setModalTaskId(null)}> -
e.stopPropagation()}>加载中...
-
- ); + const task = taskDetail; + if (!task) { + return ( +
setModalTaskId(null)}> +
+
+
加载中...
+
+
+ ); + } - const close = () => { setModalTaskId(null); }; const sm = STATUS_META[task.status] || STATUS_META.pending; const pm = PRIORITY_META[task.priority] || { color: '#6b7280', label: `P${task.priority}` }; + const close = () => setModalTaskId(null); + + const comments = (task as any).comments || []; + const outputs = (task as any).outputs || []; + const reviews = (task as any).reviews || []; + const decisions = (task as any).decisions || []; + const events = (task as any).events || []; + const experiences = (task as any).experiences || []; const tabs = [ { key: 'overview' as const, label: '📋 总览' }, - { key: 'outputs' as const, label: '📦 产出', badge: (task.outputs || []).length }, - { key: 'reviews' as const, label: '🔍 审查', badge: (task.reviews || []).length }, - { key: 'experience' as const, label: '🧠 经验', badge: (task.experiences || []).length }, + { key: 'outputs' as const, label: '📦 产出', badge: outputs.length }, + { key: 'reviews' as const, label: '🔍 审查', badge: reviews.length }, + { key: 'experience' as const, label: '🧠 经验', badge: experiences.length }, ]; - const comments = task.comments || []; - const outputs = task.outputs || []; - const reviews = task.reviews || []; - const events = task.events || []; - const decisions = task.decisions || []; - const experiences = task.experiences || []; - return ( -
-
e.stopPropagation()}> +
+
e.stopPropagation()}> {/* Header */}
@@ -239,20 +224,26 @@ export default function TaskModal() {
{tabs.map(tab => ( ))}
- {/* Content */} + {/* Tab Content */}
+ {/* ── 总览 ── */} {activeTab === 'overview' && (<> {/* 描述 */} {task.description && ( @@ -262,21 +253,21 @@ export default function TaskModal() {
)} - {/* 状态按钮 */} + {/* 状态操作 */}
- +
- {/* 信息网格 */} + {/* 任务信息 */}
{[ - ['类型', task.task_type || '—'], + ['任务类型', task.task_type || '—'], ['优先级', `${task.priority} (${pm.label})`], - ['风险', task.risk_level || '—'], - ['重试', `${task.retry_count}/${task.max_retries}`], + ['风险等级', task.risk_level || '—'], + ['重试次数', `${task.retry_count}/${task.max_retries}`], ['认领时间', task.claimed_at ? fmtTime(task.claimed_at) : '—'], ['开始时间', task.started_at ? fmtTime(task.started_at) : '—'], ['截止时间', task.deadline ? fmtTime(task.deadline) : '无'], @@ -299,40 +290,41 @@ export default function TaskModal() {
{/* 评论 */} -
- -
- {comments.map((c: any) => { - const ct = COMMENT_TYPE_LABEL[c.comment_type] || COMMENT_TYPE_LABEL.general; - return ( -
-
-
- {AGENT_EMOJI[c.author] || '🤖'} - {c.author} - {ct.icon} {ct.label} + {comments.length > 0 && ( +
+ +
+ {comments.map((c: any, i: number) => { + const ct = COMMENT_TYPE_LABEL[c.comment_type] || COMMENT_TYPE_LABEL.general; + return ( +
+
+
+ {AGENT_EMOJI[c.author] || '🤖'} + {c.author} + {ct.icon} {ct.label} +
+ {c.created_at ? fmtTime(c.created_at) : ''}
- {fmtTime(c.created_at)} +
{c.body || c.content}
-
{c.body}
-
- ); - })} - {comments.length === 0 &&
暂无评论
} + ); + })} +
-
+ )} - {/* 决策 */} + {/* 决策记录 */} {decisions.length > 0 && (
- {decisions.map((d: any) => ( -
+ {decisions.map((d: any, i: number) => ( +
- {AGENT_EMOJI[d.decider] || ''} {d.decider} - {fmtTime(d.created_at)} + {AGENT_EMOJI[d.decider] || '🤖'} {d.decider} + {d.created_at ? fmtTime(d.created_at) : ''}
-
{d.rationale}
+
{d.rationale || d.decision}
))}
@@ -344,66 +336,78 @@ export default function TaskModal() {
🛐
Checkpoint 功能开发中
-
v2.7 提供
+
验证/决策/执行三种检查点将在 v2.7 版本提供
)} + {/* ── 产出 ── */} {activeTab === 'outputs' && (<> {outputs.length === 0 ? ( -
暂无产出物
+
暂无产出物。Agent 完成后将自动提交。
) : (
- {outputs.map((o: any) => ( -
+ {outputs.map((o: any, i: number) => ( +
{AGENT_EMOJI[o.agent] || '🤖'} {o.agent} - {fmtTime(o.created_at)} -
-
{o.summary || o.title}
-
- {o.output_type} + {o.created_at ? fmtTime(o.created_at) : ''}
+
{o.summary || o.title || o.content_path || '(无摘要)'}
+ {o.output_type &&
{o.output_type}
}
))}
)} )} + {/* ── 审查 ── */} {activeTab === 'reviews' && (<> {reviews.length === 0 ? ( -
暂无审查记录
+
暂无审查记录。
) : ( - reviews.map((r: any) => ( -
+ reviews.map((r: any, i: number) => ( +
{AGENT_EMOJI[r.reviewer] || '🤖'} {r.reviewer} - - {r.verdict === 'approve' ? '✅ 通过' : '❌ 驳回'} - + {r.verdict === 'APPROVE' ? '✅ 通过' : '❌ 驳回'}
- {fmtTime(r.created_at)} + {r.created_at ? fmtTime(r.created_at) : ''}
- {r.confidence != null &&
置信度: {(r.confidence * 100).toFixed(0)}%
} -
{r.summary}
+
+ {r.confidence != null && 置信度: {(r.confidence * 100).toFixed(0)}%} + {r.round != null && 轮次: {r.round}} +
+
{r.summary || '(无摘要)'}
)) )} )} + {/* ── 经验 ── */} {activeTab === 'experience' && (<> {experiences.length === 0 ? ( -
暂无经验沉淀
+
暂无经验沉淀。任务完成后将自动触发蒸馏。
) : ( - experiences.map((exp: any) => ( -
+ experiences.map((exp: any, i: number) => ( +
{exp.title} - {fmtTime(exp.created_at)} + {exp.created_at ? fmtTime(exp.created_at) : ''}
{exp.summary}
{exp.tags && (