From 424a05265a153eb83e1f71723ad6c2907abf3ba5 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sun, 17 May 2026 12:36:47 +0800 Subject: [PATCH] auto-sync: 2026-05-17 12:36:47 --- src/frontend/src/components/EdictBoard.tsx | 202 ++++++++++++++------- 1 file changed, 135 insertions(+), 67 deletions(-) diff --git a/src/frontend/src/components/EdictBoard.tsx b/src/frontend/src/components/EdictBoard.tsx index ed68bf3..f7ccf51 100644 --- a/src/frontend/src/components/EdictBoard.tsx +++ b/src/frontend/src/components/EdictBoard.tsx @@ -2,31 +2,38 @@ * EdictBoard v2.0 — 任务看板 * 数据模型:v2.6 黑板(扁平任务,无 DAG) * Mock 数据用于 UI 预览 + * + * 司马懿评审修复: + * - P0: priority 改为 number(1-10),前端映射 label + * - P1: 状态管线补 review/blocked(6步),VALID_TRANSITIONS 对齐后端 8 状态 + * - P2: V2Task 补 depends_on, parent_task 字段 */ import { useState } from 'react'; import { useStore } from '../store'; -// ── v2.0 Task 类型 ── +// ── v2.0 Task 类型(对齐后端 tasks 表)── export interface V2Task { id: string; title: string; description: string; - status: 'pending' | 'claimed' | 'working' | 'done' | 'failed'; + status: 'pending' | 'claimed' | 'working' | 'review' | 'done' | 'failed' | 'blocked' | 'cancelled'; assignee: string | null; assigned_by: string | null; - priority: 'low' | 'medium' | 'high' | 'critical'; + depends_on: string | null; // P2: 依赖任务 + parent_task: string | null; // P2: 父任务 + priority: number; // P0: 1-10 整数 task_type: string; created_at: string; updated_at: string; claimed_at: string | null; completed_at: string | null; started_at: string | null; + deadline: string | null; retry_count: number; max_retries: number; risk_level: 'low' | 'standard' | 'high' | 'critical'; - escalated: boolean; - deadline: string | null; - // 关联数据(mock) + escalated: number; // 0/1 整数,与后端一致 + // 关联数据(API 聚合或 mock) comments_count: number; outputs_count: number; review_status: 'none' | 'pending' | 'approved' | 'rejected' | 'rebuttal'; @@ -34,27 +41,49 @@ export interface V2Task { project_id: string; } -// ── 状态管线 ── -const PIPELINE = [ +// ── 状态管线(6 步,对齐后端 VALID_STATUSES)── +// blocked 不在线性管道上,用独立标记 +const PIPELINE_STEPS = [ { key: 'pending', label: '待认领', icon: '📋' }, { key: 'claimed', label: '已认领', icon: '👤' }, { key: 'working', label: '执行中', icon: '⚔️' }, + { key: 'review', label: '审查中', icon: '🔍' }, { key: 'done', label: '已完成', icon: '✅' }, ] as const; -const STATUS_META: Record = { - pending: { color: '#7a9aff', bg: '#0a1028', label: '待认领' }, - claimed: { color: '#a07aff', bg: '#110a28', label: '已认领' }, - working: { color: '#6a9eff', bg: '#0a1530', label: '执行中' }, - done: { color: '#2ecc8a', bg: '#0a2018', label: '已完成' }, - failed: { color: '#ff5270', bg: '#200a10', label: '失败' }, +// 状态在线性管线中的位置索引(用于判断 done/active/pending) +const PIPELINE_ORDER: Record = { + pending: 0, claimed: 1, working: 2, review: 3, done: 4, + // 以下不在线性管道上 + failed: 2, // 标记在 working 位置 + blocked: 2, // 标记在 working 位置 + cancelled: -1, }; -const PRIORITY_META: Record = { - low: { color: '#6b7280', label: '低' }, - medium: { color: '#3b82f6', label: '中' }, - high: { color: '#f59e0b', label: '高' }, - critical: { color: '#ff5270', label: '紧急' }, +// ── 状态元信息(8 状态全覆盖)── +const STATUS_META: Record = { + pending: { color: '#7a9aff', bg: '#0a1028', label: '待认领' }, + claimed: { color: '#a07aff', bg: '#110a28', label: '已认领' }, + working: { color: '#6a9eff', bg: '#0a1530', label: '执行中' }, + review: { color: '#818cf8', bg: '#0f0a28', label: '审查中' }, + done: { color: '#2ecc8a', bg: '#0a2018', label: '已完成' }, + failed: { color: '#ff5270', bg: '#200a10', label: '失败' }, + blocked: { color: '#f59e0b', bg: '#1a1508', label: '阻塞' }, + cancelled: { color: '#6b7280', bg: '#141824', label: '已取消' }, +}; + +// P0: priority 整数映射 +const PRIORITY_META: Record = { + 1: { color: '#6b7280', label: '低' }, + 2: { color: '#6b7280', label: '低' }, + 3: { color: '#3b82f6', label: '中' }, + 4: { color: '#3b82f6', label: '中' }, + 5: { color: '#f59e0b', label: '高' }, + 6: { color: '#f59e0b', label: '高' }, + 7: { color: '#ff5270', label: '紧急' }, + 8: { color: '#ff5270', label: '紧急' }, + 9: { color: '#ff5270', label: '紧急' }, + 10: { color: '#ff5270', label: '紧急' }, }; const RISK_META: Record = { @@ -82,56 +111,72 @@ const MOCK_TASKS: V2Task[] = [ { id: 'task-001', title: '动量因子策略回测', description: '基于成交量的日线动量策略回测,持仓周期3-10天波段交易', status: 'working', assignee: 'zhangfei-dev', assigned_by: 'pangtong-fujunshi', - priority: 'high', task_type: 'backtest', created_at: '2026-05-17T08:30:00', updated_at: '2026-05-17T11:20:00', + depends_on: null, parent_task: null, + priority: 7, task_type: 'backtest', created_at: '2026-05-17T08:30:00', updated_at: '2026-05-17T11:20:00', claimed_at: '2026-05-17T08:35:00', completed_at: null, started_at: '2026-05-17T08:36:00', - retry_count: 0, max_retries: 3, risk_level: 'standard', escalated: false, - deadline: '2026-05-17T18:00:00', comments_count: 3, outputs_count: 1, + deadline: '2026-05-17T18:00:00', retry_count: 0, max_retries: 3, risk_level: 'standard', escalated: 0, + comments_count: 3, outputs_count: 1, review_status: 'pending', latest_event: '回测脚本运行中,已完成70%数据回测', project_id: 'demo', }, { id: 'task-002', title: '分钟线数据质量检查', description: '检查沪深300成分股1分钟线数据完整性(2024-01至今)', status: 'done', assignee: 'zhaoyun-data', assigned_by: 'pangtong-fujunshi', - priority: 'medium', task_type: 'data_verify', created_at: '2026-05-17T07:00:00', updated_at: '2026-05-17T09:15:00', + depends_on: null, parent_task: null, + priority: 3, task_type: 'data_verify', created_at: '2026-05-17T07:00:00', updated_at: '2026-05-17T09:15:00', claimed_at: '2026-05-17T07:05:00', completed_at: '2026-05-17T09:15:00', started_at: '2026-05-17T07:06:00', - retry_count: 0, max_retries: 2, risk_level: 'low', escalated: false, - deadline: null, comments_count: 5, outputs_count: 2, + deadline: null, retry_count: 0, max_retries: 2, risk_level: 'low', escalated: 0, + comments_count: 5, outputs_count: 2, review_status: 'approved', latest_event: '数据质量报告已通过审查', project_id: 'demo', }, { id: 'task-003', title: 'vnpy网关接口升级', description: '将CTP网关从6.3.19升级到6.7.0,适配新版API', status: 'claimed', assignee: 'jiangwei-infra', assigned_by: 'pangtong-fujunshi', - priority: 'critical', task_type: 'infra', created_at: '2026-05-17T06:00:00', updated_at: '2026-05-17T10:00:00', + depends_on: null, parent_task: null, + priority: 9, task_type: 'infra', created_at: '2026-05-17T06:00:00', updated_at: '2026-05-17T10:00:00', claimed_at: '2026-05-17T10:00:00', completed_at: null, started_at: null, - retry_count: 0, max_retries: 2, risk_level: 'critical', escalated: false, - deadline: '2026-05-18T18:00:00', comments_count: 2, outputs_count: 0, + deadline: '2026-05-18T18:00:00', retry_count: 0, max_retries: 2, risk_level: 'critical', escalated: 0, + comments_count: 2, outputs_count: 0, review_status: 'none', latest_event: '姜维已认领,准备开始', project_id: 'demo', }, { id: 'task-004', title: '风控规则代码审查', description: '审查止损/仓位/滑点检查模块代码质量', status: 'pending', assignee: null, assigned_by: 'pangtong-fujunshi', - priority: 'medium', task_type: 'code_review', created_at: '2026-05-17T09:00:00', updated_at: '2026-05-17T09:00:00', + depends_on: 'task-006', parent_task: null, + priority: 5, task_type: 'code_review', created_at: '2026-05-17T09:00:00', updated_at: '2026-05-17T09:00:00', claimed_at: null, completed_at: null, started_at: null, - retry_count: 0, max_retries: 2, risk_level: 'standard', escalated: false, - deadline: null, comments_count: 0, outputs_count: 0, + deadline: null, retry_count: 0, max_retries: 2, risk_level: 'standard', escalated: 0, + comments_count: 0, outputs_count: 0, review_status: 'none', latest_event: null, project_id: 'demo', }, { id: 'task-005', title: '策略参数优化实验', description: '使用网格搜索优化动量因子参数(lookback=5-30, threshold=0.01-0.05)', status: 'failed', assignee: 'zhangfei-dev', assigned_by: 'pangtong-fujunshi', - priority: 'high', task_type: 'research', created_at: '2026-05-16T14:00:00', updated_at: '2026-05-17T03:00:00', + depends_on: null, parent_task: 'task-001', + priority: 7, task_type: 'research', created_at: '2026-05-16T14:00:00', updated_at: '2026-05-17T03:00:00', claimed_at: '2026-05-16T14:10:00', completed_at: null, started_at: '2026-05-16T14:12:00', - retry_count: 2, max_retries: 3, risk_level: 'high', escalated: true, - deadline: null, comments_count: 8, outputs_count: 0, + deadline: null, retry_count: 2, max_retries: 3, risk_level: 'high', escalated: 1, + comments_count: 8, outputs_count: 0, review_status: 'rebuttal', latest_event: '网格搜索内存溢出,第2次重试失败', project_id: 'demo', }, { id: 'task-006', title: '滑点模型实现', description: '实现基于成交量加权的滑点模型,支持市价单和限价单', - status: 'working', assignee: 'guanyu-dev', assigned_by: 'pangtong-fujunshi', - priority: 'high', task_type: 'coding', created_at: '2026-05-17T07:30:00', updated_at: '2026-05-17T11:00:00', + status: 'review', assignee: 'guanyu-dev', assigned_by: 'pangtong-fujunshi', + depends_on: null, parent_task: null, + priority: 6, task_type: 'coding', created_at: '2026-05-17T07:30:00', updated_at: '2026-05-17T11:30:00', claimed_at: '2026-05-17T07:45:00', completed_at: null, started_at: '2026-05-17T07:46:00', - retry_count: 0, max_retries: 2, risk_level: 'standard', escalated: false, - deadline: '2026-05-17T20:00:00', comments_count: 1, outputs_count: 0, - review_status: 'none', latest_event: '滑点基类已完成,正在实现成交量加权逻辑', project_id: 'demo', + deadline: '2026-05-17T20:00:00', retry_count: 0, max_retries: 2, risk_level: 'standard', escalated: 0, + comments_count: 1, outputs_count: 1, + review_status: 'pending', latest_event: '产出已提交,等待司马懿审查', project_id: 'demo', + }, + { + id: 'task-007', title: '回测数据下载', description: '从 NAS 下载沪深300历史日线数据(2020-2025)', + status: 'blocked', assignee: 'zhaoyun-data', assigned_by: 'pangtong-fujunshi', + depends_on: 'task-003', parent_task: null, + priority: 4, task_type: 'data', created_at: '2026-05-17T06:30:00', updated_at: '2026-05-17T10:30:00', + claimed_at: '2026-05-17T06:35:00', completed_at: null, started_at: '2026-05-17T06:36:00', + deadline: null, retry_count: 0, max_retries: 2, risk_level: 'low', escalated: 0, + comments_count: 1, outputs_count: 0, + review_status: 'none', latest_event: '等待 vnpy 网关升级完成后才能下载', project_id: 'demo', }, ]; @@ -155,35 +200,51 @@ function formatDeadline(iso: string): string { return `${Math.floor(hrs / 24)}天后`; } +function getPriorityLabel(p: number): { color: string; label: string } { + return PRIORITY_META[p] || { color: '#6b7280', label: `P${p}` }; +} + // ── 状态管线组件 ── function StatusPipeline({ status }: { status: string }) { - const currentIdx = PIPELINE.findIndex(p => p.key === status); - const failed = status === 'failed'; + const isLinear = ['pending', 'claimed', 'working', 'review', 'done'].includes(status); + const isFailed = status === 'failed'; + const isBlocked = status === 'blocked'; + const isCancelled = status === 'cancelled'; + const curIdx = PIPELINE_ORDER[status] ?? -1; return (
- {PIPELINE.map((stage, i) => { - const isDone = !failed && i < currentIdx; - const isActive = !failed && i === currentIdx; - const isFailed = failed && i === currentIdx; + {PIPELINE_STEPS.map((stage, i) => { + const isDone = isLinear && i < curIdx; + const isActive = (isLinear && i === curIdx) || (isFailed && i === 2) || (isBlocked && i === 2); + const isFailedNode = isFailed && i === 2; + const isBlockedNode = isBlocked && i === 2; + + let bg = 'transparent'; + let borderColor = 'transparent'; + let textColor = '#6b7280'; + let icon = stage.icon; + + if (isDone) { bg = '#0a2018'; textColor = '#2ecc8a'; icon = '✓'; } + if (isActive && !isFailedNode && !isBlockedNode) { bg = '#0a1530'; borderColor = '#6a9eff'; textColor = '#6a9eff'; } + if (isFailedNode) { bg = '#200a10'; borderColor = '#ff5270'; textColor = '#ff5270'; icon = '✗'; } + if (isBlockedNode) { bg = '#1a1508'; borderColor = '#f59e0b'; textColor = '#f59e0b'; icon = '🚧'; } + return (
- {isDone ? '✓' : stage.icon} - {stage.label} + {icon} + + {isFailedNode ? '失败' : isBlockedNode ? '阻塞' : stage.label} +
- {i < PIPELINE.length - 1 && ( - + {i < PIPELINE_STEPS.length - 1 && ( + )}
); @@ -195,7 +256,7 @@ function StatusPipeline({ status }: { status: string }) { // ── 任务卡片 ── function TaskCard({ task, onOpen }: { task: V2Task; onOpen: () => void }) { const sm = STATUS_META[task.status]; - const pm = PRIORITY_META[task.priority]; + const pm = getPriorityLabel(task.priority); const rm = RISK_META[task.risk_level]; const rvm = REVIEW_META[task.review_status]; const agentEmoji = task.assignee ? (AGENT_EMOJI[task.assignee] || '🤖') : ''; @@ -228,10 +289,15 @@ function TaskCard({ task, onOpen }: { task: V2Task; onOpen: () => void }) { {pm.label}优先级 - {task.escalated && ( + {task.escalated ? 1 : 0 ? ( ⚠️ 已升级 + ) : null} + {task.depends_on && ( + + 🔗 依赖 {task.depends_on} + )}
@@ -240,7 +306,7 @@ function TaskCard({ task, onOpen }: { task: V2Task; onOpen: () => void }) { ⏱ {timeAgo(task.updated_at)} {task.outputs_count > 0 && 📦 {task.outputs_count}产出} {task.comments_count > 0 && 💬 {task.comments_count}评论} - {rvm.icon && {rvm.icon} {rvm.label}} + {rvm.icon && {rvm.icon}} {/* 最新事件 */} @@ -288,8 +354,10 @@ export default function EdictBoard() { { key: 'pending', label: '待认领', icon: '📋' }, { key: 'claimed', label: '已认领', icon: '👤' }, { key: 'working', label: '执行中', icon: '⚔️' }, + { key: 'review', label: '审查中', icon: '🔍' }, { key: 'done', label: '已完成', icon: '✅' }, { key: 'failed', label: '失败', icon: '❌' }, + { key: 'blocked', label: '阻塞', icon: '🚧' }, ]; let tasks = MOCK_TASKS; @@ -299,17 +367,17 @@ export default function EdictBoard() { tasks = tasks.filter(t => t.title.toLowerCase().includes(q) || t.id.toLowerCase().includes(q)); } - // 排序:working > claimed > pending > done > failed - const order: Record = { working: 0, claimed: 1, pending: 2, failed: 3, done: 4 }; - tasks.sort((a, b) => (order[a.status] ?? 5) - (order[b.status] ?? 5)); + // 排序:working > review > claimed > blocked > pending > failed > done + const order: Record = { working: 0, review: 1, claimed: 2, blocked: 3, pending: 4, failed: 5, done: 6, cancelled: 7 }; + tasks.sort((a, b) => (order[a.status] ?? 8) - (order[b.status] ?? 8)); const counts: Record = { all: MOCK_TASKS.length }; MOCK_TASKS.forEach(t => { counts[t.status] = (counts[t.status] || 0) + 1; }); // 统计 - const activeCount = MOCK_TASKS.filter(t => ['working', 'claimed'].includes(t.status)).length; + const activeCount = MOCK_TASKS.filter(t => ['working', 'claimed', 'review'].includes(t.status)).length; const doneCount = MOCK_TASKS.filter(t => t.status === 'done').length; - const failedCount = MOCK_TASKS.filter(t => t.status === 'failed').length; + const failedCount = MOCK_TASKS.filter(t => ['failed', 'blocked'].includes(t.status)).length; return (
@@ -324,12 +392,12 @@ export default function EdictBoard() {
{doneCount}
-
失败/升级
+
失败/阻塞
{failedCount}
审查中
-
{MOCK_TASKS.filter(t => t.review_status !== 'none').length}
+
{MOCK_TASKS.filter(t => t.status === 'review').length}