@tea-agent/loop-agent 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +3 -2
- package/dist/application/dag/generate-task-dag.js +15 -0
- package/dist/application/dag/run-dag.js +10 -0
- package/dist/application/dag/validate-dag.js +11 -0
- package/dist/commands/init.js +74 -7
- package/dist/shared/package-metadata.js +135 -0
- package/dist/task/config-types.js +1 -0
- package/dist/worker/cli.js +3 -1
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/read-model.js +312 -83
- package/dist/worker/observe/paths.js +17 -0
- package/dist/worker/observe/routes.js +165 -21
- package/dist/worker/observe/server.js +59 -1
- package/dist/worker/observe/static/api.js +27 -0
- package/dist/worker/observe/static/app.js +120 -2598
- package/dist/worker/observe/static/constants.js +148 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/dag-helpers.js +172 -0
- package/dist/worker/observe/static/dag-model.js +72 -0
- package/dist/worker/observe/static/dom.js +61 -0
- package/dist/worker/observe/static/format-pool.js +67 -0
- package/dist/worker/observe/static/format.js +292 -0
- package/dist/worker/observe/static/index.html +300 -82
- package/dist/worker/observe/static/kpi.js +94 -0
- package/dist/worker/observe/static/relations.js +128 -0
- package/dist/worker/observe/static/router.js +85 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +68 -0
- package/dist/worker/observe/static/state.js +253 -0
- package/dist/worker/observe/static/styles.css +1719 -495
- package/dist/worker/observe/static/views/batch.js +226 -0
- package/dist/worker/observe/static/views/dag-graph.js +172 -0
- package/dist/worker/observe/static/views/dag-inspector.js +477 -0
- package/dist/worker/observe/static/views/dag.js +362 -0
- package/dist/worker/observe/static/views/dashboard.js +442 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +453 -0
- package/dist/worker/observe/static/views/pool.js +347 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +205 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +260 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/init-hybrid.js +396 -3
- package/dist/workflows/dag/node-execution.js +123 -29
- package/dist/workflows/dag/repair-artifact.js +91 -0
- package/dist/workflows/dag/report.js +50 -0
- package/dist/workflows/dag/retry-policy.js +138 -0
- package/dist/workflows/dag/runner.js +32 -0
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/skill-snapshot.js +2 -0
- package/dist/workflows/dag/types.js +44 -1
- package/dist/workflows/dag/validate.js +68 -4
- package/docs/agent-dag-runner.md +26 -1
- package/docs/architecture/dag-execution.md +6 -0
- package/docs/architecture/evolution.md +4 -3
- package/docs/architecture/facts-and-state.md +1 -1
- package/docs/design/README.md +4 -3
- package/docs/exec-plans/active/README.md +1 -3
- package/docs/exec-plans/completed/README.md +11 -0
- package/docs/feature-workflow.md +28 -0
- package/docs/progress/README.md +18 -0
- package/docs/reports/README.md +8 -2
- package/docs/templates/agent-dag-report.schema.json +17 -0
- package/docs/templates/agent-dag.schema.json +69 -1
- package/docs/templates/agent-dag.supervised-implementation.json +8 -2
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +139 -0
- package/docs/templates/backend-test-dag.json +276 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -0
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +22 -3
- package/skills/loop-agent/references/verification-and-failure-handling.md +6 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/** Observe UI view module (R5). */
|
|
2
|
+
import {
|
|
3
|
+
UI_TEXT,
|
|
4
|
+
LIVENESS_LABELS,
|
|
5
|
+
ARTIFACT_LABELS,
|
|
6
|
+
ARTIFACT_KEYS,
|
|
7
|
+
STEP_LABELS,
|
|
8
|
+
STEP_ORDER,
|
|
9
|
+
DAG_INSPECTOR_DEFAULT_WIDTH,
|
|
10
|
+
DAG_INSPECTOR_MIN_WIDTH,
|
|
11
|
+
DAG_INSPECTOR_MAX_WIDTH,
|
|
12
|
+
DAG_INSPECTOR_RIGHT_OFFSET,
|
|
13
|
+
DAG_INSPECTOR_VIEWPORT_MARGIN,
|
|
14
|
+
DAG_INSPECTOR_KEYBOARD_STEP,
|
|
15
|
+
TERMINAL_RUN_STATUSES,
|
|
16
|
+
DAG_EFFECTIVE_STATUS_LABELS,
|
|
17
|
+
STATUS_LABELS,
|
|
18
|
+
} from "../constants.js";
|
|
19
|
+
import { clearNode, el, metaGrid, buildTable } from "../dom.js";
|
|
20
|
+
import {
|
|
21
|
+
badge,
|
|
22
|
+
badgeClass,
|
|
23
|
+
statusLabel,
|
|
24
|
+
formatTs,
|
|
25
|
+
formatMs,
|
|
26
|
+
formatDuration,
|
|
27
|
+
formatAgo,
|
|
28
|
+
dagStatusBadge,
|
|
29
|
+
livenessBadge,
|
|
30
|
+
displayDurationMs,
|
|
31
|
+
derivedDurationMs,
|
|
32
|
+
summaryText,
|
|
33
|
+
truncateText,
|
|
34
|
+
repoBaseName,
|
|
35
|
+
parseMarkdownBlocks,
|
|
36
|
+
markdownLinkHref,
|
|
37
|
+
} from "../format.js";
|
|
38
|
+
import { parseRoute, navigate, buildHashPath } from "../router.js";
|
|
39
|
+
import { fetchJson, artifactUrl, parseArtifactPreviewResponse } from "../api.js";
|
|
40
|
+
import {
|
|
41
|
+
objectRouteLink,
|
|
42
|
+
findFeatureInSnapshot,
|
|
43
|
+
featureLookupState,
|
|
44
|
+
buildObjectRelations,
|
|
45
|
+
renderObjectRelationBar,
|
|
46
|
+
} from "../relations.js";
|
|
47
|
+
import { copyText, renderRecommendedCommand } from "../copy.js";
|
|
48
|
+
import {
|
|
49
|
+
isPageVisible,
|
|
50
|
+
detailPollDelay,
|
|
51
|
+
dashboardPollDelay,
|
|
52
|
+
captureDagGraphViewportState,
|
|
53
|
+
updateDagGraphViewportState,
|
|
54
|
+
updateDagTimelineViewportState,
|
|
55
|
+
renderViewState,
|
|
56
|
+
mountViewState,
|
|
57
|
+
} from "../state.js";
|
|
58
|
+
import { uiState } from "../state.js";
|
|
59
|
+
import { sortPoolTasks } from "../format-pool.js";
|
|
60
|
+
import {
|
|
61
|
+
dagMetaVisibility,
|
|
62
|
+
isDagRunActive,
|
|
63
|
+
dashboardVisibleFeatures,
|
|
64
|
+
findDagForRun,
|
|
65
|
+
} from "../dag-model.js";
|
|
66
|
+
import { layoutDag } from "../dag-layout.js";
|
|
67
|
+
import { showView, setBreadcrumb, updateHeaderRefresh, mountRelationBar } from "../shell-chrome.js";
|
|
68
|
+
import { artifactLink } from "../kpi.js";
|
|
69
|
+
import { renderInlineArtifactList } from "./run.js";
|
|
70
|
+
import { mountBoundedEventTimeline } from "./timeline.js";
|
|
71
|
+
|
|
72
|
+
export async function renderBatch(batchRunId) {
|
|
73
|
+
showView("batch");
|
|
74
|
+
setBreadcrumb([
|
|
75
|
+
{ label: UI_TEXT.dashboard, href: "#/" },
|
|
76
|
+
{ label: batchRunId },
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const metaEl = document.getElementById("batch-meta");
|
|
80
|
+
const tasksEl = document.getElementById("batch-tasks");
|
|
81
|
+
const evidenceEl = document.getElementById("batch-evidence");
|
|
82
|
+
const timelineEl = document.getElementById("batch-timeline");
|
|
83
|
+
if (metaEl) mountViewState(metaEl, "loading", "加载批次…");
|
|
84
|
+
|
|
85
|
+
const [batch, snapshot] = await Promise.all([
|
|
86
|
+
fetchJson(`/api/batches/${encodeURIComponent(batchRunId)}`),
|
|
87
|
+
fetchJson("/api/snapshot"),
|
|
88
|
+
]);
|
|
89
|
+
if (snapshot) {
|
|
90
|
+
uiState.lastSnapshot = snapshot;
|
|
91
|
+
updateHeaderRefresh(snapshot.generatedAt);
|
|
92
|
+
}
|
|
93
|
+
if (!batch) {
|
|
94
|
+
clearNode(metaEl);
|
|
95
|
+
mountViewState(metaEl, "error", "找不到该批次。");
|
|
96
|
+
if (tasksEl) clearNode(tasksEl);
|
|
97
|
+
if (evidenceEl) clearNode(evidenceEl);
|
|
98
|
+
if (timelineEl) clearNode(timelineEl);
|
|
99
|
+
mountRelationBar("batch-relations", {}, snapshot);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
mountRelationBar(
|
|
104
|
+
"batch-relations",
|
|
105
|
+
{
|
|
106
|
+
featureId: batch.featureId,
|
|
107
|
+
batchRunId: batch.batchRunId,
|
|
108
|
+
taskId: batch.tasks?.[0]?.taskId,
|
|
109
|
+
workerRunId: batch.tasks?.[0]?.workerRunId,
|
|
110
|
+
dagRunId: batch.tasks?.[0]?.dagRunId,
|
|
111
|
+
},
|
|
112
|
+
snapshot,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const totalDerived = derivedDurationMs(batch.startedAt, batch.finishedAt);
|
|
116
|
+
clearNode(metaEl);
|
|
117
|
+
metaEl.appendChild(
|
|
118
|
+
metaGrid([
|
|
119
|
+
["批次 ID", batch.batchRunId],
|
|
120
|
+
[
|
|
121
|
+
"功能 ID",
|
|
122
|
+
batch.featureId
|
|
123
|
+
? objectRouteLink("feature", batch.featureId, snapshot)
|
|
124
|
+
: el("span", "muted", "—"),
|
|
125
|
+
],
|
|
126
|
+
["状态", badge(batch.status)],
|
|
127
|
+
["开始时间", formatTs(batch.startedAt)],
|
|
128
|
+
["结束时间", formatTs(batch.finishedAt)],
|
|
129
|
+
["总耗时", formatDuration(totalDerived)],
|
|
130
|
+
["摘要", summaryText(batch.summary)],
|
|
131
|
+
]),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
clearNode(tasksEl);
|
|
135
|
+
tasksEl.appendChild(el("h3", null, "Task"));
|
|
136
|
+
|
|
137
|
+
const tasks = batch.tasks ?? [];
|
|
138
|
+
if (tasks.length === 0) {
|
|
139
|
+
mountViewState(tasksEl, "empty", "该批次暂无 Task");
|
|
140
|
+
} else {
|
|
141
|
+
const snap = snapshot ?? uiState.lastSnapshot;
|
|
142
|
+
const rows = tasks.map((task) => {
|
|
143
|
+
const links = el("div", "artifact-links batch-task-links");
|
|
144
|
+
links.appendChild(objectRouteLink("task", task.taskId, snap));
|
|
145
|
+
links.appendChild(document.createTextNode(" · "));
|
|
146
|
+
links.appendChild(objectRouteLink("run", task.workerRunId, snap));
|
|
147
|
+
if (task.dagRunId) {
|
|
148
|
+
links.appendChild(document.createTextNode(" · "));
|
|
149
|
+
links.appendChild(objectRouteLink("dag", task.dagRunId, snap));
|
|
150
|
+
}
|
|
151
|
+
const evidence = el("div", "artifact-links batch-task-evidence");
|
|
152
|
+
const refs = task.artifactRefs ?? {};
|
|
153
|
+
evidence.appendChild(artifactLink("报告", refs.reportMarkdown));
|
|
154
|
+
evidence.appendChild(document.createTextNode(" "));
|
|
155
|
+
evidence.appendChild(artifactLink("诊断", refs.doctorMarkdown));
|
|
156
|
+
evidence.appendChild(document.createTextNode(" "));
|
|
157
|
+
evidence.appendChild(artifactLink("收尾方案", refs.closeoutDraft));
|
|
158
|
+
evidence.appendChild(document.createTextNode(" "));
|
|
159
|
+
evidence.appendChild(artifactLink("run 记录", refs.runRecordPath));
|
|
160
|
+
const taskDur = displayDurationMs(task);
|
|
161
|
+
return {
|
|
162
|
+
className: task.status === "running" ? "active-task" : "",
|
|
163
|
+
onClick: task.workerRunId
|
|
164
|
+
? () => navigate(`/run/${encodeURIComponent(task.workerRunId)}`)
|
|
165
|
+
: undefined,
|
|
166
|
+
cells: [
|
|
167
|
+
task.taskId,
|
|
168
|
+
task.featureId ?? "—",
|
|
169
|
+
task.harnessTaskId ?? "—",
|
|
170
|
+
task.workerRunId ?? "—",
|
|
171
|
+
badge(task.status),
|
|
172
|
+
formatTs(task.startedAt),
|
|
173
|
+
formatTs(task.finishedAt),
|
|
174
|
+
formatMs(taskDur),
|
|
175
|
+
task.failureCategory ?? "—",
|
|
176
|
+
task.recommendedFollowUp ?? "—",
|
|
177
|
+
links,
|
|
178
|
+
evidence,
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
tasksEl.appendChild(
|
|
183
|
+
buildTable(
|
|
184
|
+
[
|
|
185
|
+
"Task ID",
|
|
186
|
+
"Feature",
|
|
187
|
+
"Harness Task",
|
|
188
|
+
"Worker 运行 ID",
|
|
189
|
+
"状态",
|
|
190
|
+
"开始",
|
|
191
|
+
"结束",
|
|
192
|
+
"耗时",
|
|
193
|
+
"失败分类",
|
|
194
|
+
"建议跟进",
|
|
195
|
+
"关联",
|
|
196
|
+
"证据",
|
|
197
|
+
],
|
|
198
|
+
rows,
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (evidenceEl) {
|
|
204
|
+
clearNode(evidenceEl);
|
|
205
|
+
evidenceEl.appendChild(el("h3", null, "批次证据预览"));
|
|
206
|
+
const batchArts = new Map();
|
|
207
|
+
if (batch.batchRunPath) batchArts.set("batch-run", batch.batchRunPath);
|
|
208
|
+
for (const task of tasks) {
|
|
209
|
+
const refs = task.artifactRefs ?? {};
|
|
210
|
+
for (const key of ARTIFACT_KEYS) {
|
|
211
|
+
const p = refs[key];
|
|
212
|
+
if (p) batchArts.set(`${task.taskId}:${key}`, p);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
renderInlineArtifactList(evidenceEl, batchArts);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (timelineEl) {
|
|
219
|
+
await mountBoundedEventTimeline(
|
|
220
|
+
timelineEl,
|
|
221
|
+
`/api/batches/${encodeURIComponent(batchRunId)}/events`,
|
|
222
|
+
"批次事件时间线",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** DAG dependency graph rendering (Observe UI R5). */
|
|
2
|
+
import { layoutDag } from "../dag-layout.js";
|
|
3
|
+
import { el } from "../dom.js";
|
|
4
|
+
import {
|
|
5
|
+
statusLabel,
|
|
6
|
+
statusSymbol,
|
|
7
|
+
formatMs,
|
|
8
|
+
truncateText,
|
|
9
|
+
} from "../format.js";
|
|
10
|
+
import {
|
|
11
|
+
uiState,
|
|
12
|
+
dagGraphViewportStates,
|
|
13
|
+
updateDagGraphViewportState,
|
|
14
|
+
} from "../state.js";
|
|
15
|
+
import { selectDagNode } from "./dag-inspector.js";
|
|
16
|
+
|
|
17
|
+
export function svgEl(tag, attrs = {}) {
|
|
18
|
+
const node = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
19
|
+
for (const [key, value] of Object.entries(attrs))
|
|
20
|
+
node.setAttribute(key, String(value));
|
|
21
|
+
return node;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function renderDagGraph(dag, dagRunId, existingWrap = null) {
|
|
25
|
+
const nodes = dag.nodes ?? [];
|
|
26
|
+
const edges = dag.edges ?? [];
|
|
27
|
+
const layout = layoutDag(nodes, edges, dag.ranks ?? []);
|
|
28
|
+
const wrap = existingWrap ?? el("div", "dag-graph-wrap");
|
|
29
|
+
if (existingWrap) {
|
|
30
|
+
// reuse-dag-graph-wrap: replace children while keeping wrap identity.
|
|
31
|
+
while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
|
|
32
|
+
}
|
|
33
|
+
const toolbar = el("div", "dag-graph-toolbar");
|
|
34
|
+
toolbar.appendChild(
|
|
35
|
+
el(
|
|
36
|
+
"span",
|
|
37
|
+
"dag-graph-summary",
|
|
38
|
+
`${nodes.length} 节点 · ${layout.edges.length} 条依赖`,
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
const reset = el("button", "dag-graph-reset", "重置视图");
|
|
42
|
+
toolbar.appendChild(reset);
|
|
43
|
+
wrap.appendChild(toolbar);
|
|
44
|
+
if (edges.length === 0)
|
|
45
|
+
wrap.appendChild(
|
|
46
|
+
el(
|
|
47
|
+
"p",
|
|
48
|
+
"dag-graph-fallback",
|
|
49
|
+
"该运行没有可用依赖数据;图按并行层级展示,节点表格仍保留完整数据。",
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
const viewport = el("div", "dag-graph-viewport");
|
|
53
|
+
viewport.dataset.dagRunId = dagRunId;
|
|
54
|
+
const svg = svgEl("svg", {
|
|
55
|
+
class: "dag-graph",
|
|
56
|
+
viewBox: `0 0 ${layout.width} ${layout.height}`,
|
|
57
|
+
width: layout.width,
|
|
58
|
+
height: layout.height,
|
|
59
|
+
role: "img",
|
|
60
|
+
"aria-label": `DAG 依赖图,共 ${nodes.length} 个节点`,
|
|
61
|
+
});
|
|
62
|
+
const defs = svgEl("defs");
|
|
63
|
+
const marker = svgEl("marker", {
|
|
64
|
+
id: "dag-arrow",
|
|
65
|
+
viewBox: "0 0 10 10",
|
|
66
|
+
refX: 9,
|
|
67
|
+
refY: 5,
|
|
68
|
+
markerWidth: 7,
|
|
69
|
+
markerHeight: 7,
|
|
70
|
+
orient: "auto-start-reverse",
|
|
71
|
+
});
|
|
72
|
+
marker.appendChild(svgEl("path", { d: "M 0 0 L 10 5 L 0 10 z" }));
|
|
73
|
+
defs.appendChild(marker);
|
|
74
|
+
svg.appendChild(defs);
|
|
75
|
+
const related = new Set([uiState.selectedDagNodeId]);
|
|
76
|
+
for (const edge of layout.edges)
|
|
77
|
+
if (
|
|
78
|
+
edge.from === uiState.selectedDagNodeId ||
|
|
79
|
+
edge.to === uiState.selectedDagNodeId
|
|
80
|
+
) {
|
|
81
|
+
related.add(edge.from);
|
|
82
|
+
related.add(edge.to);
|
|
83
|
+
}
|
|
84
|
+
for (const edge of layout.edges) {
|
|
85
|
+
const path = svgEl("path", {
|
|
86
|
+
d: edge.path,
|
|
87
|
+
class: `dag-edge${uiState.selectedDagNodeId && (!related.has(edge.from) || !related.has(edge.to)) ? " dag-dimmed" : ""}`,
|
|
88
|
+
"marker-end": "url(#dag-arrow)",
|
|
89
|
+
});
|
|
90
|
+
path.appendChild(svgEl("title"));
|
|
91
|
+
path.firstChild.textContent = `${edge.from} → ${edge.to}`;
|
|
92
|
+
svg.appendChild(path);
|
|
93
|
+
}
|
|
94
|
+
const byId = Object.fromEntries(nodes.map((node) => [node.nodeId, node]));
|
|
95
|
+
for (const position of layout.nodes) {
|
|
96
|
+
const node = byId[position.nodeId] ?? { nodeId: position.nodeId };
|
|
97
|
+
const status = (node.status ?? "unknown").toLowerCase();
|
|
98
|
+
const group = svgEl("g", {
|
|
99
|
+
transform: `translate(${position.x} ${position.y})`,
|
|
100
|
+
class: `dag-graph-node status-${status}${node.nodeId === uiState.selectedDagNodeId ? " selected" : ""}${uiState.selectedDagNodeId && !related.has(node.nodeId) ? " dag-dimmed" : ""}`,
|
|
101
|
+
tabindex: 0,
|
|
102
|
+
role: "button",
|
|
103
|
+
"aria-label": `${node.nodeId},状态 ${status}`,
|
|
104
|
+
});
|
|
105
|
+
group.appendChild(
|
|
106
|
+
svgEl("rect", { width: position.width, height: position.height, rx: 10 }),
|
|
107
|
+
);
|
|
108
|
+
const title = svgEl("title");
|
|
109
|
+
title.textContent = `${node.nodeId}\n${node.executor ?? "未知执行器"}\n${status}`;
|
|
110
|
+
group.appendChild(title);
|
|
111
|
+
const label = svgEl("text", { x: 14, y: 26, class: "dag-node-label" });
|
|
112
|
+
label.textContent = truncateText(node.label ?? node.nodeId, 26);
|
|
113
|
+
group.appendChild(label);
|
|
114
|
+
const executor = svgEl("text", { x: 14, y: 50, class: "dag-node-meta" });
|
|
115
|
+
executor.textContent = `◈ ${node.executor ?? "unknown"}`;
|
|
116
|
+
group.appendChild(executor);
|
|
117
|
+
const state = svgEl("text", { x: 14, y: 74, class: "dag-node-status" });
|
|
118
|
+
state.textContent = `${statusSymbol(status)} ${statusLabel(node.status)} · ${formatMs(node.durationMs)}`;
|
|
119
|
+
group.appendChild(state);
|
|
120
|
+
const activate = () => selectDagNode(dagRunId, node.nodeId);
|
|
121
|
+
group.addEventListener("click", activate);
|
|
122
|
+
group.addEventListener("keydown", (event) => {
|
|
123
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
activate();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
svg.appendChild(group);
|
|
129
|
+
}
|
|
130
|
+
viewport.appendChild(svg);
|
|
131
|
+
wrap.appendChild(viewport);
|
|
132
|
+
const graphViewportState =
|
|
133
|
+
dagGraphViewportStates.get(dagRunId) ??
|
|
134
|
+
(uiState.dagGraphViewportState?.dagRunId === dagRunId
|
|
135
|
+
? uiState.dagGraphViewportState
|
|
136
|
+
: null);
|
|
137
|
+
viewport.addEventListener(
|
|
138
|
+
"scroll",
|
|
139
|
+
() => {
|
|
140
|
+
uiState.dagGraphViewportState = updateDagGraphViewportState(
|
|
141
|
+
uiState.dagGraphViewportState,
|
|
142
|
+
dagRunId,
|
|
143
|
+
viewport.scrollLeft,
|
|
144
|
+
viewport.scrollTop,
|
|
145
|
+
);
|
|
146
|
+
dagGraphViewportStates.set(dagRunId, uiState.dagGraphViewportState);
|
|
147
|
+
},
|
|
148
|
+
{ passive: true },
|
|
149
|
+
);
|
|
150
|
+
if (graphViewportState) {
|
|
151
|
+
const restoreViewport = () => {
|
|
152
|
+
viewport.scrollLeft = graphViewportState.scrollLeft;
|
|
153
|
+
viewport.scrollTop = graphViewportState.scrollTop;
|
|
154
|
+
};
|
|
155
|
+
if (typeof requestAnimationFrame === "function") {
|
|
156
|
+
requestAnimationFrame(restoreViewport);
|
|
157
|
+
} else {
|
|
158
|
+
restoreViewport();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
reset.addEventListener("click", () => {
|
|
162
|
+
uiState.dagGraphViewportState = updateDagGraphViewportState(
|
|
163
|
+
uiState.dagGraphViewportState,
|
|
164
|
+
dagRunId,
|
|
165
|
+
0,
|
|
166
|
+
0,
|
|
167
|
+
);
|
|
168
|
+
dagGraphViewportStates.set(dagRunId, uiState.dagGraphViewportState);
|
|
169
|
+
viewport.scrollTo({ left: 0, top: 0, behavior: "smooth" });
|
|
170
|
+
});
|
|
171
|
+
return wrap;
|
|
172
|
+
}
|