agentweaver 0.1.18 → 0.1.20
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/README.md +54 -6
- package/dist/artifacts.js +9 -0
- package/dist/executors/git-commit-executor.js +24 -6
- package/dist/flow-state.js +3 -8
- package/dist/git/git-diff-parser.js +223 -0
- package/dist/git/git-service.js +562 -0
- package/dist/git/git-stage-selection.js +24 -0
- package/dist/git/git-status-parser.js +171 -0
- package/dist/git/git-types.js +1 -0
- package/dist/index.js +454 -108
- package/dist/interactive/auto-flow.js +644 -0
- package/dist/interactive/controller.js +489 -7
- package/dist/interactive/progress.js +194 -1
- package/dist/interactive/state.js +34 -0
- package/dist/interactive/web/index.js +237 -5
- package/dist/interactive/web/protocol.js +222 -1
- package/dist/interactive/web/server.js +497 -3
- package/dist/interactive/web/static/app.js +2462 -37
- package/dist/interactive/web/static/index.html +113 -11
- package/dist/interactive/web/static/styles.css +1 -1
- package/dist/interactive/web/static/styles.input.css +1383 -149
- package/dist/pipeline/auto-flow-blocks.js +307 -0
- package/dist/pipeline/auto-flow-config.js +273 -0
- package/dist/pipeline/auto-flow-identity.js +49 -0
- package/dist/pipeline/auto-flow-presets.js +52 -0
- package/dist/pipeline/auto-flow-resolver.js +830 -0
- package/dist/pipeline/auto-flow-types.js +17 -0
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flows.js +27 -1
- package/dist/pipeline/flow-specs/auto-common-guided.json +11 -0
- package/dist/pipeline/flow-specs/auto-golang.json +12 -1
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +54 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +19 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +33 -1
- package/dist/pipeline/flow-specs/review/review-project.json +19 -1
- package/dist/pipeline/flow-specs/task-source/manual-jira-input.json +70 -0
- package/dist/pipeline/node-registry.js +9 -0
- package/dist/pipeline/nodes/codex-prompt-node.js +8 -1
- package/dist/pipeline/nodes/flow-run-node.js +5 -3
- package/dist/pipeline/nodes/git-status-node.js +2 -168
- package/dist/pipeline/nodes/manual-jira-task-input-node.js +146 -0
- package/dist/pipeline/nodes/opencode-prompt-node.js +8 -1
- package/dist/pipeline/nodes/plan-codex-node.js +8 -1
- package/dist/pipeline/spec-loader.js +14 -4
- package/dist/runtime/artifact-catalog.js +403 -0
- package/dist/runtime/settings.js +114 -0
- package/dist/scope.js +14 -4
- package/package.json +1 -1
- package/dist/pipeline/flow-specs/auto-common.json +0 -179
- package/dist/pipeline/flow-specs/auto-simple.json +0 -141
|
@@ -1,11 +1,57 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
+
var AUTO_FLOW_HEIGHT_DEFAULT = 520;
|
|
5
|
+
var AUTO_FLOW_HEIGHT_MIN = 120;
|
|
6
|
+
var AUTO_FLOW_HEIGHT_MAX = 640;
|
|
7
|
+
var AUTO_FLOW_LOWER_PANELS_MIN = 180;
|
|
8
|
+
var WORKSPACE_SPLIT_DEFAULT = 36;
|
|
9
|
+
var WORKSPACE_SPLIT_MIN = 24;
|
|
10
|
+
var WORKSPACE_SPLIT_MAX = 58;
|
|
11
|
+
var autoFlowResizeDrag = null;
|
|
12
|
+
var workspaceResizeDrag = null;
|
|
13
|
+
|
|
4
14
|
var state = {
|
|
5
15
|
viewModel: null,
|
|
6
16
|
connectionState: "connecting",
|
|
17
|
+
theme: "light",
|
|
18
|
+
autoFlowHeight: null,
|
|
19
|
+
workspaceSplit: WORKSPACE_SPLIT_DEFAULT,
|
|
20
|
+
logAutoscroll: true,
|
|
7
21
|
formValues: {},
|
|
8
22
|
modalSignature: null,
|
|
23
|
+
artifacts: {
|
|
24
|
+
signature: null,
|
|
25
|
+
loading: false,
|
|
26
|
+
catalog: null,
|
|
27
|
+
error: null,
|
|
28
|
+
preview: null,
|
|
29
|
+
selectedId: null,
|
|
30
|
+
actionStatus: null,
|
|
31
|
+
actionStatusFailed: false,
|
|
32
|
+
previewRequestId: 0,
|
|
33
|
+
viewerModes: {},
|
|
34
|
+
},
|
|
35
|
+
gitSelectedPaths: [],
|
|
36
|
+
gitCommitMessage: "",
|
|
37
|
+
gitDiff: {
|
|
38
|
+
open: false,
|
|
39
|
+
selectedPath: null,
|
|
40
|
+
mode: "head",
|
|
41
|
+
loading: false,
|
|
42
|
+
error: null,
|
|
43
|
+
diff: null,
|
|
44
|
+
requestId: 0,
|
|
45
|
+
},
|
|
46
|
+
scrollSync: {
|
|
47
|
+
suppress: false,
|
|
48
|
+
releaseTimer: null,
|
|
49
|
+
sentOffsets: {
|
|
50
|
+
progress: null,
|
|
51
|
+
log: null,
|
|
52
|
+
help: null,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
9
55
|
};
|
|
10
56
|
|
|
11
57
|
var elements = {
|
|
@@ -16,16 +62,62 @@
|
|
|
16
62
|
run: document.getElementById("run-button"),
|
|
17
63
|
interrupt: document.getElementById("interrupt-button"),
|
|
18
64
|
help: document.getElementById("help-button"),
|
|
65
|
+
themeToggle: document.getElementById("theme-toggle-button"),
|
|
66
|
+
themeToggleLabel: document.getElementById("theme-toggle-label"),
|
|
19
67
|
flowsTitle: document.getElementById("flows-title"),
|
|
20
68
|
flows: document.getElementById("flows-list"),
|
|
21
|
-
|
|
69
|
+
autoFlowEditor: document.getElementById("auto-flow-editor"),
|
|
70
|
+
autoFlowResizer: document.getElementById("auto-flow-resizer"),
|
|
71
|
+
splitPanels: document.getElementById("split-panels"),
|
|
72
|
+
workspaceResizer: document.getElementById("workspace-resizer"),
|
|
22
73
|
progressTitle: document.getElementById("progress-title"),
|
|
74
|
+
progressFlowLabel: document.getElementById("progress-flow-label"),
|
|
23
75
|
progress: document.getElementById("progress-text"),
|
|
24
|
-
|
|
25
|
-
|
|
76
|
+
gitRefresh: document.getElementById("git-refresh-button"),
|
|
77
|
+
gitSummary: document.getElementById("git-summary"),
|
|
78
|
+
gitBranchInput: document.getElementById("git-branch-input"),
|
|
79
|
+
gitCreateBranch: document.getElementById("git-create-branch-button"),
|
|
80
|
+
gitCheckoutSelect: document.getElementById("git-checkout-select"),
|
|
81
|
+
gitCheckout: document.getElementById("git-checkout-button"),
|
|
82
|
+
gitFetch: document.getElementById("git-fetch-button"),
|
|
83
|
+
gitPull: document.getElementById("git-pull-button"),
|
|
84
|
+
gitFiles: document.getElementById("git-files"),
|
|
85
|
+
gitCommitMessage: document.getElementById("git-commit-message"),
|
|
86
|
+
gitStage: document.getElementById("git-stage-button"),
|
|
87
|
+
gitUnstage: document.getElementById("git-unstage-button"),
|
|
88
|
+
gitCommit: document.getElementById("git-commit-button"),
|
|
89
|
+
gitPush: document.getElementById("git-push-button"),
|
|
90
|
+
gitFeedback: document.getElementById("git-feedback"),
|
|
91
|
+
gitDiffDrawer: document.getElementById("git-diff-drawer"),
|
|
92
|
+
gitDiffClose: document.getElementById("git-diff-close-button"),
|
|
93
|
+
gitDiffTitle: document.getElementById("git-diff-title"),
|
|
94
|
+
gitDiffMeta: document.getElementById("git-diff-meta"),
|
|
95
|
+
gitDiffStatus: document.getElementById("git-diff-status"),
|
|
96
|
+
gitDiffFileList: document.getElementById("git-diff-file-list"),
|
|
97
|
+
gitDiffSelectedTitle: document.getElementById("git-diff-selected-title"),
|
|
98
|
+
gitDiffSelectedMeta: document.getElementById("git-diff-selected-meta"),
|
|
99
|
+
gitDiffModeControls: document.getElementById("git-diff-mode-controls"),
|
|
100
|
+
gitDiffBody: document.getElementById("git-diff-body"),
|
|
26
101
|
logTitle: document.getElementById("log-title"),
|
|
27
102
|
log: document.getElementById("log-text"),
|
|
103
|
+
logAutoscroll: document.getElementById("log-autoscroll-toggle"),
|
|
28
104
|
clearLog: document.getElementById("clear-log-button"),
|
|
105
|
+
artifactOpen: document.getElementById("artifact-open-button"),
|
|
106
|
+
artifactDrawer: document.getElementById("artifact-drawer"),
|
|
107
|
+
artifactClose: document.getElementById("artifact-close-button"),
|
|
108
|
+
artifactTitle: document.getElementById("artifact-title"),
|
|
109
|
+
artifactMeta: document.getElementById("artifact-meta"),
|
|
110
|
+
artifactMessage: document.getElementById("artifact-message"),
|
|
111
|
+
artifactList: document.getElementById("artifact-list"),
|
|
112
|
+
artifactSelectedTitle: document.getElementById("artifact-selected-title"),
|
|
113
|
+
artifactSelectedMeta: document.getElementById("artifact-selected-meta"),
|
|
114
|
+
artifactActionStatus: document.getElementById("artifact-action-status"),
|
|
115
|
+
artifactCopyContent: document.getElementById("artifact-copy-content-button"),
|
|
116
|
+
artifactCopyReference: document.getElementById("artifact-copy-reference-button"),
|
|
117
|
+
artifactOpenRaw: document.getElementById("artifact-open-raw-link"),
|
|
118
|
+
artifactDownload: document.getElementById("artifact-download-link"),
|
|
119
|
+
artifactToolbarClose: document.getElementById("artifact-toolbar-close-button"),
|
|
120
|
+
artifactPreview: document.getElementById("artifact-preview-text"),
|
|
29
121
|
helpPanel: document.getElementById("help-panel"),
|
|
30
122
|
helpText: document.getElementById("help-text"),
|
|
31
123
|
closeHelp: document.getElementById("close-help-button"),
|
|
@@ -39,10 +131,411 @@
|
|
|
39
131
|
return fallback;
|
|
40
132
|
}
|
|
41
133
|
|
|
134
|
+
function normalizeTheme(value) {
|
|
135
|
+
return value === "dark" ? "dark" : "light";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isTheme(value) {
|
|
139
|
+
return value === "dark" || value === "light";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function persistThemePreference(theme) {
|
|
143
|
+
api.updateSettings({ theme: normalizeTheme(theme) });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function applyTheme(theme) {
|
|
147
|
+
var nextTheme = normalizeTheme(theme);
|
|
148
|
+
var root = document.documentElement || document.body;
|
|
149
|
+
state.theme = nextTheme;
|
|
150
|
+
if (root) {
|
|
151
|
+
root.setAttribute("data-theme", nextTheme);
|
|
152
|
+
}
|
|
153
|
+
if (elements.themeToggle) {
|
|
154
|
+
var dark = nextTheme === "dark";
|
|
155
|
+
elements.themeToggle.setAttribute("aria-pressed", dark ? "true" : "false");
|
|
156
|
+
elements.themeToggle.setAttribute("aria-label", dark ? "Switch to light theme" : "Switch to dark theme");
|
|
157
|
+
elements.themeToggle.title = dark ? "Switch to light theme" : "Switch to dark theme";
|
|
158
|
+
}
|
|
159
|
+
if (elements.themeToggleLabel) {
|
|
160
|
+
elements.themeToggleLabel.textContent = nextTheme === "dark" ? "Dark" : "Light";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function toggleTheme() {
|
|
165
|
+
var nextTheme = state.theme === "dark" ? "light" : "dark";
|
|
166
|
+
applyTheme(nextTheme);
|
|
167
|
+
persistThemePreference(nextTheme);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function persistAutoFlowHeight(height) {
|
|
171
|
+
var clamped = clampAutoFlowHeight(height);
|
|
172
|
+
api.updateSettings({ autoFlowHeight: clamped });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function autoFlowHeightBounds() {
|
|
176
|
+
var max = AUTO_FLOW_HEIGHT_MAX;
|
|
177
|
+
var detailsPane = elements && elements.autoFlowEditor ? elements.autoFlowEditor.parentNode : null;
|
|
178
|
+
if (detailsPane) {
|
|
179
|
+
var rectHeight = typeof detailsPane.getBoundingClientRect === "function"
|
|
180
|
+
? detailsPane.getBoundingClientRect().height
|
|
181
|
+
: 0;
|
|
182
|
+
if (Number.isFinite(rectHeight) && rectHeight > AUTO_FLOW_HEIGHT_MIN + AUTO_FLOW_LOWER_PANELS_MIN) {
|
|
183
|
+
max = Math.min(max, rectHeight - AUTO_FLOW_LOWER_PANELS_MIN);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
min: AUTO_FLOW_HEIGHT_MIN,
|
|
188
|
+
max: Math.max(AUTO_FLOW_HEIGHT_MIN, max),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function clampAutoFlowHeight(value) {
|
|
193
|
+
var numeric = Number(value);
|
|
194
|
+
if (!Number.isFinite(numeric)) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
var bounds = autoFlowHeightBounds();
|
|
198
|
+
return Math.min(bounds.max, Math.max(bounds.min, Math.round(numeric)));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getAutoFlowEditorHeight() {
|
|
202
|
+
if (!elements.autoFlowEditor) {
|
|
203
|
+
return state.autoFlowHeight || AUTO_FLOW_HEIGHT_DEFAULT;
|
|
204
|
+
}
|
|
205
|
+
if (typeof elements.autoFlowEditor.getBoundingClientRect === "function") {
|
|
206
|
+
var rect = elements.autoFlowEditor.getBoundingClientRect();
|
|
207
|
+
if (rect && Number.isFinite(rect.height) && rect.height > 0) {
|
|
208
|
+
return rect.height;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
var inlineHeight = parseInt(elements.autoFlowEditor.style.height || "", 10);
|
|
212
|
+
return Number.isFinite(inlineHeight) ? inlineHeight : (state.autoFlowHeight || AUTO_FLOW_HEIGHT_DEFAULT);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function applyAutoFlowHeight(height) {
|
|
216
|
+
if (!elements.autoFlowEditor) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
var clamped = clampAutoFlowHeight(height);
|
|
220
|
+
if (clamped === null) {
|
|
221
|
+
state.autoFlowHeight = null;
|
|
222
|
+
elements.autoFlowEditor.style.height = "";
|
|
223
|
+
updateAutoFlowResizerState(null);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
state.autoFlowHeight = clamped;
|
|
227
|
+
elements.autoFlowEditor.style.height = clamped + "px";
|
|
228
|
+
updateAutoFlowResizerState(clamped);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function updateAutoFlowResizerState(currentHeight) {
|
|
232
|
+
if (!elements.autoFlowResizer) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
var bounds = autoFlowHeightBounds();
|
|
236
|
+
elements.autoFlowResizer.setAttribute("aria-valuemin", String(bounds.min));
|
|
237
|
+
elements.autoFlowResizer.setAttribute("aria-valuemax", String(bounds.max));
|
|
238
|
+
elements.autoFlowResizer.setAttribute("aria-valuenow", String(Math.round(currentHeight || getAutoFlowEditorHeight())));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function beginAutoFlowResize(event) {
|
|
242
|
+
if (!elements.autoFlowEditor || elements.autoFlowEditor.hidden) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (event.button !== undefined && event.button !== 0) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (event.preventDefault) {
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
}
|
|
251
|
+
autoFlowResizeDrag = {
|
|
252
|
+
startY: Number.isFinite(Number(event.clientY)) ? Number(event.clientY) : 0,
|
|
253
|
+
startHeight: getAutoFlowEditorHeight(),
|
|
254
|
+
};
|
|
255
|
+
document.body.classList.add("auto-flow-resizing");
|
|
256
|
+
elements.autoFlowResizer.classList.add("resizing");
|
|
257
|
+
if (event.currentTarget && typeof event.currentTarget.setPointerCapture === "function" && event.pointerId !== undefined) {
|
|
258
|
+
try {
|
|
259
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
260
|
+
} catch {
|
|
261
|
+
// Pointer capture is a progressive enhancement for smoother dragging.
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
window.addEventListener("pointermove", updateAutoFlowResize);
|
|
265
|
+
window.addEventListener("pointerup", finishAutoFlowResize);
|
|
266
|
+
window.addEventListener("pointercancel", finishAutoFlowResize);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function updateAutoFlowResize(event) {
|
|
270
|
+
if (!autoFlowResizeDrag) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (event.preventDefault) {
|
|
274
|
+
event.preventDefault();
|
|
275
|
+
}
|
|
276
|
+
var clientY = Number(event.clientY);
|
|
277
|
+
if (!Number.isFinite(clientY)) {
|
|
278
|
+
clientY = autoFlowResizeDrag.startY;
|
|
279
|
+
}
|
|
280
|
+
applyAutoFlowHeight(autoFlowResizeDrag.startHeight + clientY - autoFlowResizeDrag.startY);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function finishAutoFlowResize() {
|
|
284
|
+
if (!autoFlowResizeDrag) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
autoFlowResizeDrag = null;
|
|
288
|
+
document.body.classList.remove("auto-flow-resizing");
|
|
289
|
+
elements.autoFlowResizer.classList.remove("resizing");
|
|
290
|
+
persistAutoFlowHeight(state.autoFlowHeight);
|
|
291
|
+
window.removeEventListener("pointermove", updateAutoFlowResize);
|
|
292
|
+
window.removeEventListener("pointerup", finishAutoFlowResize);
|
|
293
|
+
window.removeEventListener("pointercancel", finishAutoFlowResize);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function resetAutoFlowHeight() {
|
|
297
|
+
applyAutoFlowHeight(null);
|
|
298
|
+
persistAutoFlowHeight(null);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function handleAutoFlowResizerKeydown(event) {
|
|
302
|
+
var step = event.shiftKey ? 48 : 20;
|
|
303
|
+
var nextHeight = getAutoFlowEditorHeight();
|
|
304
|
+
if (event.key === "ArrowUp") {
|
|
305
|
+
nextHeight -= step;
|
|
306
|
+
} else if (event.key === "ArrowDown") {
|
|
307
|
+
nextHeight += step;
|
|
308
|
+
} else if (event.key === "Home") {
|
|
309
|
+
nextHeight = AUTO_FLOW_HEIGHT_MIN;
|
|
310
|
+
} else if (event.key === "End") {
|
|
311
|
+
nextHeight = AUTO_FLOW_HEIGHT_MAX;
|
|
312
|
+
} else {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
event.preventDefault();
|
|
316
|
+
applyAutoFlowHeight(nextHeight);
|
|
317
|
+
persistAutoFlowHeight(state.autoFlowHeight);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function persistWorkspaceSplit(split) {
|
|
321
|
+
var nextSplit = Number.isFinite(split) ? clampWorkspaceSplit(split) : WORKSPACE_SPLIT_DEFAULT;
|
|
322
|
+
api.updateSettings({ workspaceSplit: nextSplit });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function clampWorkspaceSplit(value) {
|
|
326
|
+
var numeric = Number(value);
|
|
327
|
+
if (!Number.isFinite(numeric)) {
|
|
328
|
+
return WORKSPACE_SPLIT_DEFAULT;
|
|
329
|
+
}
|
|
330
|
+
return Math.min(WORKSPACE_SPLIT_MAX, Math.max(WORKSPACE_SPLIT_MIN, Math.round(numeric)));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function applyWorkspaceSplit(split) {
|
|
334
|
+
if (!elements.splitPanels) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
var clamped = clampWorkspaceSplit(split);
|
|
338
|
+
state.workspaceSplit = clamped;
|
|
339
|
+
if (elements.splitPanels.style && typeof elements.splitPanels.style.setProperty === "function") {
|
|
340
|
+
elements.splitPanels.style.setProperty("--aw-work-panel-width", clamped + "%");
|
|
341
|
+
} else if (elements.splitPanels.style) {
|
|
342
|
+
elements.splitPanels.style["--aw-work-panel-width"] = clamped + "%";
|
|
343
|
+
}
|
|
344
|
+
updateWorkspaceResizerState(clamped);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function workspacePanelWidth() {
|
|
348
|
+
if (!elements.splitPanels || typeof elements.splitPanels.getBoundingClientRect !== "function") {
|
|
349
|
+
return 0;
|
|
350
|
+
}
|
|
351
|
+
var rect = elements.splitPanels.getBoundingClientRect();
|
|
352
|
+
return rect && Number.isFinite(rect.width) ? rect.width : 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function updateWorkspaceResizerState(split) {
|
|
356
|
+
if (!elements.workspaceResizer) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
elements.workspaceResizer.setAttribute("aria-valuemin", String(WORKSPACE_SPLIT_MIN));
|
|
360
|
+
elements.workspaceResizer.setAttribute("aria-valuemax", String(WORKSPACE_SPLIT_MAX));
|
|
361
|
+
elements.workspaceResizer.setAttribute("aria-valuenow", String(clampWorkspaceSplit(split)));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function beginWorkspaceResize(event) {
|
|
365
|
+
if (event.button !== undefined && event.button !== 0) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
var width = workspacePanelWidth();
|
|
369
|
+
if (width <= 0) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (event.preventDefault) {
|
|
373
|
+
event.preventDefault();
|
|
374
|
+
}
|
|
375
|
+
workspaceResizeDrag = {
|
|
376
|
+
startX: Number.isFinite(Number(event.clientX)) ? Number(event.clientX) : 0,
|
|
377
|
+
startSplit: state.workspaceSplit,
|
|
378
|
+
width: width,
|
|
379
|
+
};
|
|
380
|
+
document.body.classList.add("workspace-split-resizing");
|
|
381
|
+
elements.workspaceResizer.classList.add("resizing");
|
|
382
|
+
if (event.currentTarget && typeof event.currentTarget.setPointerCapture === "function" && event.pointerId !== undefined) {
|
|
383
|
+
try {
|
|
384
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
385
|
+
} catch {
|
|
386
|
+
// Pointer capture is a progressive enhancement for smoother dragging.
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
window.addEventListener("pointermove", updateWorkspaceResize);
|
|
390
|
+
window.addEventListener("pointerup", finishWorkspaceResize);
|
|
391
|
+
window.addEventListener("pointercancel", finishWorkspaceResize);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function updateWorkspaceResize(event) {
|
|
395
|
+
if (!workspaceResizeDrag) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (event.preventDefault) {
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
}
|
|
401
|
+
var clientX = Number(event.clientX);
|
|
402
|
+
if (!Number.isFinite(clientX)) {
|
|
403
|
+
clientX = workspaceResizeDrag.startX;
|
|
404
|
+
}
|
|
405
|
+
var nextSplit = workspaceResizeDrag.startSplit + ((clientX - workspaceResizeDrag.startX) / workspaceResizeDrag.width) * 100;
|
|
406
|
+
applyWorkspaceSplit(nextSplit);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function finishWorkspaceResize() {
|
|
410
|
+
if (!workspaceResizeDrag) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
workspaceResizeDrag = null;
|
|
414
|
+
document.body.classList.remove("workspace-split-resizing");
|
|
415
|
+
elements.workspaceResizer.classList.remove("resizing");
|
|
416
|
+
persistWorkspaceSplit(state.workspaceSplit);
|
|
417
|
+
window.removeEventListener("pointermove", updateWorkspaceResize);
|
|
418
|
+
window.removeEventListener("pointerup", finishWorkspaceResize);
|
|
419
|
+
window.removeEventListener("pointercancel", finishWorkspaceResize);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function resetWorkspaceSplit() {
|
|
423
|
+
applyWorkspaceSplit(WORKSPACE_SPLIT_DEFAULT);
|
|
424
|
+
persistWorkspaceSplit(null);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function handleWorkspaceResizerKeydown(event) {
|
|
428
|
+
var step = event.shiftKey ? 6 : 2;
|
|
429
|
+
var nextSplit = state.workspaceSplit;
|
|
430
|
+
if (event.key === "ArrowLeft") {
|
|
431
|
+
nextSplit -= step;
|
|
432
|
+
} else if (event.key === "ArrowRight") {
|
|
433
|
+
nextSplit += step;
|
|
434
|
+
} else if (event.key === "Home") {
|
|
435
|
+
nextSplit = WORKSPACE_SPLIT_MIN;
|
|
436
|
+
} else if (event.key === "End") {
|
|
437
|
+
nextSplit = WORKSPACE_SPLIT_MAX;
|
|
438
|
+
} else {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
event.preventDefault();
|
|
442
|
+
applyWorkspaceSplit(nextSplit);
|
|
443
|
+
persistWorkspaceSplit(state.workspaceSplit);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function persistLogAutoscroll(enabled) {
|
|
447
|
+
api.updateSettings({ logAutoscroll: Boolean(enabled) });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function applyLogAutoscroll(enabled) {
|
|
451
|
+
state.logAutoscroll = Boolean(enabled);
|
|
452
|
+
if (elements.logAutoscroll) {
|
|
453
|
+
elements.logAutoscroll.checked = state.logAutoscroll;
|
|
454
|
+
}
|
|
455
|
+
if (state.logAutoscroll) {
|
|
456
|
+
scrollLogToBottom();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function applyWebUiSettings(settings) {
|
|
461
|
+
if (!settings || typeof settings !== "object") {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (isTheme(settings.theme)) {
|
|
465
|
+
applyTheme(settings.theme);
|
|
466
|
+
}
|
|
467
|
+
if ("autoFlowHeight" in settings) {
|
|
468
|
+
applyAutoFlowHeight(settings.autoFlowHeight);
|
|
469
|
+
}
|
|
470
|
+
if (Number.isFinite(Number(settings.workspaceSplit))) {
|
|
471
|
+
applyWorkspaceSplit(settings.workspaceSplit);
|
|
472
|
+
}
|
|
473
|
+
if (typeof settings.logAutoscroll === "boolean") {
|
|
474
|
+
applyLogAutoscroll(settings.logAutoscroll);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function scrollLogToBottom() {
|
|
479
|
+
if (!elements.log) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
elements.log.scrollTop = elements.log.scrollHeight;
|
|
483
|
+
rememberScrollOffset("log", elements.log.scrollTop);
|
|
484
|
+
}
|
|
485
|
+
|
|
42
486
|
function actionId() {
|
|
43
487
|
return "web-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
|
|
44
488
|
}
|
|
45
489
|
|
|
490
|
+
function controlId(prefix) {
|
|
491
|
+
var parts = Array.prototype.slice.call(arguments, 1).map(function (part) {
|
|
492
|
+
return String(part || "")
|
|
493
|
+
.trim()
|
|
494
|
+
.replace(/[^A-Za-z0-9_-]+/g, "-")
|
|
495
|
+
.replace(/^-+|-+$/g, "") || "value";
|
|
496
|
+
});
|
|
497
|
+
return [prefix].concat(parts).join("-");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function roundedScrollTop(element) {
|
|
501
|
+
return Math.round(Number(element.scrollTop) || 0);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function rememberScrollOffset(pane, offset) {
|
|
505
|
+
if (state.scrollSync.sentOffsets[pane] !== undefined) {
|
|
506
|
+
state.scrollSync.sentOffsets[pane] = Math.round(Number(offset) || 0);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function rememberCurrentScrollOffsets() {
|
|
511
|
+
rememberScrollOffset("progress", elements.progress ? elements.progress.scrollTop : 0);
|
|
512
|
+
rememberScrollOffset("log", elements.log ? elements.log.scrollTop : 0);
|
|
513
|
+
rememberScrollOffset("help", elements.helpText ? elements.helpText.scrollTop : 0);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function releaseScrollSuppressionSoon() {
|
|
517
|
+
if (state.scrollSync.releaseTimer) {
|
|
518
|
+
clearTimeout(state.scrollSync.releaseTimer);
|
|
519
|
+
}
|
|
520
|
+
state.scrollSync.releaseTimer = setTimeout(function () {
|
|
521
|
+
state.scrollSync.suppress = false;
|
|
522
|
+
state.scrollSync.releaseTimer = null;
|
|
523
|
+
rememberCurrentScrollOffsets();
|
|
524
|
+
}, 0);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function sendScrollPane(pane, element) {
|
|
528
|
+
if (state.scrollSync.suppress) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
var offset = roundedScrollTop(element);
|
|
532
|
+
if (state.scrollSync.sentOffsets[pane] === offset) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
rememberScrollOffset(pane, offset);
|
|
536
|
+
api.scrollPane(pane, offset);
|
|
537
|
+
}
|
|
538
|
+
|
|
46
539
|
function selectedFlow() {
|
|
47
540
|
var vm = state.viewModel;
|
|
48
541
|
if (!vm || !Array.isArray(vm.flowItems)) {
|
|
@@ -129,6 +622,12 @@
|
|
|
129
622
|
clearLog: function () {
|
|
130
623
|
api.send({ type: "log.clear" });
|
|
131
624
|
},
|
|
625
|
+
openArtifactExplorer: function () {
|
|
626
|
+
api.send({ type: "artifactExplorer.open" });
|
|
627
|
+
},
|
|
628
|
+
closeArtifactExplorer: function () {
|
|
629
|
+
api.send({ type: "artifactExplorer.close" });
|
|
630
|
+
},
|
|
132
631
|
toggleHelp: function () {
|
|
133
632
|
api.send({ type: "help.toggle", visible: !(state.viewModel && state.viewModel.helpVisible) });
|
|
134
633
|
},
|
|
@@ -138,6 +637,60 @@
|
|
|
138
637
|
scrollPane: function (pane, offset) {
|
|
139
638
|
api.send({ type: "scroll", pane: pane, offset: offset });
|
|
140
639
|
},
|
|
640
|
+
refreshGit: function () {
|
|
641
|
+
api.send({ type: "git.refresh" });
|
|
642
|
+
},
|
|
643
|
+
createGitBranch: function () {
|
|
644
|
+
api.send({ type: "git.createBranch", branchName: elements.gitBranchInput.value });
|
|
645
|
+
},
|
|
646
|
+
checkoutGitBranch: function () {
|
|
647
|
+
api.send({ type: "git.checkout", branchName: elements.gitCheckoutSelect.value });
|
|
648
|
+
},
|
|
649
|
+
fetchGit: function () {
|
|
650
|
+
api.send({ type: "git.fetch" });
|
|
651
|
+
},
|
|
652
|
+
pullGit: function () {
|
|
653
|
+
api.send({ type: "git.pullFfOnly" });
|
|
654
|
+
},
|
|
655
|
+
stageGit: function () {
|
|
656
|
+
api.send({ type: "git.stage", paths: selectedGitPaths() });
|
|
657
|
+
},
|
|
658
|
+
unstageGit: function () {
|
|
659
|
+
api.send({ type: "git.unstage", paths: selectedGitPaths() });
|
|
660
|
+
},
|
|
661
|
+
commitGit: function () {
|
|
662
|
+
api.send({ type: "git.commit", paths: selectedGitPaths(), message: elements.gitCommitMessage.value });
|
|
663
|
+
},
|
|
664
|
+
pushGit: function () {
|
|
665
|
+
api.send({ type: "git.push" });
|
|
666
|
+
},
|
|
667
|
+
updateGitCommitMessage: function () {
|
|
668
|
+
api.send({ type: "git.updateCommitMessage", message: elements.gitCommitMessage.value });
|
|
669
|
+
},
|
|
670
|
+
updateSettings: function (settings) {
|
|
671
|
+
api.send({ type: "settings.update", settings: settings });
|
|
672
|
+
},
|
|
673
|
+
selectAutoFlowPreset: function (preset) {
|
|
674
|
+
api.send({ type: "autoFlow.selectPreset", preset: preset });
|
|
675
|
+
},
|
|
676
|
+
saveAutoFlow: function () {
|
|
677
|
+
api.send({ type: "autoFlow.save" });
|
|
678
|
+
},
|
|
679
|
+
resetAutoFlow: function () {
|
|
680
|
+
api.send({ type: "autoFlow.reset" });
|
|
681
|
+
},
|
|
682
|
+
toggleAutoFlowBlock: function (slotId, blockId, enabled) {
|
|
683
|
+
api.send({ type: "autoFlow.toggleBlock", slotId: slotId, blockId: blockId, enabled: enabled });
|
|
684
|
+
},
|
|
685
|
+
updateAutoFlowParam: function (slotId, blockId, paramName, value) {
|
|
686
|
+
api.send({ type: "autoFlow.updateParam", slotId: slotId, blockId: blockId, paramName: paramName, value: value });
|
|
687
|
+
},
|
|
688
|
+
insertAutoFlowBlock: function (slotId, blockId) {
|
|
689
|
+
api.send({ type: "autoFlow.insertBlock", slotId: slotId, blockId: blockId });
|
|
690
|
+
},
|
|
691
|
+
removeAutoFlowBlock: function (slotId, blockId) {
|
|
692
|
+
api.send({ type: "autoFlow.removeBlock", slotId: slotId, blockId: blockId });
|
|
693
|
+
},
|
|
141
694
|
};
|
|
142
695
|
|
|
143
696
|
function setConnection(nextState) {
|
|
@@ -156,11 +709,18 @@
|
|
|
156
709
|
}
|
|
157
710
|
|
|
158
711
|
if (message.type === "snapshot") {
|
|
712
|
+
applyWebUiSettings(message.settings);
|
|
159
713
|
var uiState = captureUiState();
|
|
160
714
|
state.viewModel = message.viewModel || {};
|
|
161
715
|
state.formValues = state.viewModel.form ? Object.assign({}, state.viewModel.form.values || {}) : {};
|
|
716
|
+
state.gitSelectedPaths = state.viewModel.gitWorkspace && Array.isArray(state.viewModel.gitWorkspace.selectedPaths)
|
|
717
|
+
? state.viewModel.gitWorkspace.selectedPaths.slice()
|
|
718
|
+
: [];
|
|
719
|
+
state.scrollSync.suppress = true;
|
|
162
720
|
render();
|
|
163
721
|
restoreUiState(uiState);
|
|
722
|
+
rememberCurrentScrollOffsets();
|
|
723
|
+
releaseScrollSuppressionSoon();
|
|
164
724
|
return;
|
|
165
725
|
}
|
|
166
726
|
if (message.type === "log.append") {
|
|
@@ -181,40 +741,978 @@
|
|
|
181
741
|
if (!lines) return;
|
|
182
742
|
var wasPinned = elements.log.scrollTop + elements.log.clientHeight >= elements.log.scrollHeight - 8;
|
|
183
743
|
elements.log.textContent += (elements.log.textContent ? "\n" : "") + lines;
|
|
184
|
-
if (wasPinned) {
|
|
185
|
-
|
|
744
|
+
if (state.logAutoscroll || wasPinned) {
|
|
745
|
+
scrollLogToBottom();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function render() {
|
|
750
|
+
var vm = state.viewModel || {};
|
|
751
|
+
elements.title.textContent = text(vm.title, "AgentWeaver");
|
|
752
|
+
elements.header.textContent = text(vm.header, "Local operator console");
|
|
753
|
+
elements.status.textContent = text(vm.statusText, "Idle");
|
|
754
|
+
elements.flowsTitle.textContent = text(vm.flowListTitle, "Flows");
|
|
755
|
+
renderAutoFlowEditor(vm);
|
|
756
|
+
elements.progressTitle.textContent = text(vm.progressTitle, "Progress");
|
|
757
|
+
renderProgress(vm);
|
|
758
|
+
elements.logTitle.textContent = text(vm.logTitle, "Activity");
|
|
759
|
+
setTextPreservingScroll(elements.log, text(vm.logText, ""), state.logAutoscroll);
|
|
760
|
+
elements.helpText.textContent = text(vm.helpText, "No help is available.");
|
|
761
|
+
elements.helpPanel.hidden = !vm.helpVisible;
|
|
762
|
+
elements.help.setAttribute("aria-pressed", vm.helpVisible ? "true" : "false");
|
|
763
|
+
|
|
764
|
+
renderFlows(vm);
|
|
765
|
+
renderGitWorkspace(vm);
|
|
766
|
+
renderGitDiffDrawer(vm);
|
|
767
|
+
renderModal(vm);
|
|
768
|
+
renderArtifactExplorer(vm);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function gitWorkspace(vm) {
|
|
772
|
+
var workspace = vm && vm.gitWorkspace;
|
|
773
|
+
if (!workspace || typeof workspace !== "object") {
|
|
774
|
+
return {
|
|
775
|
+
available: false,
|
|
776
|
+
changedFiles: [],
|
|
777
|
+
branches: [],
|
|
778
|
+
remotes: [],
|
|
779
|
+
selectedPaths: [],
|
|
780
|
+
commitMessage: "",
|
|
781
|
+
operation: { status: "idle" },
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
return workspace;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function renderGitWorkspace(vm) {
|
|
788
|
+
var git = gitWorkspace(vm);
|
|
789
|
+
var blocked = hasBlockingInput(vm);
|
|
790
|
+
var changedFiles = Array.isArray(git.changedFiles) ? git.changedFiles : [];
|
|
791
|
+
var branches = Array.isArray(git.branches) ? git.branches : [];
|
|
792
|
+
var operation = git.operation || { status: "idle" };
|
|
793
|
+
var isRunning = operation.status === "running";
|
|
794
|
+
|
|
795
|
+
if (typeof git.commitMessage === "string" && document.activeElement !== elements.gitCommitMessage) {
|
|
796
|
+
state.gitCommitMessage = git.commitMessage;
|
|
797
|
+
elements.gitCommitMessage.value = git.commitMessage;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
elements.gitSummary.innerHTML = "";
|
|
801
|
+
var summary = document.createElement("span");
|
|
802
|
+
if (!git.available) {
|
|
803
|
+
summary.textContent = git.error || "Git workspace is unavailable.";
|
|
804
|
+
} else {
|
|
805
|
+
summary.append(
|
|
806
|
+
document.createTextNode("Branch "),
|
|
807
|
+
strong(git.detachedHead ? "detached HEAD" : (git.branch || "-")),
|
|
808
|
+
document.createTextNode(git.clean ? " | clean" : " | dirty"),
|
|
809
|
+
document.createTextNode(" | upstream " + (git.upstream || "-")),
|
|
810
|
+
document.createTextNode(" | ahead " + (git.ahead || 0) + " behind " + (git.behind || 0)),
|
|
811
|
+
);
|
|
812
|
+
if (git.lastCommit && git.lastCommit.shortHash) {
|
|
813
|
+
summary.append(document.createTextNode(" | last " + git.lastCommit.shortHash + " " + (git.lastCommit.subject || "")));
|
|
814
|
+
}
|
|
815
|
+
if (git.refreshedAt) {
|
|
816
|
+
summary.append(document.createTextNode(" | refreshed " + git.refreshedAt));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
elements.gitSummary.append(summary);
|
|
820
|
+
|
|
821
|
+
renderGitBranches(branches, git.branch);
|
|
822
|
+
renderGitFiles(changedFiles);
|
|
823
|
+
|
|
824
|
+
elements.gitCreateBranch.disabled = blocked || isRunning || !git.available;
|
|
825
|
+
elements.gitCheckout.disabled = blocked || isRunning || !git.available || !elements.gitCheckoutSelect.value;
|
|
826
|
+
elements.gitFetch.disabled = blocked || isRunning || !git.available;
|
|
827
|
+
elements.gitPull.disabled = blocked || isRunning || !git.available;
|
|
828
|
+
elements.gitStage.disabled = blocked || isRunning || !git.available || selectedStageableGitPaths(git).length === 0;
|
|
829
|
+
elements.gitUnstage.disabled = blocked || isRunning || !git.available || selectedUnstageableGitPaths(git).length === 0;
|
|
830
|
+
elements.gitCommit.disabled = blocked || isRunning || !git.available || elements.gitCommitMessage.value.trim().length === 0;
|
|
831
|
+
elements.gitPush.disabled = blocked || isRunning || !git.available || !git.canPush;
|
|
832
|
+
elements.gitPush.title = git.canPush ? "Push current branch" : (git.pushDisabledReason || "Push is not available.");
|
|
833
|
+
|
|
834
|
+
elements.gitFeedback.className = "git-feedback";
|
|
835
|
+
if (operation.status === "error") {
|
|
836
|
+
elements.gitFeedback.classList.add("error");
|
|
837
|
+
} else if (operation.status === "success") {
|
|
838
|
+
elements.gitFeedback.classList.add("success");
|
|
839
|
+
}
|
|
840
|
+
var feedback = operation.message || git.pushDisabledReason || "";
|
|
841
|
+
var warnings = Array.isArray(git.warnings) && git.warnings.length > 0 ? " " + git.warnings.join(" ") : "";
|
|
842
|
+
elements.gitFeedback.textContent = feedback + warnings;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function strong(value) {
|
|
846
|
+
var element = document.createElement("strong");
|
|
847
|
+
element.textContent = value;
|
|
848
|
+
return element;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function renderAutoFlowEditor(vm) {
|
|
852
|
+
var model = vm && vm.autoFlow;
|
|
853
|
+
if (!elements.autoFlowEditor) return;
|
|
854
|
+
elements.autoFlowEditor.innerHTML = "";
|
|
855
|
+
if (!model || !Array.isArray(model.slots)) {
|
|
856
|
+
elements.autoFlowEditor.hidden = true;
|
|
857
|
+
if (elements.autoFlowResizer) {
|
|
858
|
+
elements.autoFlowResizer.hidden = true;
|
|
859
|
+
}
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
elements.autoFlowEditor.hidden = false;
|
|
863
|
+
if (elements.autoFlowResizer) {
|
|
864
|
+
elements.autoFlowResizer.hidden = false;
|
|
865
|
+
}
|
|
866
|
+
applyAutoFlowHeight(state.autoFlowHeight);
|
|
867
|
+
var blocked = hasBlockingInput(vm);
|
|
868
|
+
|
|
869
|
+
var toolbar = document.createElement("div");
|
|
870
|
+
toolbar.className = "auto-flow-toolbar";
|
|
871
|
+
var simple = document.createElement("button");
|
|
872
|
+
simple.type = "button";
|
|
873
|
+
simple.textContent = "Simple";
|
|
874
|
+
simple.disabled = blocked;
|
|
875
|
+
simple.className = model.basePreset === "simple" ? "primary" : "";
|
|
876
|
+
simple.addEventListener("click", function () {
|
|
877
|
+
api.selectAutoFlowPreset("simple");
|
|
878
|
+
});
|
|
879
|
+
var standard = document.createElement("button");
|
|
880
|
+
standard.type = "button";
|
|
881
|
+
standard.textContent = "Standard";
|
|
882
|
+
standard.disabled = blocked;
|
|
883
|
+
standard.className = model.basePreset === "standard" ? "primary" : "";
|
|
884
|
+
standard.addEventListener("click", function () {
|
|
885
|
+
api.selectAutoFlowPreset("standard");
|
|
886
|
+
});
|
|
887
|
+
var save = document.createElement("button");
|
|
888
|
+
save.type = "button";
|
|
889
|
+
save.textContent = "Save";
|
|
890
|
+
save.disabled = blocked || !model.status || !model.status.canSave;
|
|
891
|
+
save.addEventListener("click", api.saveAutoFlow);
|
|
892
|
+
var reset = document.createElement("button");
|
|
893
|
+
reset.type = "button";
|
|
894
|
+
reset.textContent = "Reset";
|
|
895
|
+
reset.disabled = blocked || !model.status || !model.status.canReset;
|
|
896
|
+
reset.title = "Discard unsaved auto-flow edits";
|
|
897
|
+
reset.addEventListener("click", api.resetAutoFlow);
|
|
898
|
+
var status = document.createElement("span");
|
|
899
|
+
status.className = "auto-flow-status " + (model.status && model.status.valid ? "valid" : "invalid");
|
|
900
|
+
status.textContent = (model.status && model.status.valid ? "valid" : "invalid") + " | " + text(model.status && model.status.sourceLabel, model.configName || "auto-flow");
|
|
901
|
+
toolbar.append(simple, standard, save, reset, status);
|
|
902
|
+
elements.autoFlowEditor.append(toolbar);
|
|
903
|
+
|
|
904
|
+
if (model.status && model.status.lastMessage) {
|
|
905
|
+
var message = document.createElement("div");
|
|
906
|
+
message.className = "auto-flow-message";
|
|
907
|
+
message.textContent = model.status.lastMessage;
|
|
908
|
+
elements.autoFlowEditor.append(message);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (Array.isArray(model.diagnostics) && model.diagnostics.length > 0) {
|
|
912
|
+
var diagnostics = document.createElement("div");
|
|
913
|
+
diagnostics.className = "auto-flow-diagnostics";
|
|
914
|
+
model.diagnostics.forEach(function (diagnostic) {
|
|
915
|
+
var item = document.createElement("div");
|
|
916
|
+
item.textContent = diagnostic.message || "Invalid auto-flow configuration.";
|
|
917
|
+
diagnostics.append(item);
|
|
918
|
+
});
|
|
919
|
+
elements.autoFlowEditor.append(diagnostics);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
var slots = document.createElement("div");
|
|
923
|
+
slots.className = "auto-flow-slots";
|
|
924
|
+
model.slots.forEach(function (slot) {
|
|
925
|
+
var slotRow = document.createElement("section");
|
|
926
|
+
slotRow.className = "auto-flow-slot status-" + slot.status;
|
|
927
|
+
slotRow.dataset.slotId = slot.slotId;
|
|
928
|
+
var title = document.createElement("div");
|
|
929
|
+
title.className = "auto-flow-slot-title";
|
|
930
|
+
title.append(strong(text(slot.title, slot.slotId)), document.createTextNode(" "), statusPill(slot.status));
|
|
931
|
+
var reason = document.createElement("div");
|
|
932
|
+
reason.className = "auto-flow-reason";
|
|
933
|
+
reason.textContent = text(slot.reason, "");
|
|
934
|
+
slotRow.append(title, reason);
|
|
935
|
+
|
|
936
|
+
var blockList = document.createElement("div");
|
|
937
|
+
blockList.className = "auto-flow-blocks";
|
|
938
|
+
if (!Array.isArray(slot.blocks) || slot.blocks.length === 0) {
|
|
939
|
+
var empty = document.createElement("div");
|
|
940
|
+
empty.className = "auto-flow-empty";
|
|
941
|
+
empty.textContent = "Empty slot.";
|
|
942
|
+
blockList.append(empty);
|
|
943
|
+
} else {
|
|
944
|
+
slot.blocks.forEach(function (block) {
|
|
945
|
+
blockList.append(renderAutoFlowBlock(block, blocked));
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
slotRow.append(blockList);
|
|
949
|
+
var insert = renderAutoFlowInsert(slot, model.availableBlocks || [], blocked);
|
|
950
|
+
if (insert) {
|
|
951
|
+
slotRow.append(insert);
|
|
952
|
+
}
|
|
953
|
+
slots.append(slotRow);
|
|
954
|
+
});
|
|
955
|
+
elements.autoFlowEditor.append(slots);
|
|
956
|
+
updateAutoFlowResizerState(state.autoFlowHeight);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function statusPill(status) {
|
|
960
|
+
var pill = document.createElement("span");
|
|
961
|
+
pill.className = "auto-flow-pill status-" + status;
|
|
962
|
+
pill.textContent = status || "pending";
|
|
963
|
+
return pill;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function renderAutoFlowBlock(block, blocked) {
|
|
967
|
+
var row = document.createElement("div");
|
|
968
|
+
row.className = "auto-flow-block status-" + block.status;
|
|
969
|
+
row.dataset.blockId = block.blockId;
|
|
970
|
+
row.dataset.slotId = block.slotId;
|
|
971
|
+
var enabled = document.createElement("input");
|
|
972
|
+
enabled.type = "checkbox";
|
|
973
|
+
enabled.id = controlId("auto-flow-enabled", block.slotId, block.blockId);
|
|
974
|
+
enabled.name = enabled.id;
|
|
975
|
+
enabled.checked = block.enabled !== false;
|
|
976
|
+
enabled.disabled = blocked || block.locked || !(block.actions && (block.actions.canEnable || block.actions.canDisable));
|
|
977
|
+
enabled.title = block.locked ? "Locked core block" : "Enable or disable block";
|
|
978
|
+
enabled.addEventListener("change", function () {
|
|
979
|
+
api.toggleAutoFlowBlock(block.slotId, block.blockId, enabled.checked);
|
|
980
|
+
});
|
|
981
|
+
var main = document.createElement("div");
|
|
982
|
+
main.className = "auto-flow-block-main";
|
|
983
|
+
var label = document.createElement("div");
|
|
984
|
+
label.className = "auto-flow-block-label";
|
|
985
|
+
label.append(strong(text(block.title, block.blockId)), document.createTextNode(" "), statusPill(block.status));
|
|
986
|
+
if (block.locked) {
|
|
987
|
+
var locked = document.createElement("span");
|
|
988
|
+
locked.className = "auto-flow-locked";
|
|
989
|
+
locked.textContent = "locked";
|
|
990
|
+
label.append(document.createTextNode(" "), locked);
|
|
991
|
+
}
|
|
992
|
+
var reason = document.createElement("div");
|
|
993
|
+
reason.className = "auto-flow-reason";
|
|
994
|
+
reason.textContent = text(block.reason, "");
|
|
995
|
+
main.append(label, reason);
|
|
996
|
+
if (Array.isArray(block.params) && block.params.length > 0) {
|
|
997
|
+
var params = document.createElement("div");
|
|
998
|
+
params.className = "auto-flow-params";
|
|
999
|
+
block.params.forEach(function (param) {
|
|
1000
|
+
var field = document.createElement("label");
|
|
1001
|
+
field.textContent = param.label + " ";
|
|
1002
|
+
var input = document.createElement("input");
|
|
1003
|
+
input.type = "number";
|
|
1004
|
+
input.id = controlId("auto-flow-param", block.slotId, block.blockId, param.name);
|
|
1005
|
+
input.name = input.id;
|
|
1006
|
+
input.min = String(param.min);
|
|
1007
|
+
input.max = String(param.max);
|
|
1008
|
+
input.step = "1";
|
|
1009
|
+
input.value = param.value === null || param.value === undefined ? "" : String(param.value);
|
|
1010
|
+
input.disabled = blocked || !(block.actions && block.actions.canEditParams);
|
|
1011
|
+
input.addEventListener("change", function () {
|
|
1012
|
+
var next = Number(input.value);
|
|
1013
|
+
if (Number.isInteger(next)) {
|
|
1014
|
+
api.updateAutoFlowParam(block.slotId, block.blockId, param.name, next);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
field.append(input);
|
|
1018
|
+
params.append(field);
|
|
1019
|
+
});
|
|
1020
|
+
main.append(params);
|
|
1021
|
+
}
|
|
1022
|
+
row.append(enabled, main);
|
|
1023
|
+
if (block.actions && block.actions.canRemove) {
|
|
1024
|
+
var remove = document.createElement("button");
|
|
1025
|
+
remove.type = "button";
|
|
1026
|
+
remove.className = "danger";
|
|
1027
|
+
remove.textContent = "Delete";
|
|
1028
|
+
remove.disabled = blocked;
|
|
1029
|
+
remove.title = "Remove block from this slot";
|
|
1030
|
+
remove.addEventListener("click", function (event) {
|
|
1031
|
+
if (event && typeof event.preventDefault === "function") {
|
|
1032
|
+
event.preventDefault();
|
|
1033
|
+
}
|
|
1034
|
+
api.removeAutoFlowBlock(block.slotId, block.blockId);
|
|
1035
|
+
});
|
|
1036
|
+
row.append(remove);
|
|
1037
|
+
}
|
|
1038
|
+
return row;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function renderAutoFlowInsert(slot, availableBlocks, blocked) {
|
|
1042
|
+
var configured = new Set((Array.isArray(slot.blocks) ? slot.blocks : []).map(function (block) {
|
|
1043
|
+
return block.blockId;
|
|
1044
|
+
}));
|
|
1045
|
+
var candidates = availableBlocks.filter(function (block) {
|
|
1046
|
+
return Array.isArray(block.allowedSlots)
|
|
1047
|
+
&& block.allowedSlots.indexOf(slot.slotId) !== -1
|
|
1048
|
+
&& !configured.has(block.blockId);
|
|
1049
|
+
});
|
|
1050
|
+
if (candidates.length === 0) {
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
var container = document.createElement("div");
|
|
1054
|
+
container.className = "auto-flow-insert";
|
|
1055
|
+
var select = document.createElement("select");
|
|
1056
|
+
select.id = controlId("auto-flow-insert", slot.slotId);
|
|
1057
|
+
select.name = select.id;
|
|
1058
|
+
select.value = "";
|
|
1059
|
+
var placeholder = document.createElement("option");
|
|
1060
|
+
placeholder.value = "";
|
|
1061
|
+
placeholder.textContent = "Add block...";
|
|
1062
|
+
placeholder.disabled = true;
|
|
1063
|
+
placeholder.selected = true;
|
|
1064
|
+
select.append(placeholder);
|
|
1065
|
+
candidates.forEach(function (block) {
|
|
1066
|
+
var option = document.createElement("option");
|
|
1067
|
+
option.value = block.blockId;
|
|
1068
|
+
option.textContent = block.title || block.blockId;
|
|
1069
|
+
select.append(option);
|
|
1070
|
+
});
|
|
1071
|
+
var button = document.createElement("button");
|
|
1072
|
+
button.type = "button";
|
|
1073
|
+
button.textContent = "Insert";
|
|
1074
|
+
button.disabled = blocked || !select.value;
|
|
1075
|
+
select.addEventListener("change", function () {
|
|
1076
|
+
button.disabled = blocked || !select.value;
|
|
1077
|
+
});
|
|
1078
|
+
button.addEventListener("click", function (event) {
|
|
1079
|
+
if (event && typeof event.preventDefault === "function") {
|
|
1080
|
+
event.preventDefault();
|
|
1081
|
+
}
|
|
1082
|
+
if (!select.value) {
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
api.insertAutoFlowBlock(slot.slotId, select.value);
|
|
1086
|
+
});
|
|
1087
|
+
container.append(select, button);
|
|
1088
|
+
return container;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function renderGitBranches(branches, currentBranch) {
|
|
1092
|
+
elements.gitCheckoutSelect.innerHTML = "";
|
|
1093
|
+
branches.forEach(function (branch) {
|
|
1094
|
+
var option = document.createElement("option");
|
|
1095
|
+
option.value = branch.name;
|
|
1096
|
+
option.textContent = branch.current ? branch.name + " (current)" : branch.name;
|
|
1097
|
+
option.selected = branch.name === currentBranch;
|
|
1098
|
+
elements.gitCheckoutSelect.append(option);
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function renderGitFiles(files) {
|
|
1103
|
+
elements.gitFiles.innerHTML = "";
|
|
1104
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
1105
|
+
var empty = document.createElement("div");
|
|
1106
|
+
empty.className = "git-empty";
|
|
1107
|
+
empty.textContent = "No changed files.";
|
|
1108
|
+
elements.gitFiles.append(empty);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
var tree = buildGitFileTree(files);
|
|
1113
|
+
elements.gitFiles.setAttribute("role", "tree");
|
|
1114
|
+
tree.forEach(function (root) {
|
|
1115
|
+
elements.gitFiles.append(renderGitTreeNode(root, 0));
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function buildGitFileTree(files) {
|
|
1120
|
+
var roots = [
|
|
1121
|
+
createGitTreeNode("group", "modified", "modified", "modified"),
|
|
1122
|
+
createGitTreeNode("group", "untracked", "untracked", "untracked"),
|
|
1123
|
+
];
|
|
1124
|
+
var rootByKey = { modified: roots[0], untracked: roots[1] };
|
|
1125
|
+
files.forEach(function (file) {
|
|
1126
|
+
var filePath = file.path || file.file || "";
|
|
1127
|
+
if (!filePath) return;
|
|
1128
|
+
var rootKey = gitRootKey(file);
|
|
1129
|
+
var parent = rootByKey[rootKey] || rootByKey.modified;
|
|
1130
|
+
var parts = filePath.split("/").filter(Boolean);
|
|
1131
|
+
var leafName = parts.pop() || filePath;
|
|
1132
|
+
parts.forEach(function (part) {
|
|
1133
|
+
parent = findOrCreateGitDirectory(parent, part);
|
|
1134
|
+
});
|
|
1135
|
+
var leaf = createGitTreeNode("file", leafName, filePath, rootKey);
|
|
1136
|
+
leaf.file = file;
|
|
1137
|
+
leaf.path = filePath;
|
|
1138
|
+
parent.children.push(leaf);
|
|
1139
|
+
});
|
|
1140
|
+
roots.forEach(sortGitTreeNode);
|
|
1141
|
+
return roots;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function gitRootKey(file) {
|
|
1145
|
+
return file && (file.type === "untracked" || file.xy === "??") ? "untracked" : "modified";
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function createGitTreeNode(kind, name, label, rootKey) {
|
|
1149
|
+
return {
|
|
1150
|
+
kind: kind,
|
|
1151
|
+
name: name,
|
|
1152
|
+
label: label,
|
|
1153
|
+
rootKey: rootKey,
|
|
1154
|
+
children: [],
|
|
1155
|
+
file: null,
|
|
1156
|
+
path: null,
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function findOrCreateGitDirectory(parent, name) {
|
|
1161
|
+
var existing = parent.children.find(function (child) {
|
|
1162
|
+
return child.kind === "dir" && child.name === name;
|
|
1163
|
+
});
|
|
1164
|
+
if (existing) return existing;
|
|
1165
|
+
var directory = createGitTreeNode("dir", name, name, parent.rootKey);
|
|
1166
|
+
parent.children.push(directory);
|
|
1167
|
+
return directory;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function sortGitTreeNode(node) {
|
|
1171
|
+
node.children.sort(function (left, right) {
|
|
1172
|
+
if (left.kind === "dir" && right.kind !== "dir") return -1;
|
|
1173
|
+
if (left.kind !== "dir" && right.kind === "dir") return 1;
|
|
1174
|
+
return left.name.localeCompare(right.name);
|
|
1175
|
+
});
|
|
1176
|
+
node.children.forEach(sortGitTreeNode);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function renderGitTreeNode(node, depth) {
|
|
1180
|
+
var paths = gitTreePaths(node);
|
|
1181
|
+
var selectedCount = paths.filter(function (filePath) {
|
|
1182
|
+
return state.gitSelectedPaths.indexOf(filePath) !== -1;
|
|
1183
|
+
}).length;
|
|
1184
|
+
var checked = paths.length > 0 && selectedCount === paths.length;
|
|
1185
|
+
var indeterminate = selectedCount > 0 && selectedCount < paths.length;
|
|
1186
|
+
var row = document.createElement("label");
|
|
1187
|
+
row.className = node.kind === "file" ? "git-file-row git-tree-row" : "git-tree-row git-tree-" + node.kind;
|
|
1188
|
+
row.style.paddingLeft = String(8 + depth * 18) + "px";
|
|
1189
|
+
row.setAttribute("role", "treeitem");
|
|
1190
|
+
row.setAttribute("aria-level", String(depth + 1));
|
|
1191
|
+
row.setAttribute("aria-checked", indeterminate ? "mixed" : String(checked));
|
|
1192
|
+
if (node.kind === "file" && node.path) {
|
|
1193
|
+
row.dataset.path = node.path;
|
|
1194
|
+
} else if (node.kind === "group") {
|
|
1195
|
+
row.dataset.gitRoot = node.rootKey;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
var checkbox = document.createElement("input");
|
|
1199
|
+
checkbox.type = "checkbox";
|
|
1200
|
+
checkbox.id = controlId("git-select", node.kind, node.rootKey, node.path || node.label || node.name);
|
|
1201
|
+
checkbox.name = checkbox.id;
|
|
1202
|
+
checkbox.checked = checked;
|
|
1203
|
+
checkbox.indeterminate = indeterminate;
|
|
1204
|
+
checkbox.disabled = paths.length === 0;
|
|
1205
|
+
checkbox.addEventListener("change", function () {
|
|
1206
|
+
setGitSelection(paths, checkbox.checked);
|
|
1207
|
+
renderGitWorkspace(state.viewModel || {});
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
var typeLabel = gitTreeTypeLabel(node);
|
|
1211
|
+
var type = null;
|
|
1212
|
+
if (typeLabel) {
|
|
1213
|
+
type = document.createElement("span");
|
|
1214
|
+
type.className = "git-file-type" + (typeLabel === "staged" ? " staged" : "");
|
|
1215
|
+
type.textContent = typeLabel;
|
|
1216
|
+
} else {
|
|
1217
|
+
row.classList.add("without-type");
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
var pathText = document.createElement("span");
|
|
1221
|
+
pathText.className = "git-file-path";
|
|
1222
|
+
pathText.textContent = gitTreeDisplayLabel(node);
|
|
1223
|
+
|
|
1224
|
+
var meta = document.createElement("span");
|
|
1225
|
+
meta.className = "git-file-meta";
|
|
1226
|
+
meta.textContent = node.kind === "file" ? gitFileMeta(node.file) : paths.length + " file" + (paths.length === 1 ? "" : "s");
|
|
1227
|
+
|
|
1228
|
+
row.append(checkbox);
|
|
1229
|
+
if (type) row.append(type);
|
|
1230
|
+
row.append(pathText, meta);
|
|
1231
|
+
if (node.kind === "file" && node.path) {
|
|
1232
|
+
var diffButton = document.createElement("button");
|
|
1233
|
+
diffButton.type = "button";
|
|
1234
|
+
diffButton.className = "git-diff-open";
|
|
1235
|
+
diffButton.textContent = "Diff";
|
|
1236
|
+
diffButton.setAttribute("aria-label", "Open diff for " + node.path);
|
|
1237
|
+
diffButton.addEventListener("click", function (event) {
|
|
1238
|
+
if (event && typeof event.preventDefault === "function") event.preventDefault();
|
|
1239
|
+
if (event && typeof event.stopPropagation === "function") event.stopPropagation();
|
|
1240
|
+
openGitDiff(node.path);
|
|
1241
|
+
});
|
|
1242
|
+
row.append(diffButton);
|
|
1243
|
+
}
|
|
1244
|
+
if (node.kind === "file") {
|
|
1245
|
+
return row;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
var container = document.createElement("div");
|
|
1249
|
+
container.className = "git-tree-node";
|
|
1250
|
+
container.append(row);
|
|
1251
|
+
node.children.forEach(function (child) {
|
|
1252
|
+
container.append(renderGitTreeNode(child, depth + 1));
|
|
1253
|
+
});
|
|
1254
|
+
return container;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function gitTreeTypeLabel(node) {
|
|
1258
|
+
if (node.kind === "group") return node.rootKey;
|
|
1259
|
+
if (node.kind === "dir") return "";
|
|
1260
|
+
if (node.file && isGitFileStaged(node.file) && !needsGitFileStage(node.file)) return "staged";
|
|
1261
|
+
return (node.file && (node.file.type || node.file.xy)) || "changed";
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function gitTreeDisplayLabel(node) {
|
|
1265
|
+
if (node.kind !== "file") return node.label;
|
|
1266
|
+
var file = node.file || {};
|
|
1267
|
+
return file.originalPath || file.originalFile
|
|
1268
|
+
? (file.originalPath || file.originalFile) + " -> " + (node.path || "")
|
|
1269
|
+
: (node.path || node.label);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function gitFileMeta(file) {
|
|
1273
|
+
file = file || {};
|
|
1274
|
+
if (file.type === "untracked" || file.xy === "??") return "untracked";
|
|
1275
|
+
if (isGitFileStaged(file) && needsGitFileStage(file)) return "staged+unstaged";
|
|
1276
|
+
if (isGitFileStaged(file)) return "staged";
|
|
1277
|
+
if (needsGitFileStage(file)) return "unstaged";
|
|
1278
|
+
return (file.xy || "").trim() || "changed";
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function gitTreePaths(node) {
|
|
1282
|
+
if (node.kind === "file") return node.path ? [node.path] : [];
|
|
1283
|
+
return node.children.reduce(function (paths, child) {
|
|
1284
|
+
return paths.concat(gitTreePaths(child));
|
|
1285
|
+
}, []);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function setGitSelection(paths, selected) {
|
|
1289
|
+
if (selected) {
|
|
1290
|
+
paths.forEach(function (filePath) {
|
|
1291
|
+
if (state.gitSelectedPaths.indexOf(filePath) === -1) {
|
|
1292
|
+
state.gitSelectedPaths.push(filePath);
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
state.gitSelectedPaths = state.gitSelectedPaths.filter(function (filePath) {
|
|
1298
|
+
return paths.indexOf(filePath) === -1;
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function selectedGitPaths() {
|
|
1303
|
+
return state.gitSelectedPaths.slice();
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function gitChangedFiles() {
|
|
1307
|
+
var git = gitWorkspace(state.viewModel || {});
|
|
1308
|
+
return Array.isArray(git.changedFiles) ? git.changedFiles : [];
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function gitFilePath(file) {
|
|
1312
|
+
return file && (file.path || file.file) || "";
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function findGitChangedFile(filePath) {
|
|
1316
|
+
return gitChangedFiles().find(function (file) {
|
|
1317
|
+
return gitFilePath(file) === filePath || file.file === filePath;
|
|
1318
|
+
}) || null;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function openGitDiff(filePath) {
|
|
1322
|
+
state.gitDiff.open = true;
|
|
1323
|
+
state.gitDiff.selectedPath = filePath;
|
|
1324
|
+
state.gitDiff.error = null;
|
|
1325
|
+
state.gitDiff.diff = null;
|
|
1326
|
+
fetchGitDiff();
|
|
1327
|
+
renderGitDiffDrawer(state.viewModel || {});
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
function closeGitDiff() {
|
|
1331
|
+
state.gitDiff.open = false;
|
|
1332
|
+
state.gitDiff.requestId += 1;
|
|
1333
|
+
state.gitDiff.loading = false;
|
|
1334
|
+
renderGitDiffDrawer(state.viewModel || {});
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function setGitDiffMode(mode) {
|
|
1338
|
+
if (state.gitDiff.mode === mode) {
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
state.gitDiff.mode = mode;
|
|
1342
|
+
state.gitDiff.error = null;
|
|
1343
|
+
state.gitDiff.diff = null;
|
|
1344
|
+
if (state.gitDiff.open && state.gitDiff.selectedPath) {
|
|
1345
|
+
fetchGitDiff();
|
|
1346
|
+
}
|
|
1347
|
+
renderGitDiffDrawer(state.viewModel || {});
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
function gitDiffApiUrl(filePath, mode) {
|
|
1351
|
+
var params = new URLSearchParams();
|
|
1352
|
+
params.set("path", filePath);
|
|
1353
|
+
params.set("mode", mode);
|
|
1354
|
+
return "/__agentweaver/api/git/diff?" + params.toString();
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function fetchGitDiff() {
|
|
1358
|
+
var filePath = state.gitDiff.selectedPath;
|
|
1359
|
+
if (!filePath) {
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
var requestId = state.gitDiff.requestId + 1;
|
|
1363
|
+
state.gitDiff.requestId = requestId;
|
|
1364
|
+
state.gitDiff.loading = true;
|
|
1365
|
+
state.gitDiff.error = null;
|
|
1366
|
+
state.gitDiff.diff = null;
|
|
1367
|
+
fetch(gitDiffApiUrl(filePath, state.gitDiff.mode))
|
|
1368
|
+
.then(function (response) {
|
|
1369
|
+
return response.json().then(function (body) {
|
|
1370
|
+
if (!response.ok) {
|
|
1371
|
+
throw new Error(body && body.message ? body.message : "Git diff request failed.");
|
|
1372
|
+
}
|
|
1373
|
+
return body;
|
|
1374
|
+
});
|
|
1375
|
+
})
|
|
1376
|
+
.then(function (diff) {
|
|
1377
|
+
if (requestId !== state.gitDiff.requestId) {
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
state.gitDiff.loading = false;
|
|
1381
|
+
state.gitDiff.diff = diff;
|
|
1382
|
+
state.gitDiff.error = null;
|
|
1383
|
+
renderGitDiffDrawer(state.viewModel || {});
|
|
1384
|
+
})
|
|
1385
|
+
.catch(function (error) {
|
|
1386
|
+
if (requestId !== state.gitDiff.requestId) {
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
state.gitDiff.loading = false;
|
|
1390
|
+
state.gitDiff.diff = null;
|
|
1391
|
+
state.gitDiff.error = error.message || "Git diff request failed.";
|
|
1392
|
+
renderGitDiffDrawer(state.viewModel || {});
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function renderGitDiffDrawer(vm) {
|
|
1397
|
+
var files = gitChangedFiles();
|
|
1398
|
+
var selectedPath = state.gitDiff.selectedPath;
|
|
1399
|
+
var selectedPathChanged = false;
|
|
1400
|
+
if (selectedPath && !findGitChangedFile(selectedPath)) {
|
|
1401
|
+
selectedPath = files[0] ? gitFilePath(files[0]) : null;
|
|
1402
|
+
state.gitDiff.selectedPath = selectedPath;
|
|
1403
|
+
state.gitDiff.diff = null;
|
|
1404
|
+
state.gitDiff.error = null;
|
|
1405
|
+
state.gitDiff.loading = false;
|
|
1406
|
+
selectedPathChanged = Boolean(selectedPath);
|
|
1407
|
+
}
|
|
1408
|
+
elements.gitDiffDrawer.hidden = !state.gitDiff.open;
|
|
1409
|
+
elements.gitDiffTitle.textContent = "Git Diff Viewer";
|
|
1410
|
+
elements.gitDiffMeta.textContent = files.length === 0
|
|
1411
|
+
? "No changed files are available."
|
|
1412
|
+
: String(files.length) + " changed file" + (files.length === 1 ? "" : "s") + ".";
|
|
1413
|
+
if (!state.gitDiff.open) {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
if (selectedPathChanged) {
|
|
1417
|
+
fetchGitDiff();
|
|
1418
|
+
}
|
|
1419
|
+
renderGitDiffFileList(files);
|
|
1420
|
+
renderGitDiffModeControls();
|
|
1421
|
+
renderGitDiffPreview(vm);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function renderGitDiffFileList(files) {
|
|
1425
|
+
elements.gitDiffFileList.innerHTML = "";
|
|
1426
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
1427
|
+
var empty = document.createElement("div");
|
|
1428
|
+
empty.className = "git-diff-empty";
|
|
1429
|
+
empty.textContent = "No changed files.";
|
|
1430
|
+
elements.gitDiffFileList.append(empty);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
files.forEach(function (file) {
|
|
1434
|
+
var filePath = gitFilePath(file);
|
|
1435
|
+
var button = document.createElement("button");
|
|
1436
|
+
button.type = "button";
|
|
1437
|
+
button.className = "git-diff-file" + (filePath === state.gitDiff.selectedPath ? " selected" : "");
|
|
1438
|
+
button.dataset.path = filePath;
|
|
1439
|
+
button.setAttribute("aria-pressed", filePath === state.gitDiff.selectedPath ? "true" : "false");
|
|
1440
|
+
button.addEventListener("click", function () {
|
|
1441
|
+
if (state.gitDiff.selectedPath !== filePath) {
|
|
1442
|
+
openGitDiff(filePath);
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
var title = document.createElement("strong");
|
|
1446
|
+
title.textContent = file.originalPath || file.originalFile
|
|
1447
|
+
? (file.originalPath || file.originalFile) + " -> " + filePath
|
|
1448
|
+
: filePath;
|
|
1449
|
+
var meta = document.createElement("span");
|
|
1450
|
+
meta.textContent = gitFileMeta(file);
|
|
1451
|
+
button.append(title, meta);
|
|
1452
|
+
elements.gitDiffFileList.append(button);
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function renderGitDiffModeControls() {
|
|
1457
|
+
ensureGitDiffModeButtons();
|
|
1458
|
+
Array.prototype.slice.call(elements.gitDiffModeControls.querySelectorAll("button")).forEach(function (button) {
|
|
1459
|
+
var mode = button.dataset.gitDiffMode || "head";
|
|
1460
|
+
var active = state.gitDiff.mode === mode;
|
|
1461
|
+
button.className = active ? "primary" : "";
|
|
1462
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function ensureGitDiffModeButtons() {
|
|
1467
|
+
if (elements.gitDiffModeControls.querySelectorAll("button").length > 0) {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
[
|
|
1471
|
+
["head", "All"],
|
|
1472
|
+
["staged", "Staged"],
|
|
1473
|
+
["worktree", "Unstaged"],
|
|
1474
|
+
].forEach(function (entry) {
|
|
1475
|
+
var button = document.createElement("button");
|
|
1476
|
+
button.type = "button";
|
|
1477
|
+
button.dataset.gitDiffMode = entry[0];
|
|
1478
|
+
button.textContent = entry[1];
|
|
1479
|
+
elements.gitDiffModeControls.append(button);
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function renderGitDiffPreview(vm) {
|
|
1484
|
+
var selected = state.gitDiff.selectedPath ? findGitChangedFile(state.gitDiff.selectedPath) : null;
|
|
1485
|
+
if (!selected) {
|
|
1486
|
+
elements.gitDiffSelectedTitle.textContent = "Diff";
|
|
1487
|
+
elements.gitDiffSelectedMeta.textContent = "No file selected.";
|
|
1488
|
+
elements.gitDiffStatus.textContent = "";
|
|
1489
|
+
renderGitDiffMessage("Select a changed file to inspect its diff.", false);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
var displayPath = selected.originalPath || selected.originalFile
|
|
1493
|
+
? (selected.originalPath || selected.originalFile) + " -> " + gitFilePath(selected)
|
|
1494
|
+
: gitFilePath(selected);
|
|
1495
|
+
elements.gitDiffSelectedTitle.textContent = displayPath;
|
|
1496
|
+
elements.gitDiffSelectedMeta.textContent = [gitFileMeta(selected), gitModeLabel(state.gitDiff.mode)].join(" | ");
|
|
1497
|
+
if (state.gitDiff.loading) {
|
|
1498
|
+
elements.gitDiffStatus.textContent = "Loading diff...";
|
|
1499
|
+
renderGitDiffMessage("Loading diff...", false);
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
if (state.gitDiff.error) {
|
|
1503
|
+
elements.gitDiffStatus.textContent = state.gitDiff.error;
|
|
1504
|
+
renderGitDiffMessage(state.gitDiff.error, true);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
var diff = state.gitDiff.diff;
|
|
1508
|
+
if (!diff) {
|
|
1509
|
+
elements.gitDiffStatus.textContent = "Diff has not been loaded yet.";
|
|
1510
|
+
renderGitDiffMessage("Diff has not been loaded yet.", false);
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
elements.gitDiffStatus.textContent = diff.message || "";
|
|
1514
|
+
renderGitDiffContent(diff);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function gitModeLabel(mode) {
|
|
1518
|
+
if (mode === "staged") return "Staged";
|
|
1519
|
+
if (mode === "worktree") return "Unstaged";
|
|
1520
|
+
return "All";
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
function renderGitDiffMessage(message, failed) {
|
|
1524
|
+
elements.gitDiffBody.textContent = "";
|
|
1525
|
+
elements.gitDiffBody.className = "git-diff-body" + (failed ? " error-text" : "");
|
|
1526
|
+
elements.gitDiffBody.textContent = message;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
function renderGitDiffContent(diff) {
|
|
1530
|
+
elements.gitDiffBody.textContent = "";
|
|
1531
|
+
elements.gitDiffBody.className = "git-diff-body";
|
|
1532
|
+
if (diff.binary) {
|
|
1533
|
+
renderGitDiffMessage(diff.message || "Binary file diff is not displayed.", false);
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
if (diff.tooLarge) {
|
|
1537
|
+
renderGitDiffMessage(diff.message || "Diff is too large to display.", false);
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (diff.empty || !Array.isArray(diff.hunks) || diff.hunks.length === 0) {
|
|
1541
|
+
renderGitDiffMessage(diff.message || "No diff is available for this mode.", false);
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
var table = document.createElement("div");
|
|
1545
|
+
table.className = "git-diff-table";
|
|
1546
|
+
diff.hunks.forEach(function (hunk) {
|
|
1547
|
+
var hunkRow = document.createElement("div");
|
|
1548
|
+
hunkRow.className = "git-diff-hunk";
|
|
1549
|
+
hunkRow.textContent = hunk.header || "@@";
|
|
1550
|
+
table.append(hunkRow);
|
|
1551
|
+
(Array.isArray(hunk.rows) ? hunk.rows : []).forEach(function (row) {
|
|
1552
|
+
table.append(renderGitDiffRow(row));
|
|
1553
|
+
});
|
|
1554
|
+
});
|
|
1555
|
+
elements.gitDiffBody.append(table);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function renderGitDiffRow(row) {
|
|
1559
|
+
var element = document.createElement("div");
|
|
1560
|
+
var kind = row && row.kind ? row.kind : "context";
|
|
1561
|
+
element.className = "git-diff-row row-" + kind;
|
|
1562
|
+
var leftNumber = document.createElement("span");
|
|
1563
|
+
leftNumber.className = "git-diff-line-number";
|
|
1564
|
+
leftNumber.textContent = row.leftLineNumber === null || row.leftLineNumber === undefined ? "" : String(row.leftLineNumber);
|
|
1565
|
+
var leftText = document.createElement("code");
|
|
1566
|
+
leftText.className = "git-diff-code left";
|
|
1567
|
+
leftText.textContent = row.leftText || "";
|
|
1568
|
+
var rightNumber = document.createElement("span");
|
|
1569
|
+
rightNumber.className = "git-diff-line-number";
|
|
1570
|
+
rightNumber.textContent = row.rightLineNumber === null || row.rightLineNumber === undefined ? "" : String(row.rightLineNumber);
|
|
1571
|
+
var rightText = document.createElement("code");
|
|
1572
|
+
rightText.className = "git-diff-code right";
|
|
1573
|
+
rightText.textContent = row.rightText || "";
|
|
1574
|
+
element.append(leftNumber, leftText, rightNumber, rightText);
|
|
1575
|
+
return element;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
function selectedStageableGitPaths(git) {
|
|
1579
|
+
return selectedGitPathsFor(git, needsGitFileStage);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
function selectedUnstageableGitPaths(git) {
|
|
1583
|
+
return selectedGitPathsFor(git, isGitFileStaged);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
function selectedGitPathsFor(git, predicate) {
|
|
1587
|
+
git = git || gitWorkspace(state.viewModel || {});
|
|
1588
|
+
var selected = selectedGitPaths();
|
|
1589
|
+
var selectedSet = new Set(selected);
|
|
1590
|
+
var files = Array.isArray(git.changedFiles) ? git.changedFiles : [];
|
|
1591
|
+
return files.filter(function (file) {
|
|
1592
|
+
var filePath = file.path || file.file || "";
|
|
1593
|
+
return selectedSet.has(filePath) && predicate(file);
|
|
1594
|
+
}).map(function (file) {
|
|
1595
|
+
return file.path || file.file || "";
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
function needsGitFileStage(file) {
|
|
1600
|
+
if (!file) return false;
|
|
1601
|
+
if (file.type === "untracked" || file.xy === "??") return true;
|
|
1602
|
+
var workTreeStatus = typeof file.workTreeStatus === "string" ? file.workTreeStatus : (file.xy || " ")[1];
|
|
1603
|
+
return workTreeStatus !== " " && workTreeStatus !== undefined;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function isGitFileStaged(file) {
|
|
1607
|
+
if (!file) return false;
|
|
1608
|
+
var indexStatus = typeof file.indexStatus === "string" ? file.indexStatus : (file.xy || " ")[0];
|
|
1609
|
+
return indexStatus !== " " && indexStatus !== "?";
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function renderProgress(vm) {
|
|
1613
|
+
var progress = vm && vm.progress;
|
|
1614
|
+
var items = progress && Array.isArray(progress.items) ? progress.items.filter(isProgressItem) : [];
|
|
1615
|
+
renderProgressFlowLabel(progress && progress.flow ? text(progress.flow.label, progress.flow.id || "") : "");
|
|
1616
|
+
if (!progress || !progress.flow || items.length === 0) {
|
|
1617
|
+
var fallbackText = text(vm && vm.progressText, "No progress yet.");
|
|
1618
|
+
elements.progress.className = "progress-tree fallback";
|
|
1619
|
+
if (elements.progress.children.length > 0) {
|
|
1620
|
+
var previousFallbackScrollTop = elements.progress.scrollTop;
|
|
1621
|
+
var fallbackWasPinned = previousFallbackScrollTop + elements.progress.clientHeight >= elements.progress.scrollHeight - 8;
|
|
1622
|
+
elements.progress.textContent = fallbackText;
|
|
1623
|
+
elements.progress.scrollTop = fallbackWasPinned ? elements.progress.scrollHeight : previousFallbackScrollTop;
|
|
1624
|
+
} else {
|
|
1625
|
+
setTextPreservingScroll(elements.progress, fallbackText);
|
|
1626
|
+
}
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
var previousScrollTop = elements.progress.scrollTop;
|
|
1631
|
+
var wasPinned = previousScrollTop + elements.progress.clientHeight >= elements.progress.scrollHeight - 8;
|
|
1632
|
+
elements.progress.className = "progress-tree";
|
|
1633
|
+
elements.progress.innerHTML = "";
|
|
1634
|
+
|
|
1635
|
+
items.forEach(function (item, index) {
|
|
1636
|
+
elements.progress.append(renderProgressRow(item, index));
|
|
1637
|
+
});
|
|
1638
|
+
elements.progress.scrollTop = wasPinned ? elements.progress.scrollHeight : previousScrollTop;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
function renderProgressFlowLabel(label) {
|
|
1642
|
+
if (!elements.progressFlowLabel) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
var value = text(label, "");
|
|
1646
|
+
elements.progressFlowLabel.textContent = value;
|
|
1647
|
+
elements.progressFlowLabel.hidden = value.length === 0;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function isProgressItem(item) {
|
|
1651
|
+
if (!item || typeof item !== "object") return false;
|
|
1652
|
+
if (["group", "phase", "step", "slot", "block", "termination"].indexOf(item.kind) === -1) return false;
|
|
1653
|
+
if (["pending", "running", "done", "success", "failed", "stopped", "skipped", "waiting-user", "disabled", "blocked", "invalid", "empty"].indexOf(item.status) === -1) return false;
|
|
1654
|
+
return typeof item.label === "string" && Number.isFinite(item.depth);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function renderProgressRow(item, index) {
|
|
1658
|
+
var depth = Math.max(0, Math.min(12, Math.floor(item.depth)));
|
|
1659
|
+
var row = document.createElement("div");
|
|
1660
|
+
row.className = "progress-row kind-" + item.kind + " status-" + item.status;
|
|
1661
|
+
row.dataset.kind = item.kind;
|
|
1662
|
+
row.dataset.status = item.status;
|
|
1663
|
+
row.dataset.depth = String(depth);
|
|
1664
|
+
row.setAttribute("role", "treeitem");
|
|
1665
|
+
row.setAttribute("aria-level", String(depth + 1));
|
|
1666
|
+
row.setAttribute("aria-current", item.status === "running" ? "step" : "false");
|
|
1667
|
+
row.style.paddingLeft = String(8 + depth * 18) + "px";
|
|
1668
|
+
row.title = item.kind + ": " + item.status;
|
|
1669
|
+
|
|
1670
|
+
var marker = document.createElement("span");
|
|
1671
|
+
marker.className = "progress-marker";
|
|
1672
|
+
marker.setAttribute("aria-hidden", "true");
|
|
1673
|
+
marker.textContent = progressMarker(item.status);
|
|
1674
|
+
|
|
1675
|
+
var body = document.createElement("span");
|
|
1676
|
+
body.className = "progress-row-body";
|
|
1677
|
+
|
|
1678
|
+
var label = document.createElement("span");
|
|
1679
|
+
label.className = "progress-label";
|
|
1680
|
+
label.textContent = text(item.label, "Untitled progress item");
|
|
1681
|
+
body.append(label);
|
|
1682
|
+
|
|
1683
|
+
if (typeof item.detail === "string" && item.detail.length > 0) {
|
|
1684
|
+
var detail = document.createElement("span");
|
|
1685
|
+
detail.className = "progress-detail";
|
|
1686
|
+
detail.textContent = item.detail;
|
|
1687
|
+
body.append(detail);
|
|
186
1688
|
}
|
|
187
|
-
}
|
|
188
1689
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
elements.status.textContent = text(vm.statusText, "Idle");
|
|
194
|
-
elements.flowsTitle.textContent = text(vm.flowListTitle, "Flows");
|
|
195
|
-
elements.description.textContent = text(vm.descriptionText, "No flow selected.");
|
|
196
|
-
elements.progressTitle.textContent = text(vm.progressTitle, "Progress");
|
|
197
|
-
setTextPreservingScroll(elements.progress, text(vm.progressText, "No progress yet."));
|
|
198
|
-
elements.summaryTitle.textContent = text(vm.summaryTitle, "Task Summary");
|
|
199
|
-
setTextPreservingScroll(elements.summary, vm.summaryVisible === false ? "Summary is hidden." : text(vm.summaryText, "No task summary yet."));
|
|
200
|
-
elements.logTitle.textContent = text(vm.logTitle, "Activity");
|
|
201
|
-
setTextPreservingScroll(elements.log, text(vm.logText, ""));
|
|
202
|
-
elements.helpText.textContent = text(vm.helpText, "No help is available.");
|
|
203
|
-
elements.helpPanel.hidden = !vm.helpVisible;
|
|
204
|
-
elements.help.setAttribute("aria-pressed", vm.helpVisible ? "true" : "false");
|
|
1690
|
+
row.append(marker, body);
|
|
1691
|
+
row.dataset.index = String(index);
|
|
1692
|
+
return row;
|
|
1693
|
+
}
|
|
205
1694
|
|
|
206
|
-
|
|
207
|
-
|
|
1695
|
+
function progressMarker(status) {
|
|
1696
|
+
if (status === "done" || status === "success") return "✓";
|
|
1697
|
+
if (status === "failed" || status === "invalid") return "×";
|
|
1698
|
+
if (status === "stopped" || status === "blocked") return "■";
|
|
1699
|
+
if (status === "running" || status === "waiting-user") return "●";
|
|
1700
|
+
if (status === "skipped") return "↷";
|
|
1701
|
+
if (status === "disabled" || status === "empty") return "·";
|
|
1702
|
+
return "○";
|
|
208
1703
|
}
|
|
209
1704
|
|
|
210
|
-
function setTextPreservingScroll(element, value) {
|
|
1705
|
+
function setTextPreservingScroll(element, value, forceBottom) {
|
|
211
1706
|
if (element.textContent === value) {
|
|
1707
|
+
if (forceBottom) {
|
|
1708
|
+
element.scrollTop = element.scrollHeight;
|
|
1709
|
+
}
|
|
212
1710
|
return;
|
|
213
1711
|
}
|
|
214
1712
|
var previousScrollTop = element.scrollTop;
|
|
215
1713
|
var wasPinned = previousScrollTop + element.clientHeight >= element.scrollHeight - 8;
|
|
216
1714
|
element.textContent = value;
|
|
217
|
-
element.scrollTop = wasPinned ? element.scrollHeight : previousScrollTop;
|
|
1715
|
+
element.scrollTop = forceBottom || wasPinned ? element.scrollHeight : previousScrollTop;
|
|
218
1716
|
}
|
|
219
1717
|
|
|
220
1718
|
function captureUiState() {
|
|
@@ -230,7 +1728,6 @@
|
|
|
230
1728
|
}
|
|
231
1729
|
return {
|
|
232
1730
|
progressScrollTop: elements.progress.scrollTop,
|
|
233
|
-
summaryScrollTop: elements.summary.scrollTop,
|
|
234
1731
|
logScrollTop: elements.log.scrollTop,
|
|
235
1732
|
helpScrollTop: elements.helpText.scrollTop,
|
|
236
1733
|
modalScrollTop: elements.modalRoot.scrollTop,
|
|
@@ -246,8 +1743,7 @@
|
|
|
246
1743
|
function restoreUiState(uiState) {
|
|
247
1744
|
if (!uiState) return;
|
|
248
1745
|
elements.progress.scrollTop = uiState.progressScrollTop;
|
|
249
|
-
elements.
|
|
250
|
-
elements.log.scrollTop = uiState.logScrollTop;
|
|
1746
|
+
elements.log.scrollTop = state.logAutoscroll ? elements.log.scrollHeight : uiState.logScrollTop;
|
|
251
1747
|
elements.helpText.scrollTop = uiState.helpScrollTop;
|
|
252
1748
|
elements.modalRoot.scrollTop = uiState.modalScrollTop;
|
|
253
1749
|
var modalBody = currentModalBody();
|
|
@@ -335,6 +1831,877 @@
|
|
|
335
1831
|
});
|
|
336
1832
|
}
|
|
337
1833
|
|
|
1834
|
+
function artifactState(vm) {
|
|
1835
|
+
var explorer = vm && vm.artifactExplorer;
|
|
1836
|
+
if (!explorer || typeof explorer !== "object") {
|
|
1837
|
+
return {
|
|
1838
|
+
available: false,
|
|
1839
|
+
open: false,
|
|
1840
|
+
scopeKey: null,
|
|
1841
|
+
runId: null,
|
|
1842
|
+
status: "unavailable",
|
|
1843
|
+
label: "Artifact Explorer",
|
|
1844
|
+
message: "",
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
return explorer;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
function hasBlockingInput(vm) {
|
|
1851
|
+
return Boolean(vm && (vm.confirmation || vm.confirmText || vm.form));
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
function artifactSignature(explorer) {
|
|
1855
|
+
return [
|
|
1856
|
+
explorer.scopeKey || "",
|
|
1857
|
+
explorer.runId || "",
|
|
1858
|
+
Array.isArray(explorer.runIds) ? explorer.runIds.join(",") : "",
|
|
1859
|
+
explorer.status || "",
|
|
1860
|
+
typeof explorer.artifactCount === "number" ? String(explorer.artifactCount) : "",
|
|
1861
|
+
].join("|");
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function renderArtifactExplorer(vm) {
|
|
1865
|
+
var explorer = artifactState(vm);
|
|
1866
|
+
var blocked = hasBlockingInput(vm);
|
|
1867
|
+
elements.artifactOpen.hidden = !explorer.available || explorer.open || blocked;
|
|
1868
|
+
elements.artifactOpen.textContent = explorer.label || "Artifacts";
|
|
1869
|
+
elements.artifactDrawer.hidden = !explorer.open;
|
|
1870
|
+
elements.artifactTitle.textContent = explorer.label || "Artifact Explorer";
|
|
1871
|
+
elements.artifactMeta.textContent = artifactMetaText(explorer);
|
|
1872
|
+
elements.artifactMessage.textContent = explorer.message || "";
|
|
1873
|
+
|
|
1874
|
+
if (!explorer.open) {
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
var signature = artifactSignature(explorer);
|
|
1879
|
+
if (signature !== state.artifacts.signature) {
|
|
1880
|
+
state.artifacts.signature = signature;
|
|
1881
|
+
state.artifacts.loading = false;
|
|
1882
|
+
state.artifacts.catalog = null;
|
|
1883
|
+
state.artifacts.error = null;
|
|
1884
|
+
state.artifacts.preview = null;
|
|
1885
|
+
state.artifacts.selectedId = null;
|
|
1886
|
+
state.artifacts.actionStatus = null;
|
|
1887
|
+
state.artifacts.actionStatusFailed = false;
|
|
1888
|
+
state.artifacts.previewRequestId += 1;
|
|
1889
|
+
state.artifacts.viewerModes = {};
|
|
1890
|
+
fetchArtifacts(explorer);
|
|
1891
|
+
}
|
|
1892
|
+
renderArtifactList(explorer);
|
|
1893
|
+
renderArtifactPreview(explorer);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
function artifactMetaText(explorer) {
|
|
1897
|
+
var parts = [];
|
|
1898
|
+
var count = typeof explorer.artifactCount === "number" ? explorer.artifactCount : null;
|
|
1899
|
+
if (explorer.status === "completed") {
|
|
1900
|
+
parts.push("Workflow completed. " + artifactCountText(count));
|
|
1901
|
+
} else if (explorer.status === "failed") {
|
|
1902
|
+
parts.push("Workflow failed. " + artifactCountText(count));
|
|
1903
|
+
} else if (count !== null) {
|
|
1904
|
+
parts.push(artifactCountText(count));
|
|
1905
|
+
}
|
|
1906
|
+
if (explorer.scopeKey) {
|
|
1907
|
+
parts.push("Scope " + explorer.scopeKey);
|
|
1908
|
+
}
|
|
1909
|
+
if (Array.isArray(explorer.runIds) && explorer.runIds.length > 1) {
|
|
1910
|
+
parts.push("Current runs " + explorer.runIds.join(", "));
|
|
1911
|
+
} else if (explorer.runId) {
|
|
1912
|
+
parts.push("Current run " + explorer.runId);
|
|
1913
|
+
}
|
|
1914
|
+
return parts.join(" | ");
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
function artifactCountText(count) {
|
|
1918
|
+
if (typeof count !== "number") {
|
|
1919
|
+
return "Artifacts are available.";
|
|
1920
|
+
}
|
|
1921
|
+
return String(count) + " artifact" + (count === 1 ? "" : "s") + " in scope.";
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
function artifactApiUrl(explorer, suffix) {
|
|
1925
|
+
var base = "/__agentweaver/api/artifacts" + (suffix || "");
|
|
1926
|
+
var params = new URLSearchParams();
|
|
1927
|
+
if (explorer.scopeKey) {
|
|
1928
|
+
params.set("scope", explorer.scopeKey);
|
|
1929
|
+
}
|
|
1930
|
+
var query = params.toString();
|
|
1931
|
+
return query ? base + "?" + query : base;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function artifactGroups(catalog) {
|
|
1935
|
+
if (catalog && Array.isArray(catalog.groups) && catalog.groups.length > 0) {
|
|
1936
|
+
return catalog.groups.map(function (group) {
|
|
1937
|
+
return {
|
|
1938
|
+
title: group.title || group.phaseId || "Artifacts",
|
|
1939
|
+
items: Array.isArray(group.items) ? group.items : [],
|
|
1940
|
+
groups: Array.isArray(group.groups) ? artifactGroups({ groups: group.groups }) : [],
|
|
1941
|
+
};
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
return [{
|
|
1945
|
+
title: "Artifacts",
|
|
1946
|
+
items: catalog && Array.isArray(catalog.items) ? catalog.items : [],
|
|
1947
|
+
}];
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
function flattenArtifacts(catalog) {
|
|
1951
|
+
function flattenGroup(group) {
|
|
1952
|
+
var nested = Array.isArray(group.groups) ? group.groups.reduce(function (items, child) {
|
|
1953
|
+
return items.concat(flattenGroup(child));
|
|
1954
|
+
}, []) : [];
|
|
1955
|
+
var ownItems = Array.isArray(group.items) ? group.items : [];
|
|
1956
|
+
if (nested.length > 0) {
|
|
1957
|
+
var nestedIds = new Set(nested.map(function (item) {
|
|
1958
|
+
return item && item.id;
|
|
1959
|
+
}));
|
|
1960
|
+
ownItems = ownItems.filter(function (item) {
|
|
1961
|
+
return item && !nestedIds.has(item.id);
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
return nested.concat(ownItems);
|
|
1965
|
+
}
|
|
1966
|
+
return artifactGroups(catalog).reduce(function (items, group) {
|
|
1967
|
+
return items.concat(flattenGroup(group));
|
|
1968
|
+
}, []);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
function selectedArtifact() {
|
|
1972
|
+
var items = state.artifacts.catalog ? flattenArtifacts(state.artifacts.catalog) : [];
|
|
1973
|
+
return items.find(function (item) {
|
|
1974
|
+
return item && item.id === state.artifacts.selectedId;
|
|
1975
|
+
}) || null;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
function artifactReference(item) {
|
|
1979
|
+
return item && (item.relativePath || item.logicalKey || item.id) || "";
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
function artifactDisplayTitle(item) {
|
|
1983
|
+
return item && (item.title || item.logicalKey || item.relativePath || item.id) || "Untitled artifact";
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
function formatArtifactBytes(value) {
|
|
1987
|
+
if (!Number.isFinite(value)) {
|
|
1988
|
+
return "Unknown size";
|
|
1989
|
+
}
|
|
1990
|
+
var bytes = Math.max(0, value);
|
|
1991
|
+
if (bytes < 1024) {
|
|
1992
|
+
return String(bytes) + " B";
|
|
1993
|
+
}
|
|
1994
|
+
var units = ["KB", "MB", "GB"];
|
|
1995
|
+
var size = bytes / 1024;
|
|
1996
|
+
var index = 0;
|
|
1997
|
+
while (size >= 1024 && index < units.length - 1) {
|
|
1998
|
+
size = size / 1024;
|
|
1999
|
+
index += 1;
|
|
2000
|
+
}
|
|
2001
|
+
return (size >= 10 ? size.toFixed(0) : size.toFixed(1)).replace(/\.0$/, "") + " " + units[index];
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
function artifactKind(item) {
|
|
2005
|
+
return item && item.kind ? String(item.kind) : "unknown";
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
function currentArtifactRunIds(explorer) {
|
|
2009
|
+
if (explorer && Array.isArray(explorer.runIds) && explorer.runIds.length > 0) {
|
|
2010
|
+
return explorer.runIds.filter(Boolean);
|
|
2011
|
+
}
|
|
2012
|
+
return explorer && explorer.runId ? [explorer.runId] : [];
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function chooseDefaultArtifact(items, explorer) {
|
|
2016
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
2017
|
+
return null;
|
|
2018
|
+
}
|
|
2019
|
+
var currentRunIds = new Set(currentArtifactRunIds(explorer));
|
|
2020
|
+
var best = null;
|
|
2021
|
+
var bestScore = Infinity;
|
|
2022
|
+
items.forEach(function (item, index) {
|
|
2023
|
+
var runScore = currentRunIds.size > 0 && item && currentRunIds.has(item.runId) ? 0 : 1;
|
|
2024
|
+
var score = runScore * 100000 + artifactUsefulnessScore(item) * 1000 + index;
|
|
2025
|
+
if (score < bestScore) {
|
|
2026
|
+
best = item;
|
|
2027
|
+
bestScore = score;
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
return best;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
function artifactUsefulnessScore(item) {
|
|
2034
|
+
var role = String(item && item.role || "").toLowerCase();
|
|
2035
|
+
var haystack = [
|
|
2036
|
+
item && item.logicalKey,
|
|
2037
|
+
item && item.relativePath,
|
|
2038
|
+
item && item.title,
|
|
2039
|
+
item && item.id,
|
|
2040
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
2041
|
+
if (role.indexOf("design") !== -1 || /\bdesign\b/.test(haystack)) return 0;
|
|
2042
|
+
if (role.indexOf("plan") !== -1 || /\bplan\b/.test(haystack)) return 1;
|
|
2043
|
+
if (role.indexOf("review") !== -1 || /\breview\b/.test(haystack)) return 2;
|
|
2044
|
+
if (role === "qa" || role.indexOf("qa") !== -1 || /\bqa\b/.test(haystack)) return 3;
|
|
2045
|
+
if (role.indexOf("ready-to-merge") !== -1 || haystack.indexOf("ready-to-merge") !== -1) return 4;
|
|
2046
|
+
if (isDiagnosticArtifact(role, haystack)) return 100;
|
|
2047
|
+
return 50;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
function isDiagnosticArtifact(role, haystack) {
|
|
2051
|
+
return role.indexOf("diagnostic") !== -1
|
|
2052
|
+
|| role.indexOf("internal") !== -1
|
|
2053
|
+
|| /\b(log|trace|debug|diagnostic|diagnostics|internal)\b/.test(haystack)
|
|
2054
|
+
|| haystack.indexOf("manifest-history") !== -1
|
|
2055
|
+
|| haystack.indexOf("restart-archives") !== -1
|
|
2056
|
+
|| haystack.indexOf("artifact-index") !== -1;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
function fetchArtifacts(explorer) {
|
|
2060
|
+
if (!explorer.scopeKey) {
|
|
2061
|
+
state.artifacts.error = "Artifact scope is not available.";
|
|
2062
|
+
renderArtifactList(explorer);
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
state.artifacts.loading = true;
|
|
2066
|
+
renderArtifactList(explorer);
|
|
2067
|
+
fetch(artifactApiUrl(explorer))
|
|
2068
|
+
.then(function (response) {
|
|
2069
|
+
return response.json().then(function (body) {
|
|
2070
|
+
if (!response.ok) {
|
|
2071
|
+
throw new Error(body && body.message ? body.message : "Artifact catalog request failed.");
|
|
2072
|
+
}
|
|
2073
|
+
return body;
|
|
2074
|
+
});
|
|
2075
|
+
})
|
|
2076
|
+
.then(function (catalog) {
|
|
2077
|
+
state.artifacts.catalog = catalog;
|
|
2078
|
+
state.artifacts.error = null;
|
|
2079
|
+
state.artifacts.loading = false;
|
|
2080
|
+
var items = flattenArtifacts(catalog);
|
|
2081
|
+
var selectedStillExists = items.some(function (item) {
|
|
2082
|
+
return item && item.id === state.artifacts.selectedId;
|
|
2083
|
+
});
|
|
2084
|
+
if (!selectedStillExists) {
|
|
2085
|
+
var defaultItem = chooseDefaultArtifact(items, explorer);
|
|
2086
|
+
state.artifacts.selectedId = defaultItem ? defaultItem.id : null;
|
|
2087
|
+
state.artifacts.preview = null;
|
|
2088
|
+
if (defaultItem) {
|
|
2089
|
+
previewArtifact(explorer, defaultItem);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
renderArtifactList(explorer);
|
|
2093
|
+
renderArtifactPreview(explorer);
|
|
2094
|
+
})
|
|
2095
|
+
.catch(function (error) {
|
|
2096
|
+
state.artifacts.catalog = null;
|
|
2097
|
+
state.artifacts.error = error.message || "Artifact catalog request failed.";
|
|
2098
|
+
state.artifacts.loading = false;
|
|
2099
|
+
renderArtifactList(explorer);
|
|
2100
|
+
renderArtifactPreview(explorer);
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function renderArtifactList(explorer) {
|
|
2105
|
+
elements.artifactList.innerHTML = "";
|
|
2106
|
+
if (state.artifacts.loading) {
|
|
2107
|
+
elements.artifactList.append(artifactEmpty("Loading artifacts..."));
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
if (state.artifacts.error) {
|
|
2111
|
+
elements.artifactList.append(artifactEmpty(state.artifacts.error));
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
var catalog = state.artifacts.catalog;
|
|
2115
|
+
if (!catalog) {
|
|
2116
|
+
elements.artifactList.append(artifactEmpty("Artifacts have not been loaded yet."));
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
var groups = artifactGroups(catalog);
|
|
2120
|
+
var rendered = 0;
|
|
2121
|
+
groups.forEach(function (group) {
|
|
2122
|
+
var renderedGroup = renderArtifactGroup(explorer, group, 0);
|
|
2123
|
+
rendered += renderedGroup.count;
|
|
2124
|
+
if (renderedGroup.element) {
|
|
2125
|
+
elements.artifactList.append(renderedGroup.element);
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2128
|
+
if (rendered === 0) {
|
|
2129
|
+
elements.artifactList.append(artifactEmpty("No artifacts were found for the current scope."));
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
function renderArtifactGroup(explorer, group, depth) {
|
|
2134
|
+
var childGroups = Array.isArray(group.groups) ? group.groups : [];
|
|
2135
|
+
var nestedIds = new Set();
|
|
2136
|
+
childGroups.forEach(function (child) {
|
|
2137
|
+
(Array.isArray(child.items) ? child.items : []).forEach(function (item) {
|
|
2138
|
+
if (item && item.id) nestedIds.add(item.id);
|
|
2139
|
+
});
|
|
2140
|
+
});
|
|
2141
|
+
var items = (Array.isArray(group.items) ? group.items : []).filter(function (item) {
|
|
2142
|
+
return item && !nestedIds.has(item.id);
|
|
2143
|
+
});
|
|
2144
|
+
var section = document.createElement("section");
|
|
2145
|
+
section.className = depth > 0 ? "artifact-group artifact-subgroup" : "artifact-group";
|
|
2146
|
+
var title = document.createElement(depth > 0 ? "h4" : "h3");
|
|
2147
|
+
title.textContent = group.title || "Artifacts";
|
|
2148
|
+
section.append(title);
|
|
2149
|
+
var count = 0;
|
|
2150
|
+
childGroups.forEach(function (child) {
|
|
2151
|
+
var renderedChild = renderArtifactGroup(explorer, child, depth + 1);
|
|
2152
|
+
count += renderedChild.count;
|
|
2153
|
+
if (renderedChild.element) {
|
|
2154
|
+
section.append(renderedChild.element);
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
items.forEach(function (item) {
|
|
2158
|
+
count += 1;
|
|
2159
|
+
section.append(renderArtifactRow(explorer, item));
|
|
2160
|
+
});
|
|
2161
|
+
return { element: count > 0 ? section : null, count: count };
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
function renderArtifactRow(explorer, item) {
|
|
2165
|
+
var row = document.createElement("article");
|
|
2166
|
+
row.className = "artifact-row" + (state.artifacts.selectedId === item.id ? " selected" : "");
|
|
2167
|
+
row.dataset.artifactId = item.id || "";
|
|
2168
|
+
var details = document.createElement("button");
|
|
2169
|
+
details.type = "button";
|
|
2170
|
+
details.className = "artifact-row-main";
|
|
2171
|
+
details.setAttribute("aria-pressed", state.artifacts.selectedId === item.id ? "true" : "false");
|
|
2172
|
+
details.addEventListener("click", function () {
|
|
2173
|
+
previewArtifact(explorer, item);
|
|
2174
|
+
});
|
|
2175
|
+
var title = document.createElement("strong");
|
|
2176
|
+
title.textContent = artifactDisplayTitle(item);
|
|
2177
|
+
var meta = document.createElement("span");
|
|
2178
|
+
meta.className = "artifact-row-meta";
|
|
2179
|
+
var kind = document.createElement("span");
|
|
2180
|
+
kind.textContent = artifactKind(item);
|
|
2181
|
+
var size = document.createElement("span");
|
|
2182
|
+
size.textContent = formatArtifactBytes(item.sizeBytes);
|
|
2183
|
+
var reference = document.createElement("span");
|
|
2184
|
+
reference.textContent = artifactReference(item);
|
|
2185
|
+
meta.append(kind, size, reference);
|
|
2186
|
+
details.append(title, meta);
|
|
2187
|
+
|
|
2188
|
+
var actions = document.createElement("div");
|
|
2189
|
+
actions.className = "artifact-row-actions";
|
|
2190
|
+
var raw = document.createElement("a");
|
|
2191
|
+
raw.href = artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/raw");
|
|
2192
|
+
raw.target = "_blank";
|
|
2193
|
+
raw.rel = "noopener noreferrer";
|
|
2194
|
+
raw.textContent = "Raw";
|
|
2195
|
+
var download = document.createElement("a");
|
|
2196
|
+
download.href = artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/download");
|
|
2197
|
+
download.textContent = "Download";
|
|
2198
|
+
actions.append(raw, download);
|
|
2199
|
+
row.append(details, actions);
|
|
2200
|
+
return row;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
function artifactEmpty(message) {
|
|
2204
|
+
var empty = document.createElement("div");
|
|
2205
|
+
empty.className = "artifact-empty";
|
|
2206
|
+
empty.textContent = message;
|
|
2207
|
+
return empty;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
function previewArtifact(explorer, item) {
|
|
2211
|
+
var requestId = state.artifacts.previewRequestId + 1;
|
|
2212
|
+
state.artifacts.previewRequestId = requestId;
|
|
2213
|
+
state.artifacts.selectedId = item.id;
|
|
2214
|
+
state.artifacts.actionStatus = null;
|
|
2215
|
+
state.artifacts.actionStatusFailed = false;
|
|
2216
|
+
if (artifactKind(item) === "binary" || artifactKind(item) === "unknown") {
|
|
2217
|
+
state.artifacts.preview = {
|
|
2218
|
+
artifactId: item.id,
|
|
2219
|
+
loading: false,
|
|
2220
|
+
placeholder: true,
|
|
2221
|
+
renderKind: artifactKind(item),
|
|
2222
|
+
kind: artifactKind(item),
|
|
2223
|
+
artifact: item,
|
|
2224
|
+
sizeBytes: item.sizeBytes,
|
|
2225
|
+
loadedBytes: 0,
|
|
2226
|
+
content: "",
|
|
2227
|
+
};
|
|
2228
|
+
renderArtifactList(explorer);
|
|
2229
|
+
renderArtifactPreview(explorer);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
state.artifacts.preview = {
|
|
2233
|
+
artifactId: item.id,
|
|
2234
|
+
loading: true,
|
|
2235
|
+
content: "Loading preview...",
|
|
2236
|
+
renderKind: artifactKind(item),
|
|
2237
|
+
kind: artifactKind(item),
|
|
2238
|
+
artifact: item,
|
|
2239
|
+
};
|
|
2240
|
+
renderArtifactList(explorer);
|
|
2241
|
+
renderArtifactPreview(explorer);
|
|
2242
|
+
fetch(artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/preview"))
|
|
2243
|
+
.then(function (response) {
|
|
2244
|
+
return response.json().then(function (body) {
|
|
2245
|
+
if (!response.ok) {
|
|
2246
|
+
throw new Error(body && body.message ? body.message : "Artifact preview request failed.");
|
|
2247
|
+
}
|
|
2248
|
+
return body;
|
|
2249
|
+
});
|
|
2250
|
+
})
|
|
2251
|
+
.then(function (preview) {
|
|
2252
|
+
if (requestId !== state.artifacts.previewRequestId || state.artifacts.selectedId !== item.id) {
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
state.artifacts.preview = {
|
|
2256
|
+
artifactId: item.id,
|
|
2257
|
+
loading: false,
|
|
2258
|
+
content: preview.content || "",
|
|
2259
|
+
truncated: preview.truncated,
|
|
2260
|
+
renderKind: preview.renderKind || preview.kind || artifactKind(item),
|
|
2261
|
+
kind: preview.kind || preview.renderKind || artifactKind(item),
|
|
2262
|
+
artifact: preview.artifact || item,
|
|
2263
|
+
sizeBytes: Number.isFinite(preview.sizeBytes) ? preview.sizeBytes : item.sizeBytes,
|
|
2264
|
+
loadedBytes: Number.isFinite(preview.loadedBytes) ? preview.loadedBytes : null,
|
|
2265
|
+
jsonParseSafe: preview.jsonParseSafe,
|
|
2266
|
+
title: preview.artifact && preview.artifact.title ? preview.artifact.title : item.title,
|
|
2267
|
+
};
|
|
2268
|
+
renderArtifactPreview(explorer);
|
|
2269
|
+
})
|
|
2270
|
+
.catch(function (error) {
|
|
2271
|
+
if (requestId !== state.artifacts.previewRequestId || state.artifacts.selectedId !== item.id) {
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
state.artifacts.preview = {
|
|
2275
|
+
artifactId: item.id,
|
|
2276
|
+
loading: false,
|
|
2277
|
+
content: error.message || "Artifact preview request failed.",
|
|
2278
|
+
renderKind: artifactKind(item),
|
|
2279
|
+
kind: artifactKind(item),
|
|
2280
|
+
artifact: item,
|
|
2281
|
+
error: true,
|
|
2282
|
+
};
|
|
2283
|
+
renderArtifactPreview(explorer);
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
function renderArtifactPreview(explorer) {
|
|
2288
|
+
var item = selectedArtifact();
|
|
2289
|
+
renderArtifactToolbar(explorer, item);
|
|
2290
|
+
var preview = state.artifacts.preview;
|
|
2291
|
+
if (!item) {
|
|
2292
|
+
elements.artifactSelectedTitle.textContent = "Preview";
|
|
2293
|
+
elements.artifactSelectedMeta.textContent = "Select an artifact to preview it.";
|
|
2294
|
+
renderPreviewMessage("Select an artifact to preview it.", false);
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
elements.artifactSelectedTitle.textContent = artifactDisplayTitle(item);
|
|
2298
|
+
elements.artifactSelectedMeta.textContent = [
|
|
2299
|
+
artifactKind(item),
|
|
2300
|
+
formatArtifactBytes(item.sizeBytes),
|
|
2301
|
+
artifactReference(item),
|
|
2302
|
+
].filter(Boolean).join(" | ");
|
|
2303
|
+
if (!preview) {
|
|
2304
|
+
renderPreviewMessage("Select an artifact to preview it.", false);
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
renderArtifactPreviewContent(explorer, item, preview);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
function resetPreviewContainer() {
|
|
2311
|
+
elements.artifactPreview.textContent = "";
|
|
2312
|
+
elements.artifactPreview.className = "artifact-preview-content text-panel compact";
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
function renderPreviewMessage(message, failed) {
|
|
2316
|
+
resetPreviewContainer();
|
|
2317
|
+
elements.artifactPreview.classList.toggle("error-text", Boolean(failed));
|
|
2318
|
+
elements.artifactPreview.textContent = message;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
function renderArtifactPreviewContent(explorer, item, preview) {
|
|
2322
|
+
if (preview.loading) {
|
|
2323
|
+
renderPreviewMessage("Loading preview...", false);
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
if (preview.error) {
|
|
2327
|
+
renderPreviewMessage(preview.content || "Artifact preview request failed.", true);
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
resetPreviewContainer();
|
|
2332
|
+
var renderKind = String(preview.renderKind || preview.kind || artifactKind(item));
|
|
2333
|
+
if (renderKind === "markdown") {
|
|
2334
|
+
renderMarkdownPreview(elements.artifactPreview, preview);
|
|
2335
|
+
} else if (renderKind === "json") {
|
|
2336
|
+
renderJsonPreview(explorer, elements.artifactPreview, item, preview);
|
|
2337
|
+
} else if (renderKind === "diff") {
|
|
2338
|
+
renderTextPreview(elements.artifactPreview, preview, "Diff preview");
|
|
2339
|
+
} else if (renderKind === "text") {
|
|
2340
|
+
renderTextPreview(elements.artifactPreview, preview, "");
|
|
2341
|
+
} else {
|
|
2342
|
+
renderArtifactPlaceholder(explorer, elements.artifactPreview, item, preview);
|
|
2343
|
+
}
|
|
2344
|
+
renderTruncationStatus(elements.artifactPreview, preview);
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
function renderTextPreview(container, preview, label) {
|
|
2348
|
+
if (label) {
|
|
2349
|
+
var badge = document.createElement("div");
|
|
2350
|
+
badge.className = "artifact-kind-label";
|
|
2351
|
+
badge.textContent = label;
|
|
2352
|
+
container.append(badge);
|
|
2353
|
+
}
|
|
2354
|
+
var block = document.createElement("pre");
|
|
2355
|
+
block.className = "artifact-text-preview";
|
|
2356
|
+
block.textContent = preview.content || "";
|
|
2357
|
+
container.append(block);
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
function renderJsonPreview(explorer, container, item, preview) {
|
|
2361
|
+
var viewer = document.createElement("div");
|
|
2362
|
+
viewer.className = "artifact-json-viewer";
|
|
2363
|
+
var controls = document.createElement("div");
|
|
2364
|
+
controls.className = "artifact-preview-modes";
|
|
2365
|
+
var artifactId = item.id || "";
|
|
2366
|
+
var mode = state.artifacts.viewerModes[artifactId] || "pretty";
|
|
2367
|
+
["pretty", "raw"].forEach(function (nextMode) {
|
|
2368
|
+
var button = document.createElement("button");
|
|
2369
|
+
button.type = "button";
|
|
2370
|
+
button.textContent = nextMode === "pretty" ? "Pretty" : "Raw";
|
|
2371
|
+
button.className = mode === nextMode ? "primary" : "";
|
|
2372
|
+
button.setAttribute("aria-pressed", mode === nextMode ? "true" : "false");
|
|
2373
|
+
button.addEventListener("click", function () {
|
|
2374
|
+
state.artifacts.viewerModes[artifactId] = nextMode;
|
|
2375
|
+
renderArtifactPreview(explorer);
|
|
2376
|
+
});
|
|
2377
|
+
controls.append(button);
|
|
2378
|
+
});
|
|
2379
|
+
viewer.append(controls);
|
|
2380
|
+
|
|
2381
|
+
var block = document.createElement("pre");
|
|
2382
|
+
block.className = "artifact-text-preview artifact-json-preview";
|
|
2383
|
+
var raw = preview.content || "";
|
|
2384
|
+
if (mode === "raw") {
|
|
2385
|
+
block.textContent = raw;
|
|
2386
|
+
viewer.append(block);
|
|
2387
|
+
container.append(viewer);
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
if (preview.truncated || preview.jsonParseSafe === false) {
|
|
2392
|
+
viewer.append(previewWarning("JSON Pretty is unavailable for truncated or unsafe previews. Raw preview is shown."));
|
|
2393
|
+
block.textContent = raw;
|
|
2394
|
+
viewer.append(block);
|
|
2395
|
+
container.append(viewer);
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
try {
|
|
2400
|
+
block.textContent = JSON.stringify(JSON.parse(raw), null, 2);
|
|
2401
|
+
} catch (error) {
|
|
2402
|
+
viewer.append(previewWarning("JSON parse error: " + (error && error.message ? error.message : "invalid JSON.")));
|
|
2403
|
+
block.textContent = raw;
|
|
2404
|
+
}
|
|
2405
|
+
viewer.append(block);
|
|
2406
|
+
container.append(viewer);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
function previewWarning(message) {
|
|
2410
|
+
var warning = document.createElement("div");
|
|
2411
|
+
warning.className = "artifact-preview-warning";
|
|
2412
|
+
warning.textContent = message;
|
|
2413
|
+
return warning;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
function renderArtifactPlaceholder(explorer, container, item, preview) {
|
|
2417
|
+
var panel = document.createElement("div");
|
|
2418
|
+
panel.className = "artifact-placeholder";
|
|
2419
|
+
var title = document.createElement("strong");
|
|
2420
|
+
title.textContent = artifactKind(item) === "binary" ? "Binary artifact" : "Preview unavailable";
|
|
2421
|
+
var meta = document.createElement("dl");
|
|
2422
|
+
appendPlaceholderMeta(meta, "Kind", artifactKind(item));
|
|
2423
|
+
appendPlaceholderMeta(meta, "Path", artifactReference(item) || item.id || "Unknown");
|
|
2424
|
+
appendPlaceholderMeta(meta, "Size", formatArtifactBytes(Number.isFinite(preview.sizeBytes) ? preview.sizeBytes : item.sizeBytes));
|
|
2425
|
+
var actions = document.createElement("div");
|
|
2426
|
+
actions.className = "artifact-placeholder-actions";
|
|
2427
|
+
var raw = document.createElement("a");
|
|
2428
|
+
raw.href = artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/raw");
|
|
2429
|
+
raw.target = "_blank";
|
|
2430
|
+
raw.rel = "noopener noreferrer";
|
|
2431
|
+
raw.textContent = "Open raw";
|
|
2432
|
+
var download = document.createElement("a");
|
|
2433
|
+
download.href = artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/download");
|
|
2434
|
+
download.textContent = "Download";
|
|
2435
|
+
actions.append(raw, download);
|
|
2436
|
+
panel.append(title, meta, actions);
|
|
2437
|
+
container.append(panel);
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
function appendPlaceholderMeta(list, label, value) {
|
|
2441
|
+
var term = document.createElement("dt");
|
|
2442
|
+
term.textContent = label;
|
|
2443
|
+
var detail = document.createElement("dd");
|
|
2444
|
+
detail.textContent = value || "";
|
|
2445
|
+
list.append(term, detail);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
function renderTruncationStatus(container, preview) {
|
|
2449
|
+
if (!preview.truncated) {
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
var status = document.createElement("div");
|
|
2453
|
+
status.className = "artifact-truncation";
|
|
2454
|
+
var loaded = Number.isFinite(preview.loadedBytes) ? preview.loadedBytes : 0;
|
|
2455
|
+
var total = Number.isFinite(preview.sizeBytes) ? preview.sizeBytes : loaded;
|
|
2456
|
+
status.textContent = "Preview truncated: loaded " + formatArtifactBytes(loaded) + " of " + formatArtifactBytes(total) + ".";
|
|
2457
|
+
container.append(status);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
function renderMarkdownPreview(container, preview) {
|
|
2461
|
+
var root = document.createElement("div");
|
|
2462
|
+
root.className = "artifact-rendered-markdown";
|
|
2463
|
+
appendMarkdownBlocks(root, preview.content || "");
|
|
2464
|
+
container.append(root);
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
function appendMarkdownBlocks(container, markdown) {
|
|
2468
|
+
var lines = String(markdown || "").replace(/\r\n?/g, "\n").split("\n");
|
|
2469
|
+
var index = 0;
|
|
2470
|
+
while (index < lines.length) {
|
|
2471
|
+
var line = lines[index];
|
|
2472
|
+
if (!line.trim()) {
|
|
2473
|
+
index += 1;
|
|
2474
|
+
continue;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
var fence = line.match(/^\s*(```+|~~~+)\s*([^\s`]*)\s*$/);
|
|
2478
|
+
if (fence) {
|
|
2479
|
+
var marker = fence[1];
|
|
2480
|
+
var codeLines = [];
|
|
2481
|
+
index += 1;
|
|
2482
|
+
while (index < lines.length && lines[index].trim() !== marker) {
|
|
2483
|
+
codeLines.push(lines[index]);
|
|
2484
|
+
index += 1;
|
|
2485
|
+
}
|
|
2486
|
+
if (index < lines.length) index += 1;
|
|
2487
|
+
var pre = document.createElement("pre");
|
|
2488
|
+
var code = document.createElement("code");
|
|
2489
|
+
if (fence[2]) {
|
|
2490
|
+
code.dataset.language = fence[2];
|
|
2491
|
+
}
|
|
2492
|
+
code.textContent = codeLines.join("\n");
|
|
2493
|
+
pre.append(code);
|
|
2494
|
+
container.append(pre);
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
if (isMarkdownTable(lines, index)) {
|
|
2499
|
+
var tableResult = renderMarkdownTable(lines, index);
|
|
2500
|
+
container.append(tableResult.table);
|
|
2501
|
+
index = tableResult.nextIndex;
|
|
2502
|
+
continue;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
var heading = line.match(/^\s{0,3}(#{1,6})\s+(.+?)\s*#*\s*$/);
|
|
2506
|
+
if (heading) {
|
|
2507
|
+
var headingNode = document.createElement("h" + heading[1].length);
|
|
2508
|
+
appendInlineMarkdown(headingNode, heading[2]);
|
|
2509
|
+
container.append(headingNode);
|
|
2510
|
+
index += 1;
|
|
2511
|
+
continue;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
var listMatch = line.match(/^\s{0,3}((?:[-*+])|(?:\d+[.)]))\s+(.+)$/);
|
|
2515
|
+
if (listMatch) {
|
|
2516
|
+
var ordered = /\d/.test(listMatch[1]);
|
|
2517
|
+
var list = document.createElement(ordered ? "ol" : "ul");
|
|
2518
|
+
while (index < lines.length) {
|
|
2519
|
+
var current = lines[index].match(/^\s{0,3}((?:[-*+])|(?:\d+[.)]))\s+(.+)$/);
|
|
2520
|
+
if (!current || /\d/.test(current[1]) !== ordered) {
|
|
2521
|
+
break;
|
|
2522
|
+
}
|
|
2523
|
+
var item = document.createElement("li");
|
|
2524
|
+
appendInlineMarkdown(item, current[2]);
|
|
2525
|
+
list.append(item);
|
|
2526
|
+
index += 1;
|
|
2527
|
+
}
|
|
2528
|
+
container.append(list);
|
|
2529
|
+
continue;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
var paragraphLines = [line.trim()];
|
|
2533
|
+
index += 1;
|
|
2534
|
+
while (index < lines.length && lines[index].trim() && !isMarkdownBlockStart(lines, index)) {
|
|
2535
|
+
paragraphLines.push(lines[index].trim());
|
|
2536
|
+
index += 1;
|
|
2537
|
+
}
|
|
2538
|
+
var paragraph = document.createElement("p");
|
|
2539
|
+
appendInlineMarkdown(paragraph, paragraphLines.join(" "));
|
|
2540
|
+
container.append(paragraph);
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
function isMarkdownBlockStart(lines, index) {
|
|
2545
|
+
var line = lines[index] || "";
|
|
2546
|
+
return /^\s*(```+|~~~+)/.test(line)
|
|
2547
|
+
|| /^\s{0,3}#{1,6}\s+/.test(line)
|
|
2548
|
+
|| /^\s{0,3}((?:[-*+])|(?:\d+[.)]))\s+/.test(line)
|
|
2549
|
+
|| isMarkdownTable(lines, index);
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
function isMarkdownTable(lines, index) {
|
|
2553
|
+
return index + 1 < lines.length
|
|
2554
|
+
&& lines[index].indexOf("|") !== -1
|
|
2555
|
+
&& /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(lines[index + 1] || "");
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
function splitMarkdownTableRow(line) {
|
|
2559
|
+
var value = String(line || "").trim();
|
|
2560
|
+
if (value.charAt(0) === "|") value = value.slice(1);
|
|
2561
|
+
if (value.charAt(value.length - 1) === "|") value = value.slice(0, -1);
|
|
2562
|
+
return value.split("|").map(function (cell) {
|
|
2563
|
+
return cell.trim();
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
function renderMarkdownTable(lines, startIndex) {
|
|
2568
|
+
var table = document.createElement("table");
|
|
2569
|
+
var thead = document.createElement("thead");
|
|
2570
|
+
var tbody = document.createElement("tbody");
|
|
2571
|
+
var headerRow = document.createElement("tr");
|
|
2572
|
+
splitMarkdownTableRow(lines[startIndex]).forEach(function (cell) {
|
|
2573
|
+
var th = document.createElement("th");
|
|
2574
|
+
appendInlineMarkdown(th, cell);
|
|
2575
|
+
headerRow.append(th);
|
|
2576
|
+
});
|
|
2577
|
+
thead.append(headerRow);
|
|
2578
|
+
var index = startIndex + 2;
|
|
2579
|
+
while (index < lines.length && lines[index].indexOf("|") !== -1 && lines[index].trim()) {
|
|
2580
|
+
var row = document.createElement("tr");
|
|
2581
|
+
splitMarkdownTableRow(lines[index]).forEach(function (cell) {
|
|
2582
|
+
var td = document.createElement("td");
|
|
2583
|
+
appendInlineMarkdown(td, cell);
|
|
2584
|
+
row.append(td);
|
|
2585
|
+
});
|
|
2586
|
+
tbody.append(row);
|
|
2587
|
+
index += 1;
|
|
2588
|
+
}
|
|
2589
|
+
table.append(thead, tbody);
|
|
2590
|
+
return { table: table, nextIndex: index };
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
function appendInlineMarkdown(parent, value) {
|
|
2594
|
+
var source = String(value || "");
|
|
2595
|
+
var pattern = /(`[^`]+`)|(\[([^\]]+)\]\(([^)\s]+)\))/g;
|
|
2596
|
+
var cursor = 0;
|
|
2597
|
+
var match;
|
|
2598
|
+
while ((match = pattern.exec(source))) {
|
|
2599
|
+
if (match.index > cursor) {
|
|
2600
|
+
parent.append(document.createTextNode(source.slice(cursor, match.index)));
|
|
2601
|
+
}
|
|
2602
|
+
if (match[1]) {
|
|
2603
|
+
var code = document.createElement("code");
|
|
2604
|
+
code.textContent = match[1].slice(1, -1);
|
|
2605
|
+
parent.append(code);
|
|
2606
|
+
} else {
|
|
2607
|
+
var href = safeMarkdownHref(match[4]);
|
|
2608
|
+
if (href) {
|
|
2609
|
+
var link = document.createElement("a");
|
|
2610
|
+
link.href = href;
|
|
2611
|
+
link.target = "_blank";
|
|
2612
|
+
link.rel = "noopener noreferrer";
|
|
2613
|
+
link.textContent = match[3];
|
|
2614
|
+
parent.append(link);
|
|
2615
|
+
} else {
|
|
2616
|
+
parent.append(document.createTextNode(match[0]));
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
cursor = pattern.lastIndex;
|
|
2620
|
+
}
|
|
2621
|
+
if (cursor < source.length) {
|
|
2622
|
+
parent.append(document.createTextNode(source.slice(cursor)));
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
function safeMarkdownHref(value) {
|
|
2627
|
+
var raw = String(value || "").trim();
|
|
2628
|
+
try {
|
|
2629
|
+
var parsed = new URL(raw, window.location.href);
|
|
2630
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.protocol === "mailto:") {
|
|
2631
|
+
return parsed.href;
|
|
2632
|
+
}
|
|
2633
|
+
} catch {
|
|
2634
|
+
return null;
|
|
2635
|
+
}
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
function renderArtifactToolbar(explorer, item) {
|
|
2640
|
+
var preview = state.artifacts.preview;
|
|
2641
|
+
var hasItem = Boolean(item);
|
|
2642
|
+
var hasContent = hasItem && preview && !preview.loading && !preview.error && typeof preview.content === "string";
|
|
2643
|
+
elements.artifactCopyContent.disabled = !hasContent;
|
|
2644
|
+
elements.artifactCopyReference.disabled = !hasItem;
|
|
2645
|
+
setArtifactActionLink(elements.artifactOpenRaw, hasItem ? artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/raw") : "");
|
|
2646
|
+
setArtifactActionLink(elements.artifactDownload, hasItem ? artifactApiUrl(explorer, "/" + encodeURIComponent(item.id) + "/download") : "");
|
|
2647
|
+
elements.artifactActionStatus.textContent = state.artifacts.actionStatus || "";
|
|
2648
|
+
elements.artifactActionStatus.classList.toggle("error-text", Boolean(state.artifacts.actionStatusFailed));
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
function setArtifactActionLink(link, href) {
|
|
2652
|
+
if (href) {
|
|
2653
|
+
link.href = href;
|
|
2654
|
+
link.classList.remove("disabled");
|
|
2655
|
+
link.setAttribute("aria-disabled", "false");
|
|
2656
|
+
} else {
|
|
2657
|
+
link.removeAttribute("href");
|
|
2658
|
+
link.classList.add("disabled");
|
|
2659
|
+
link.setAttribute("aria-disabled", "true");
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
function setArtifactActionStatus(message, failed) {
|
|
2664
|
+
state.artifacts.actionStatus = message;
|
|
2665
|
+
state.artifacts.actionStatusFailed = Boolean(failed);
|
|
2666
|
+
elements.artifactActionStatus.textContent = message || "";
|
|
2667
|
+
elements.artifactActionStatus.classList.toggle("error-text", Boolean(failed));
|
|
2668
|
+
if (failed && message) {
|
|
2669
|
+
appendLog("[artifacts] " + message);
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
function writeClipboard(value, successMessage) {
|
|
2674
|
+
if (typeof navigator === "undefined" || !navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
|
|
2675
|
+
setArtifactActionStatus("Clipboard is not available in this browser.", true);
|
|
2676
|
+
return Promise.resolve(false);
|
|
2677
|
+
}
|
|
2678
|
+
return navigator.clipboard.writeText(value).then(function () {
|
|
2679
|
+
setArtifactActionStatus(successMessage, false);
|
|
2680
|
+
return true;
|
|
2681
|
+
}).catch(function (error) {
|
|
2682
|
+
setArtifactActionStatus("Clipboard copy failed: " + (error && error.message ? error.message : "permission denied."), true);
|
|
2683
|
+
return false;
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
function copySelectedArtifactContent() {
|
|
2688
|
+
var preview = state.artifacts.preview;
|
|
2689
|
+
if (!preview || preview.loading || preview.error || typeof preview.content !== "string") {
|
|
2690
|
+
setArtifactActionStatus("Preview content is not ready to copy.", true);
|
|
2691
|
+
return;
|
|
2692
|
+
}
|
|
2693
|
+
writeClipboard(preview.content, "Copied artifact preview content.");
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
function copySelectedArtifactReference() {
|
|
2697
|
+
var item = selectedArtifact();
|
|
2698
|
+
if (!item) {
|
|
2699
|
+
setArtifactActionStatus("No artifact is selected.", true);
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
writeClipboard(artifactReference(item), "Copied artifact path/reference.");
|
|
2703
|
+
}
|
|
2704
|
+
|
|
338
2705
|
function renderModal(vm) {
|
|
339
2706
|
var nextSignature = modalSignature(vm);
|
|
340
2707
|
if (state.modalSignature === nextSignature) {
|
|
@@ -383,7 +2750,7 @@
|
|
|
383
2750
|
type: "form",
|
|
384
2751
|
formId: vm.form.formId,
|
|
385
2752
|
title: vm.form.title || definition.title,
|
|
386
|
-
|
|
2753
|
+
description: definition.description || "",
|
|
387
2754
|
footer: vm.form.footer,
|
|
388
2755
|
error: vm.form.error,
|
|
389
2756
|
submitLabel: definition.submitLabel,
|
|
@@ -465,7 +2832,7 @@
|
|
|
465
2832
|
|
|
466
2833
|
function renderForm(formModel) {
|
|
467
2834
|
var definition = formModel.definition || {};
|
|
468
|
-
var modal = createModal(text(formModel.title || definition.title, "Input required"), text(definition.description
|
|
2835
|
+
var modal = createModal(text(formModel.title || definition.title, "Input required"), text(definition.description, ""));
|
|
469
2836
|
modal.classList.add("form-" + classNameToken(formModel.formId || definition.formId || "unknown"));
|
|
470
2837
|
var body = modal.querySelector(".modal-body");
|
|
471
2838
|
var footerNote = modal.querySelector(".modal-note");
|
|
@@ -567,6 +2934,8 @@
|
|
|
567
2934
|
checkRow.className = "check-row";
|
|
568
2935
|
var checkbox = document.createElement("input");
|
|
569
2936
|
checkbox.type = "checkbox";
|
|
2937
|
+
checkbox.id = "field-" + field.id;
|
|
2938
|
+
checkbox.name = field.id;
|
|
570
2939
|
checkbox.dataset.fieldId = field.id;
|
|
571
2940
|
checkbox.dataset.fieldType = field.type;
|
|
572
2941
|
checkbox.checked = currentValue(field) === true;
|
|
@@ -584,6 +2953,7 @@
|
|
|
584
2953
|
var input = document.createElement(field.multiline ? "textarea" : "input");
|
|
585
2954
|
input.id = "field-" + field.id;
|
|
586
2955
|
if (!field.multiline) input.type = "text";
|
|
2956
|
+
input.name = field.id;
|
|
587
2957
|
if (field.placeholder) input.placeholder = field.placeholder;
|
|
588
2958
|
if (field.rows) input.rows = field.rows;
|
|
589
2959
|
input.dataset.fieldId = field.id;
|
|
@@ -619,6 +2989,7 @@
|
|
|
619
2989
|
var input = document.createElement("input");
|
|
620
2990
|
input.type = "radio";
|
|
621
2991
|
input.name = "field-" + field.id;
|
|
2992
|
+
input.id = controlId("field", field.id, option.value);
|
|
622
2993
|
input.value = option.value;
|
|
623
2994
|
input.dataset.fieldId = field.id;
|
|
624
2995
|
input.dataset.fieldType = field.type;
|
|
@@ -649,6 +3020,8 @@
|
|
|
649
3020
|
label.className = "field-option";
|
|
650
3021
|
var input = document.createElement("input");
|
|
651
3022
|
input.type = "checkbox";
|
|
3023
|
+
input.id = controlId("field", field.id, option.value);
|
|
3024
|
+
input.name = field.id;
|
|
652
3025
|
input.value = option.value;
|
|
653
3026
|
input.dataset.fieldId = field.id;
|
|
654
3027
|
input.dataset.fieldType = field.type;
|
|
@@ -685,24 +3058,76 @@
|
|
|
685
3058
|
return Object.assign({}, state.formValues);
|
|
686
3059
|
}
|
|
687
3060
|
|
|
3061
|
+
applyTheme(state.theme);
|
|
3062
|
+
if (elements.themeToggle) {
|
|
3063
|
+
elements.themeToggle.addEventListener("click", toggleTheme);
|
|
3064
|
+
}
|
|
3065
|
+
if (elements.autoFlowResizer) {
|
|
3066
|
+
elements.autoFlowResizer.setAttribute("role", "separator");
|
|
3067
|
+
elements.autoFlowResizer.setAttribute("aria-orientation", "horizontal");
|
|
3068
|
+
elements.autoFlowResizer.setAttribute("aria-label", "Resize flow editor");
|
|
3069
|
+
elements.autoFlowResizer.setAttribute("tabindex", "0");
|
|
3070
|
+
elements.autoFlowResizer.addEventListener("pointerdown", beginAutoFlowResize);
|
|
3071
|
+
elements.autoFlowResizer.addEventListener("dblclick", resetAutoFlowHeight);
|
|
3072
|
+
elements.autoFlowResizer.addEventListener("keydown", handleAutoFlowResizerKeydown);
|
|
3073
|
+
}
|
|
3074
|
+
applyWorkspaceSplit(state.workspaceSplit);
|
|
3075
|
+
if (elements.workspaceResizer) {
|
|
3076
|
+
elements.workspaceResizer.setAttribute("role", "separator");
|
|
3077
|
+
elements.workspaceResizer.setAttribute("aria-orientation", "vertical");
|
|
3078
|
+
elements.workspaceResizer.setAttribute("aria-label", "Resize workspace panels");
|
|
3079
|
+
elements.workspaceResizer.setAttribute("tabindex", "0");
|
|
3080
|
+
elements.workspaceResizer.addEventListener("pointerdown", beginWorkspaceResize);
|
|
3081
|
+
elements.workspaceResizer.addEventListener("dblclick", resetWorkspaceSplit);
|
|
3082
|
+
elements.workspaceResizer.addEventListener("keydown", handleWorkspaceResizerKeydown);
|
|
3083
|
+
}
|
|
3084
|
+
applyLogAutoscroll(state.logAutoscroll);
|
|
3085
|
+
if (elements.logAutoscroll) {
|
|
3086
|
+
elements.logAutoscroll.addEventListener("change", function () {
|
|
3087
|
+
applyLogAutoscroll(elements.logAutoscroll.checked);
|
|
3088
|
+
persistLogAutoscroll(state.logAutoscroll);
|
|
3089
|
+
});
|
|
3090
|
+
}
|
|
688
3091
|
elements.run.addEventListener("click", api.openRunConfirm);
|
|
689
3092
|
elements.interrupt.addEventListener("click", api.openInterruptConfirm);
|
|
3093
|
+
elements.artifactOpen.addEventListener("click", api.openArtifactExplorer);
|
|
3094
|
+
elements.artifactClose.addEventListener("click", api.closeArtifactExplorer);
|
|
3095
|
+
elements.artifactToolbarClose.addEventListener("click", api.closeArtifactExplorer);
|
|
3096
|
+
elements.artifactCopyContent.addEventListener("click", copySelectedArtifactContent);
|
|
3097
|
+
elements.artifactCopyReference.addEventListener("click", copySelectedArtifactReference);
|
|
3098
|
+
ensureGitDiffModeButtons();
|
|
3099
|
+
elements.gitDiffClose.addEventListener("click", closeGitDiff);
|
|
3100
|
+
Array.prototype.slice.call(elements.gitDiffModeControls.querySelectorAll("button")).forEach(function (button) {
|
|
3101
|
+
button.addEventListener("click", function () {
|
|
3102
|
+
setGitDiffMode(button.dataset.gitDiffMode || "head");
|
|
3103
|
+
});
|
|
3104
|
+
});
|
|
3105
|
+
elements.gitRefresh.addEventListener("click", api.refreshGit);
|
|
3106
|
+
elements.gitCreateBranch.addEventListener("click", api.createGitBranch);
|
|
3107
|
+
elements.gitCheckout.addEventListener("click", api.checkoutGitBranch);
|
|
3108
|
+
elements.gitFetch.addEventListener("click", api.fetchGit);
|
|
3109
|
+
elements.gitPull.addEventListener("click", api.pullGit);
|
|
3110
|
+
elements.gitStage.addEventListener("click", api.stageGit);
|
|
3111
|
+
elements.gitUnstage.addEventListener("click", api.unstageGit);
|
|
3112
|
+
elements.gitCommit.addEventListener("click", api.commitGit);
|
|
3113
|
+
elements.gitPush.addEventListener("click", api.pushGit);
|
|
3114
|
+
elements.gitCommitMessage.addEventListener("input", function () {
|
|
3115
|
+
state.gitCommitMessage = elements.gitCommitMessage.value;
|
|
3116
|
+
});
|
|
3117
|
+
elements.gitCommitMessage.addEventListener("change", api.updateGitCommitMessage);
|
|
690
3118
|
elements.help.addEventListener("click", api.toggleHelp);
|
|
691
3119
|
elements.closeHelp.addEventListener("click", function () {
|
|
692
3120
|
api.showHelp(false);
|
|
693
3121
|
});
|
|
694
3122
|
elements.clearLog.addEventListener("click", api.clearLog);
|
|
695
3123
|
elements.progress.addEventListener("scroll", function () {
|
|
696
|
-
|
|
697
|
-
});
|
|
698
|
-
elements.summary.addEventListener("scroll", function () {
|
|
699
|
-
api.scrollPane("summary", Math.round(elements.summary.scrollTop));
|
|
3124
|
+
sendScrollPane("progress", elements.progress);
|
|
700
3125
|
});
|
|
701
3126
|
elements.log.addEventListener("scroll", function () {
|
|
702
|
-
|
|
3127
|
+
sendScrollPane("log", elements.log);
|
|
703
3128
|
});
|
|
704
3129
|
elements.helpText.addEventListener("scroll", function () {
|
|
705
|
-
|
|
3130
|
+
sendScrollPane("help", elements.helpText);
|
|
706
3131
|
});
|
|
707
3132
|
|
|
708
3133
|
api.connect();
|