Remove simulated dashboard implementation per user request
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# 多 Agent 任务看板(状态流转可视化)
|
||||
|
||||
## 快速使用
|
||||
|
||||
1. 打开 `index.html`(直接双击或本地静态服务)
|
||||
2. 默认加载 `tasks.sample.json`
|
||||
3. 也可点击右上角上传你自己的 JSON 数据
|
||||
|
||||
## 数据格式
|
||||
|
||||
```json
|
||||
{
|
||||
"boardName": "看板名称",
|
||||
"updatedAt": "2026-03-08T11:45:00+08:00",
|
||||
"columns": ["Intake", "Draft", "Audit", "Approve/Reject", "Dispatch", "Execute", "Verify", "Report", "Archive"],
|
||||
"tasks": [
|
||||
{
|
||||
"id": "T-001",
|
||||
"title": "任务标题",
|
||||
"owner": "工部",
|
||||
"priority": "high",
|
||||
"state": "Execute",
|
||||
"risk": "low",
|
||||
"tags": ["dashboard"],
|
||||
"summary": "一句话描述"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 说明
|
||||
|
||||
- 看板内置三省六部状态流转提示
|
||||
- 显示进行中、待审计、已完成、高风险四项统计
|
||||
- 支持自动刷新(5s/10s/30s)与本地 JSON 上传
|
||||
- 支持“门下+兵部”双签状态标记(双签✅/❌)
|
||||
- 失败任务自动流入刑部(展示为 Audit/刑部)
|
||||
- 适合作为 Val 进行任务总调度时的可视化面板
|
||||
@@ -0,0 +1,235 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Val 多 Agent 任务看板</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0b1020;
|
||||
--panel: #121933;
|
||||
--panel2: #171f3d;
|
||||
--text: #e8ecff;
|
||||
--muted: #9aa7d6;
|
||||
--ok: #22c55e;
|
||||
--warn: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--accent: #60a5fa;
|
||||
--border: #25315f;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: linear-gradient(160deg, #080c1c, #101735 35%, #0a1126);
|
||||
color: var(--text);
|
||||
}
|
||||
.wrap { max-width: 1320px; margin: 20px auto; padding: 0 16px 28px; }
|
||||
.header {
|
||||
display: flex; justify-content: space-between; gap: 16px; align-items: center;
|
||||
background: rgba(18,25,51,.75); border: 1px solid var(--border);
|
||||
border-radius: 14px; padding: 16px;
|
||||
}
|
||||
.title h1 { margin: 0; font-size: 20px; }
|
||||
.title p { margin: 6px 0 0; color: var(--muted); font-size: 13px; }
|
||||
.controls { display: flex; gap: 10px; align-items: center; }
|
||||
input[type=file] { color: var(--muted); font-size: 12px; }
|
||||
button {
|
||||
border: 1px solid #3b4b86; background: #1a2550; color: #dce6ff;
|
||||
border-radius: 10px; padding: 8px 12px; cursor: pointer;
|
||||
}
|
||||
.grid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 14px; margin-top: 14px; }
|
||||
.panel {
|
||||
background: rgba(18,25,51,.75); border: 1px solid var(--border);
|
||||
border-radius: 14px; padding: 14px;
|
||||
}
|
||||
.panel h2 { margin: 0 0 10px; font-size: 15px; color: #c8d7ff; }
|
||||
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
|
||||
.stat { background: var(--panel2); border: 1px solid var(--border); border-radius: 10px; padding: 10px; }
|
||||
.stat .k { color: var(--muted); font-size: 12px; }
|
||||
.stat .v { font-size: 20px; margin-top: 4px; }
|
||||
.flow {
|
||||
display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
|
||||
font-size: 12px; color: #b5c3ee;
|
||||
}
|
||||
.node { border: 1px solid #355; background: #18264d; padding: 5px 8px; border-radius: 999px; }
|
||||
.arrow { color: #6e83c8; }
|
||||
.kanban {
|
||||
display: grid; grid-template-columns: repeat(9, minmax(220px, 1fr));
|
||||
gap: 10px; overflow-x: auto; padding-bottom: 8px;
|
||||
}
|
||||
.col { background: #101735; border: 1px solid var(--border); border-radius: 10px; min-height: 140px; }
|
||||
.col-head {
|
||||
padding: 8px 10px; border-bottom: 1px solid var(--border);
|
||||
display: flex; justify-content: space-between; color: #c5d4ff; font-size: 13px;
|
||||
}
|
||||
.col-body { padding: 8px; display: grid; gap: 8px; }
|
||||
.card {
|
||||
background: #162043; border: 1px solid #2f3f7a; border-radius: 10px;
|
||||
padding: 8px;
|
||||
}
|
||||
.card h4 { margin: 0; font-size: 13px; line-height: 1.35; }
|
||||
.meta { margin-top: 6px; color: var(--muted); font-size: 11px; display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.pill { border: 1px solid #3c4f93; border-radius: 999px; padding: 2px 6px; }
|
||||
.risk-low { color: var(--ok); }
|
||||
.risk-medium { color: var(--warn); }
|
||||
.risk-high { color: var(--danger); }
|
||||
.hint { margin-top: 10px; color: var(--muted); font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<h1 id="boardName">Val 多 Agent 任务看板</h1>
|
||||
<p id="updatedAt">等待加载数据...</p>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label style="font-size:12px;color:var(--muted);display:flex;gap:6px;align-items:center;">
|
||||
<input id="autoRefresh" type="checkbox" checked /> 自动刷新
|
||||
</label>
|
||||
<select id="refreshMs" style="background:#1a2550;color:#dce6ff;border:1px solid #3b4b86;border-radius:8px;padding:6px;">
|
||||
<option value="5000">5s</option>
|
||||
<option value="10000" selected>10s</option>
|
||||
<option value="30000">30s</option>
|
||||
</select>
|
||||
<input id="fileInput" type="file" accept="application/json" />
|
||||
<button id="loadSample">加载样例数据</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<h2>状态总览</h2>
|
||||
<div class="stats" id="stats"></div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>三省六部流转图</h2>
|
||||
<div class="flow">
|
||||
<span class="node">Intake</span><span class="arrow">→</span>
|
||||
<span class="node">中书 Draft</span><span class="arrow">→</span>
|
||||
<span class="node">门下 Audit</span><span class="arrow">→</span>
|
||||
<span class="node">尚书 Dispatch</span><span class="arrow">→</span>
|
||||
<span class="node">六部 Execute</span><span class="arrow">→</span>
|
||||
<span class="node">Verify</span><span class="arrow">→</span>
|
||||
<span class="node">Report</span><span class="arrow">→</span>
|
||||
<span class="node">Archive</span>
|
||||
</div>
|
||||
<div class="hint">铁律:工部执行前需要门下+兵部双签;失败先进入刑部。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="margin-top: 14px;">
|
||||
<h2>任务看板</h2>
|
||||
<div class="kanban" id="kanban"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const state = { data: null, timer: null, lastSource: 'sample' };
|
||||
|
||||
function riskClass(risk) {
|
||||
return risk === 'high' ? 'risk-high' : risk === 'medium' ? 'risk-medium' : 'risk-low';
|
||||
}
|
||||
|
||||
function applyWorkflowRules(tasks) {
|
||||
// 规则1:失败任务自动流入刑部(Justice)
|
||||
for (const t of tasks) {
|
||||
if (t.failed === true) {
|
||||
t.state = 'Audit';
|
||||
t.owner = '刑部';
|
||||
t.summary = `【自动流转】失败转刑部:${t.failReason || '待定位根因'}`;
|
||||
}
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
function hasDualSignoff(task) {
|
||||
return !!(task.signoff?.auditor && task.signoff?.guardian);
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
state.data = data;
|
||||
const columns = data.columns || [];
|
||||
const tasks = applyWorkflowRules([...(data.tasks || [])]);
|
||||
|
||||
const unsignedExec = tasks.filter(t => t.state === 'Execute' && !hasDualSignoff(t)).length;
|
||||
document.getElementById('boardName').textContent = data.boardName || '多 Agent 任务看板';
|
||||
document.getElementById('updatedAt').textContent = `更新时间:${data.updatedAt || '未知'} · 任务数 ${tasks.length} · 双签缺失执行项 ${unsignedExec}`;
|
||||
|
||||
const statsWrap = document.getElementById('stats');
|
||||
const running = tasks.filter(t => ['Dispatch','Execute','Verify'].includes(t.state)).length;
|
||||
const blocked = tasks.filter(t => t.state === 'Audit').length;
|
||||
const done = tasks.filter(t => ['Report','Archive'].includes(t.state)).length;
|
||||
const highRisk = tasks.filter(t => t.risk === 'high').length;
|
||||
const statList = [
|
||||
['进行中', running],
|
||||
['待审计', blocked],
|
||||
['已完成', done],
|
||||
['高风险', highRisk]
|
||||
];
|
||||
statsWrap.innerHTML = statList.map(([k,v]) => `<div class="stat"><div class="k">${k}</div><div class="v">${v}</div></div>`).join('');
|
||||
|
||||
const kanban = document.getElementById('kanban');
|
||||
kanban.innerHTML = columns.map(col => {
|
||||
const list = tasks.filter(t => t.state === col);
|
||||
return `<div class="col">
|
||||
<div class="col-head"><span>${col}</span><span>${list.length}</span></div>
|
||||
<div class="col-body">
|
||||
${list.length ? list.map(t => {
|
||||
const dual = hasDualSignoff(t);
|
||||
const signText = dual ? '双签✅' : '双签❌';
|
||||
const signStyle = dual ? 'style="color:#22c55e"' : 'style="color:#ef4444"';
|
||||
return `
|
||||
<div class="card">
|
||||
<h4>${t.id} · ${t.title}</h4>
|
||||
<div class="meta">
|
||||
<span class="pill">${t.owner || '未分配'}</span>
|
||||
<span class="pill">P:${t.priority || 'n/a'}</span>
|
||||
<span class="pill ${riskClass(t.risk)}">Risk:${t.risk || 'low'}</span>
|
||||
<span class="pill" ${signStyle}>${signText}</span>
|
||||
</div>
|
||||
<div class="meta">${t.summary || ''}</div>
|
||||
</div>
|
||||
`}).join('') : `<div class="meta">暂无任务</div>`}
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function loadSample() {
|
||||
const res = await fetch('./tasks.sample.json', { cache: 'no-store' });
|
||||
const data = await res.json();
|
||||
render(data);
|
||||
}
|
||||
|
||||
function setupAutoRefresh() {
|
||||
if (state.timer) clearInterval(state.timer);
|
||||
const enabled = document.getElementById('autoRefresh').checked;
|
||||
const ms = Number(document.getElementById('refreshMs').value || 10000);
|
||||
if (!enabled || state.lastSource !== 'sample') return;
|
||||
state.timer = setInterval(loadSample, ms);
|
||||
}
|
||||
|
||||
document.getElementById('loadSample').addEventListener('click', async () => {
|
||||
state.lastSource = 'sample';
|
||||
await loadSample();
|
||||
setupAutoRefresh();
|
||||
});
|
||||
|
||||
document.getElementById('fileInput').addEventListener('change', async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
state.lastSource = 'upload';
|
||||
if (state.timer) clearInterval(state.timer);
|
||||
const text = await file.text();
|
||||
render(JSON.parse(text));
|
||||
});
|
||||
|
||||
document.getElementById('autoRefresh').addEventListener('change', setupAutoRefresh);
|
||||
document.getElementById('refreshMs').addEventListener('change', setupAutoRefresh);
|
||||
|
||||
loadSample().then(setupAutoRefresh);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"boardName": "Val 多 Agent 任务看板",
|
||||
"updatedAt": "2026-03-08T11:45:00+08:00",
|
||||
"columns": [
|
||||
"Intake",
|
||||
"Draft",
|
||||
"Audit",
|
||||
"Approve/Reject",
|
||||
"Dispatch",
|
||||
"Execute",
|
||||
"Verify",
|
||||
"Report",
|
||||
"Archive"
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"id": "T-001",
|
||||
"title": "实现多 Agent 任务看板(状态流转可视化)",
|
||||
"owner": "工部",
|
||||
"priority": "high",
|
||||
"state": "Execute",
|
||||
"risk": "low",
|
||||
"signoff": { "auditor": true, "guardian": true },
|
||||
"tags": ["dashboard", "workflow"],
|
||||
"summary": "前端页面 + 状态统计 + 流转图",
|
||||
"createdAt": "2026-03-08 11:42",
|
||||
"updatedAt": "2026-03-08 11:46"
|
||||
},
|
||||
{
|
||||
"id": "T-002",
|
||||
"title": "审查执行前安全红线",
|
||||
"owner": "兵部",
|
||||
"priority": "high",
|
||||
"state": "Audit",
|
||||
"risk": "medium",
|
||||
"signoff": { "auditor": true, "guardian": false },
|
||||
"tags": ["security"],
|
||||
"summary": "确认无外发、无不可逆破坏动作",
|
||||
"createdAt": "2026-03-08 11:43",
|
||||
"updatedAt": "2026-03-08 11:44"
|
||||
},
|
||||
{
|
||||
"id": "T-003",
|
||||
"title": "定义汇报模板并统一输出格式",
|
||||
"owner": "礼部",
|
||||
"priority": "medium",
|
||||
"state": "Verify",
|
||||
"risk": "low",
|
||||
"signoff": { "auditor": true, "guardian": true },
|
||||
"tags": ["style", "report"],
|
||||
"summary": "结论优先 + 风险提示 + 选项推荐",
|
||||
"createdAt": "2026-03-08 11:44",
|
||||
"updatedAt": "2026-03-08 11:46"
|
||||
},
|
||||
{
|
||||
"id": "T-004",
|
||||
"title": "批处理脚本执行异常",
|
||||
"owner": "工部",
|
||||
"priority": "high",
|
||||
"state": "Execute",
|
||||
"risk": "high",
|
||||
"failed": true,
|
||||
"failReason": "脚本在沙箱中权限不足",
|
||||
"signoff": { "auditor": true, "guardian": true },
|
||||
"tags": ["incident", "retry"],
|
||||
"summary": "按宪章应自动流入刑部处理",
|
||||
"createdAt": "2026-03-08 11:47",
|
||||
"updatedAt": "2026-03-08 11:49"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user