v_graph_visualizer.html
9.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<!DOCTYPE html>
<!--
v_graph_visualizer — 静态原型(mock 数据,无构建步骤)
用途:给 Claude Code 在实现 PH3 U-026 时用作视觉+结构参照,不是最终代码,
但 DOM 结构 / class 命名 / 数据字段名刻意和真实实现保持一致,可以直接在这个
基础上接入真实 c_ws_subscribe 推送数据,而不必重新设计布局。
对应 SDD 条目:
- 02-V-界面与交互.md §2.7(v_graph_visualizer)+ §2.7.1(v_log_viewer,本原型未展开,
真实页面里通过 click_view_raw_log 从 Work Node 卡片展开)
- 03-C-控制与接口.md 3.6 节:work_node_execution.status 六态着色语义
done=绿 / running=蓝 / reflecting|retrying=黄 / waiting_for_quota=浅蓝+时钟图标(信息态,非告警)
/ blocked|failed=红 / pending=灰
- sdd-mvcp-contract.json view_contract.v_graph_visualizer:
fields=[{name:"run_id", visible_when:"task_has_active_run"}]
events=[{event:"hover_node", target_controller_id:"c_work_node_get", payload:{node_exec_id}}]
DOM 约定(供 CC 直接复用):
- 每个 Work Node 卡片:class="work-node status-<work_node_execution.status 枚举值>"
- data-node-exec-id 属性对应真实的 node_exec_id,真实实现里 hover 事件从这里读取
- waiting_for_quota 状态额外带 data-quota-limit-type / data-quota-wait-since 两个属性,
对应 03-C 3.6 节退避重试语义里要展示的 quota_limit_type + 已等待时长
-->
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>v_graph_visualizer 原型(mock 数据)</title>
<style>
:root {
--status-pending: #9ca3af;
--status-running: #3b82f6;
--status-reflecting: #eab308;
--status-waiting-quota: #7dd3fc;
--status-blocked: #ef4444;
--status-done: #22c55e;
--border: #d1d5db;
--text: #111827;
--text-muted: #6b7280;
--bg-panel: #f9fafb;
}
body { font-family: -apple-system, sans-serif; margin: 0; padding: 24px; background: #fff; color: var(--text); }
.proto-banner { background: #fef3c7; border: 1px solid #f59e0b; border-radius: 6px; padding: 8px 12px; font-size: 13px; margin-bottom: 20px; }
h2 { font-size: 15px; margin: 0 0 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; }
section { margin-bottom: 32px; }
/* ---- Org Graph:固定三节点 ---- */
.org-graph { display: flex; align-items: center; gap: 0; }
.org-node { border: 2px solid var(--border); border-radius: 10px; padding: 14px 24px; text-align: center; background: var(--bg-panel); min-width: 120px; }
.org-node.active { border-color: var(--status-running); background: #eff6ff; box-shadow: 0 0 0 3px rgba(59,130,246,.15); }
.org-node .role { font-weight: 600; font-size: 14px; }
.org-node .hint { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
.org-arrow { flex: 0 0 60px; text-align: center; color: var(--text-muted); font-size: 20px; }
/* ---- Work Graph:动态节点/边 ---- */
.work-graph { position: relative; border: 1px dashed var(--border); border-radius: 8px; padding: 32px; background: var(--bg-panel); }
.work-row { display: flex; gap: 24px; margin-bottom: 40px; justify-content: center; }
.work-node {
border: 2px solid var(--border);
border-radius: 8px;
padding: 10px 14px;
min-width: 140px;
background: #fff;
cursor: pointer;
position: relative;
font-size: 13px;
}
.work-node .node-id { font-weight: 600; }
.work-node .node-skill { color: var(--text-muted); font-size: 11px; margin-top: 2px; }
.work-node.status-pending { border-color: var(--status-pending); }
.work-node.status-running { border-color: var(--status-running); background: #eff6ff; }
.work-node.status-reflecting, .work-node.status-retrying { border-color: var(--status-reflecting); background: #fefce8; }
.work-node.status-waiting_for_quota { border-color: var(--status-waiting-quota); background: #f0f9ff; }
.work-node.status-waiting_for_quota::after { content: "🕐"; position: absolute; top: -8px; right: -8px; font-size: 14px; }
.work-node.status-blocked, .work-node.status-failed { border-color: var(--status-blocked); background: #fef2f2; }
.work-node.status-done { border-color: var(--status-done); background: #f0fdf4; }
/* ---- 图例 ---- */
.legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--text-muted); }
.legend-item { display: flex; align-items: center; gap: 6px; }
.legend-swatch { width: 14px; height: 14px; border-radius: 4px; border: 2px solid; }
/* ---- hover tooltip(示意,真实实现走 c_work_node_get) ---- */
.tooltip {
position: absolute; z-index: 10; background: #111827; color: #fff; font-size: 12px;
padding: 8px 10px; border-radius: 6px; max-width: 260px; display: none; pointer-events: none;
line-height: 1.5;
}
.tooltip.show { display: block; }
</style>
</head>
<body>
<div class="proto-banner">
这是 v_graph_visualizer 的静态原型(mock 数据,不连真实后端)。用于 PH3 U-026 开发前的视觉核对——
DOM 结构 / class 命名 / data-* 属性均按 02-V §2.7、03-C 3.6 节约定设计,实现时可以直接复用这套结构接入
c_ws_subscribe 推送的真实数据,不需要重新排版。
</div>
<section>
<h2>Org Graph(固定三节点,02-V §2.7)</h2>
<div class="org-graph">
<div class="org-node">
<div class="role">Planner</div>
<div class="hint">已完成</div>
</div>
<div class="org-arrow">→</div>
<div class="org-node active">
<div class="role">Coder</div>
<div class="hint">当前节点(task.assigned_org_node)</div>
</div>
<div class="org-arrow">→</div>
<div class="org-node">
<div class="role">Validator</div>
<div class="hint">未开始</div>
</div>
</div>
</section>
<section>
<h2>Work Graph(当前 work_graph_run.graph_manifest 渲染,节点按 status 着色)</h2>
<div class="work-graph">
<div class="work-row">
<div class="work-node status-done" data-node-exec-id="node-001"
onmouseover="showTip(event, 'node-001', 'skill: code-task-development', 'output_schema_ref: code_diff.schema.json', 'retry_count: 0')"
onmouseout="hideTip()">
<div class="node-id">worker:module-a</div>
<div class="node-skill">code-task-development</div>
</div>
<div class="work-node status-done" data-node-exec-id="node-002"
onmouseover="showTip(event, 'node-002', 'skill: code-task-development', 'output_schema_ref: code_diff.schema.json', 'retry_count: 1')"
onmouseout="hideTip()">
<div class="node-id">worker:module-b</div>
<div class="node-skill">code-task-development</div>
</div>
</div>
<div class="work-row">
<div class="work-node status-waiting_for_quota" data-node-exec-id="node-003"
data-quota-limit-type="session" data-quota-wait-since="12 分钟前"
onmouseover="showTip(event, 'node-003', 'skill: code-agent-engineering', 'quota_limit_type: session', '已等待 12 分钟,自愈型阻塞')"
onmouseout="hideTip()">
<div class="node-id">worker:module-c</div>
<div class="node-skill">code-agent-engineering</div>
</div>
<div class="work-node status-reflecting" data-node-exec-id="node-004"
onmouseover="showTip(event, 'node-004', 'skill: debugging-toolkit', 'retry_count: 2', 'reflection_notes: unit test 断言不通过')"
onmouseout="hideTip()">
<div class="node-id">worker:module-d</div>
<div class="node-skill">debugging-toolkit</div>
</div>
</div>
<div class="work-row">
<div class="work-node status-pending" data-node-exec-id="node-005"
onmouseover="showTip(event, 'node-005', 'skill: software-planning-execution', 'output_schema_ref: test_report.schema.json', 'retry_count: 0')"
onmouseout="hideTip()">
<div class="node-id">fan-in:merge</div>
<div class="node-skill">等待上游节点全部完成</div>
</div>
</div>
</div>
<div class="tooltip" id="tooltip"></div>
<div class="legend" style="margin-top:16px;">
<div class="legend-item"><span class="legend-swatch" style="border-color:var(--status-pending)"></span>pending</div>
<div class="legend-item"><span class="legend-swatch" style="border-color:var(--status-running)"></span>running</div>
<div class="legend-item"><span class="legend-swatch" style="border-color:var(--status-reflecting)"></span>reflecting / retrying</div>
<div class="legend-item"><span class="legend-swatch" style="border-color:var(--status-waiting-quota)"></span>waiting_for_quota(信息态,非告警,见 03-C 3.6 节)</div>
<div class="legend-item"><span class="legend-swatch" style="border-color:var(--status-blocked)"></span>blocked / failed</div>
<div class="legend-item"><span class="legend-swatch" style="border-color:var(--status-done)"></span>done</div>
</div>
</section>
<script>
// 真实实现里这段逻辑对应 hover_node 事件 -> c_work_node_get(node_exec_id)
// 这里用 data-* 属性里预置的 mock 文案代替真实接口返回
function showTip(evt, nodeId, ...lines) {
var tip = document.getElementById('tooltip');
tip.innerHTML = '<b>' + nodeId + '</b><br>' + lines.join('<br>');
tip.style.left = (evt.pageX + 12) + 'px';
tip.style.top = (evt.pageY + 12) + 'px';
tip.classList.add('show');
}
function hideTip() {
document.getElementById('tooltip').classList.remove('show');
}
</script>
</body>
</html>