loadtoagent 1.0.0 → 1.3.1
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.ko.md +7 -0
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +23 -1
- package/docs/UI-AUDIT-100.md +118 -0
- package/docs/UI-AUDIT-101-200.md +120 -0
- package/docs/UI-AUDIT-201-300.md +120 -0
- package/main.js +183 -37
- package/package.json +15 -4
- package/preload.js +11 -0
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -9
- package/renderer/app-dashboard.js +197 -58
- package/renderer/app-drawer-content.js +103 -86
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +79 -55
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -9
- package/renderer/app-events-navigation.js +78 -31
- package/renderer/app-events-sessions.js +171 -4
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +32 -4
- package/renderer/app-graph-orchestration.js +16 -13
- package/renderer/app-graph-view.js +228 -112
- package/renderer/app-management.js +211 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +103 -33
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +119 -38
- package/renderer/app.js +155 -76
- package/renderer/i18n-messages.js +833 -15
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +162 -75
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +50 -1
- package/renderer/styles-cards.css +26 -0
- package/renderer/styles-collaboration.css +18 -88
- package/renderer/styles-components.css +126 -14
- package/renderer/styles-management.css +355 -0
- package/renderer/styles-overlays.css +13 -2
- package/renderer/styles-product.css +24 -16
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +862 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +31 -14
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +37 -17
- package/renderer/styles-run-composer.css +5 -0
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +160 -0
- package/renderer/styles-terminal.css +339 -2
- package/renderer/styles-tmux.css +154 -0
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +69 -2
- package/renderer/styles.css +27 -12
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +233 -40
- package/renderer/terminal-workbench.js +176 -78
- package/renderer/terminal.js +350 -37
- package/src/agentMonitor/claudeParser.js +96 -7
- package/src/agentMonitor/codexParser.js +59 -4
- package/src/agentMonitor/executionActivity.js +282 -0
- package/src/agentMonitor/genericParser.js +56 -13
- package/src/agentMonitor/hierarchy.js +17 -0
- package/src/agentMonitor/responseIntent.js +51 -0
- package/src/agentMonitor.js +21 -3
- package/src/agentRunner.js +66 -1
- package/src/attentionNotifier.js +71 -0
- package/src/automationMonitor.js +258 -0
- package/src/contracts.js +10 -1
- package/src/ipc/registerAgentIpc.js +9 -3
- package/src/ipc/registerAppIpc.js +4 -1
- package/src/ipc/registerTerminalIpc.js +12 -6
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +81 -23
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +247 -0
- package/src/terminalHost.js +381 -0
- package/src/terminalHostDaemon.js +60 -0
- package/src/terminalManager.js +158 -3
- package/src/tmuxMonitor.js +2 -2
- package/src/updateInstaller.js +175 -0
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createDialogEventBindings = function createDialogEventBindings(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const {
|
|
7
|
-
$, $$, state, providerInfo, renderProviderRail, scheduleAgentWorkflowConnections, resumeAgentTerminal, loadSessionDetail,
|
|
8
|
+
$, $$, state, providerInfo, visibleProviders = () => state.providers, renderProviderRail, scheduleAgentWorkflowConnections, resumeAgentTerminal, loadSessionDetail,
|
|
8
9
|
closeDrawer, renderDrawer, providerPickerHtml, syncRunComposer, openRunModal, closeRunModal, toast, performUiAction,
|
|
9
|
-
handleRun, trapDialogFocus,
|
|
10
|
+
handleRun, trapDialogFocus, currentDialog, selectView, saveRunDraft = () => {}, safeBackdrop = null,
|
|
11
|
+
copyText = async () => false,
|
|
12
|
+
dispatchAgentCommand, controlManagedRun, quickRespond, prepareReassignment,
|
|
10
13
|
} = context;
|
|
11
14
|
|
|
12
15
|
function bindRunComposerEvents() {
|
|
13
16
|
$("#newRunBtn").addEventListener("click", openRunModal);
|
|
14
17
|
$$("[data-open-run]").forEach((button) => button.addEventListener("click", openRunModal));
|
|
15
|
-
$("#closeRunModalBtn").addEventListener("click", closeRunModal);
|
|
16
|
-
$("#cancelRunBtn").addEventListener("click", closeRunModal);
|
|
17
|
-
$("#runModal")
|
|
18
|
+
$("#closeRunModalBtn").addEventListener("click", () => closeRunModal());
|
|
19
|
+
$("#cancelRunBtn").addEventListener("click", () => closeRunModal());
|
|
20
|
+
if (safeBackdrop) safeBackdrop($("#runModal"), () => closeRunModal());
|
|
21
|
+
else $("#runModal").addEventListener("click", (event) => {
|
|
18
22
|
if (event.target === $("#runModal")) closeRunModal();
|
|
19
23
|
});
|
|
20
24
|
$("#runProviderPicker").addEventListener("click", (event) => {
|
|
@@ -23,6 +27,20 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
23
27
|
state.runProvider = button.dataset.runProvider;
|
|
24
28
|
$("#runProviderPicker").innerHTML = providerPickerHtml();
|
|
25
29
|
syncRunComposer();
|
|
30
|
+
saveRunDraft();
|
|
31
|
+
});
|
|
32
|
+
$("#runProviderPicker").addEventListener("keydown", (event) => {
|
|
33
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
34
|
+
const options = $$("#runProviderPicker [data-run-provider]:not(:disabled)");
|
|
35
|
+
const current = Math.max(0, options.indexOf(event.target.closest("[data-run-provider]")));
|
|
36
|
+
const next = event.key === "Home"
|
|
37
|
+
? 0
|
|
38
|
+
: event.key === "End"
|
|
39
|
+
? options.length - 1
|
|
40
|
+
: (current + (["ArrowRight", "ArrowDown"].includes(event.key) ? 1 : -1) + options.length) % options.length;
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
options[next]?.click();
|
|
43
|
+
requestAnimationFrame(() => $("#runProviderPicker").querySelector(`[data-run-provider="${CSS.escape(state.runProvider)}"]`)?.focus());
|
|
26
44
|
});
|
|
27
45
|
$("#runProviderHelp").addEventListener("click", async (event) => {
|
|
28
46
|
const docs = event.target.closest("[data-provider-docs]");
|
|
@@ -31,31 +49,33 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
31
49
|
if (provider.docs)
|
|
32
50
|
await performUiAction(async () => {
|
|
33
51
|
const result = await window.loadtoagent.openExternal(provider.docs);
|
|
34
|
-
if (!result || result.ok === false) throw new Error("
|
|
35
|
-
}, "
|
|
52
|
+
if (!result || result.ok === false) throw new Error(t("run.docs_open_failed"));
|
|
53
|
+
}, t("run.docs_open_failed"), docs);
|
|
36
54
|
return;
|
|
37
55
|
}
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
const recheck = event.target.closest("[data-provider-recheck]");
|
|
57
|
+
if (recheck) {
|
|
58
|
+
const nextAvailability = await performUiAction(() => window.loadtoagent.probeProviders(), t("run.cli_check_failed"), recheck);
|
|
40
59
|
if (!nextAvailability) return;
|
|
41
60
|
state.availability = nextAvailability;
|
|
42
|
-
const installed =
|
|
61
|
+
const installed = visibleProviders().find((provider) => state.availability[provider.id]);
|
|
43
62
|
if (installed) state.runProvider = installed.id;
|
|
44
63
|
$("#runProviderPicker").innerHTML = providerPickerHtml();
|
|
45
64
|
renderProviderRail();
|
|
46
65
|
syncRunComposer();
|
|
47
|
-
toast(installed ?
|
|
66
|
+
toast(installed ? t("run.cli_ready", { provider: installed.label }) : t("run.cli_not_found"));
|
|
48
67
|
}
|
|
49
68
|
});
|
|
50
69
|
$("#runPrompt").addEventListener("input", syncRunComposer);
|
|
51
70
|
$(".run-prompt-examples").addEventListener("click", (event) => {
|
|
52
|
-
const example = event.target.closest("[data-run-prompt-
|
|
71
|
+
const example = event.target.closest("[data-run-prompt-key]");
|
|
53
72
|
if (!example) return;
|
|
54
73
|
const input = $("#runPrompt");
|
|
55
|
-
const text = example.dataset.
|
|
74
|
+
const text = t(example.dataset.runPromptKey);
|
|
56
75
|
if (!input.value.trim()) input.value = text;
|
|
57
76
|
else input.setRangeText(`${input.selectionStart ? "\n\n" : ""}${text}`, input.selectionStart, input.selectionEnd, "end");
|
|
58
77
|
syncRunComposer();
|
|
78
|
+
saveRunDraft();
|
|
59
79
|
input.focus();
|
|
60
80
|
});
|
|
61
81
|
$("#runWorkspaceSuggestions").addEventListener("click", (event) => {
|
|
@@ -63,14 +83,28 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
63
83
|
if (!workspace) return;
|
|
64
84
|
$("#runCwd").value = workspace.dataset.runWorkspace;
|
|
65
85
|
syncRunComposer();
|
|
86
|
+
saveRunDraft();
|
|
87
|
+
});
|
|
88
|
+
$("#runWorkspaceSuggestions").addEventListener("keydown", (event) => {
|
|
89
|
+
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
90
|
+
const options = $$("#runWorkspaceSuggestions [data-run-workspace]");
|
|
91
|
+
const current = Math.max(0, options.indexOf(event.target.closest("[data-run-workspace]")));
|
|
92
|
+
const next = event.key === "Home"
|
|
93
|
+
? 0
|
|
94
|
+
: event.key === "End"
|
|
95
|
+
? options.length - 1
|
|
96
|
+
: (current + (event.key === "ArrowRight" ? 1 : -1) + options.length) % options.length;
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
options[next]?.focus();
|
|
66
99
|
});
|
|
67
100
|
$("#runCwd").addEventListener("input", syncRunComposer);
|
|
68
101
|
$("#allowWrites").addEventListener("change", syncRunComposer);
|
|
69
102
|
$("#pickRunCwdBtn").addEventListener("click", async () => {
|
|
70
|
-
const folder = await performUiAction(() => window.loadtoagent.pickWorkspace(), "
|
|
103
|
+
const folder = await performUiAction(() => window.loadtoagent.pickWorkspace(), t("workspace.pick_failed"), $("#pickRunCwdBtn"));
|
|
71
104
|
if (folder) {
|
|
72
105
|
$("#runCwd").value = folder;
|
|
73
106
|
syncRunComposer();
|
|
107
|
+
saveRunDraft();
|
|
74
108
|
}
|
|
75
109
|
});
|
|
76
110
|
$("#runForm").addEventListener("keydown", (event) => {
|
|
@@ -80,11 +114,27 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
80
114
|
}
|
|
81
115
|
});
|
|
82
116
|
$("#runForm").addEventListener("submit", handleRun);
|
|
117
|
+
$("#runForm").addEventListener("invalid", (event) => {
|
|
118
|
+
event.target.setAttribute("aria-invalid", "true");
|
|
119
|
+
if (!$("#runForm").dataset.invalidFocusQueued) {
|
|
120
|
+
$("#runForm").dataset.invalidFocusQueued = "true";
|
|
121
|
+
queueMicrotask(() => {
|
|
122
|
+
delete $("#runForm").dataset.invalidFocusQueued;
|
|
123
|
+
$("#runForm").querySelector(":invalid")?.focus({ preventScroll: true });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}, true);
|
|
127
|
+
$("#runForm").addEventListener("input", (event) => {
|
|
128
|
+
if (event.target.matches("input, textarea, select") && event.target.checkValidity()) event.target.removeAttribute("aria-invalid");
|
|
129
|
+
$("#runError").classList.add("hidden");
|
|
130
|
+
$("#runError").textContent = "";
|
|
131
|
+
});
|
|
83
132
|
}
|
|
84
133
|
|
|
85
134
|
function bindDrawerAndGlobalEvents() {
|
|
86
135
|
$("#closeDrawerBtn").addEventListener("click", closeDrawer);
|
|
87
|
-
$("#drawerBackdrop")
|
|
136
|
+
if (safeBackdrop) safeBackdrop($("#drawerBackdrop"), closeDrawer, $("#detailDrawer"));
|
|
137
|
+
else $("#drawerBackdrop").addEventListener("click", closeDrawer);
|
|
88
138
|
$(".drawer-tabs").addEventListener("click", (event) => {
|
|
89
139
|
const tab = event.target.closest("[data-tab]");
|
|
90
140
|
if (tab) {
|
|
@@ -94,7 +144,8 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
94
144
|
}
|
|
95
145
|
});
|
|
96
146
|
$(".drawer-tabs").addEventListener("keydown", (event) => {
|
|
97
|
-
|
|
147
|
+
const pageKey = event.ctrlKey && ["PageUp", "PageDown"].includes(event.key);
|
|
148
|
+
if (!pageKey && !["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
98
149
|
const tabs = $$(".drawer-tab:not(.hidden)");
|
|
99
150
|
const current = Math.max(0, tabs.indexOf(event.target.closest(".drawer-tab")));
|
|
100
151
|
const next =
|
|
@@ -102,7 +153,7 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
102
153
|
? 0
|
|
103
154
|
: event.key === "End"
|
|
104
155
|
? tabs.length - 1
|
|
105
|
-
: (current + (
|
|
156
|
+
: (current + (["ArrowRight", "ArrowDown", "PageDown"].includes(event.key) ? 1 : -1) + tabs.length) % tabs.length;
|
|
106
157
|
event.preventDefault();
|
|
107
158
|
state.drawerTab = tabs[next].dataset.tab;
|
|
108
159
|
state.drawerForceLatest = state.drawerTab === "chat";
|
|
@@ -110,14 +161,50 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
110
161
|
$(`.drawer-tab[data-tab="${state.drawerTab}"]`)?.focus();
|
|
111
162
|
});
|
|
112
163
|
$("#detailDrawer").addEventListener("click", async (event) => {
|
|
164
|
+
const copy = event.target.closest("[data-copy-text]");
|
|
165
|
+
if (copy) {
|
|
166
|
+
await copyText(copy.dataset.copyText);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
113
169
|
const resume = event.target.closest("[data-resume-agent]");
|
|
114
170
|
if (resume) {
|
|
115
|
-
|
|
171
|
+
if (resume.dataset.busy === "true") return;
|
|
172
|
+
resume.dataset.busy = "true";
|
|
173
|
+
resume.disabled = true;
|
|
174
|
+
resume.setAttribute("aria-busy", "true");
|
|
175
|
+
try {
|
|
176
|
+
await resumeAgentTerminal(resume.dataset.resumeAgent);
|
|
177
|
+
} finally {
|
|
178
|
+
if (resume.isConnected) {
|
|
179
|
+
delete resume.dataset.busy;
|
|
180
|
+
resume.disabled = false;
|
|
181
|
+
resume.removeAttribute("aria-busy");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const quick = event.target.closest("[data-attention-quick]");
|
|
187
|
+
if (quick) {
|
|
188
|
+
quickRespond(quick.dataset.attentionSessionId || state.selectedId, quick.dataset.attentionQuick, $("#detailDrawer"));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const managedAction = event.target.closest("[data-managed-run-action]");
|
|
192
|
+
if (managedAction) {
|
|
193
|
+
await controlManagedRun(managedAction.dataset.managementSessionId || state.selectedId, managedAction.dataset.managedRunAction);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const reassign = event.target.closest("[data-reassign-session]");
|
|
197
|
+
if (reassign) {
|
|
198
|
+
prepareReassignment(reassign.dataset.reassignSession);
|
|
116
199
|
return;
|
|
117
200
|
}
|
|
118
201
|
const retry = event.target.closest("[data-retry-detail]");
|
|
119
202
|
if (retry) {
|
|
120
|
-
|
|
203
|
+
if (retry.dataset.busy === "true") return;
|
|
204
|
+
retry.dataset.busy = "true";
|
|
205
|
+
retry.disabled = true;
|
|
206
|
+
retry.setAttribute("aria-busy", "true");
|
|
207
|
+
await loadSessionDetail(retry.dataset.retryDetail, true);
|
|
121
208
|
return;
|
|
122
209
|
}
|
|
123
210
|
const latest = event.target.closest("[data-scroll-latest]");
|
|
@@ -127,33 +214,55 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
127
214
|
return;
|
|
128
215
|
}
|
|
129
216
|
const stop = event.target.closest("[data-stop-run]");
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
217
|
+
if (stop) await controlManagedRun(state.selectedId, "stop");
|
|
218
|
+
});
|
|
219
|
+
$("#detailDrawer").addEventListener("input", (event) => {
|
|
220
|
+
const input = event.target.closest("[data-agent-command-draft]");
|
|
221
|
+
if (input) state.agentCommandDrafts.set(input.dataset.agentCommandDraft, input.value);
|
|
222
|
+
});
|
|
223
|
+
$("#detailDrawer").addEventListener("change", (event) => {
|
|
224
|
+
const picker = event.target.closest("[data-agent-command-target]");
|
|
225
|
+
if (!picker) return;
|
|
226
|
+
if (picker.value) state.agentCommandTargets.set(picker.dataset.agentCommandTarget, picker.value);
|
|
227
|
+
else state.agentCommandTargets.delete(picker.dataset.agentCommandTarget);
|
|
228
|
+
picker.closest("form")?.querySelectorAll("button").forEach(button => { button.disabled = !picker.value; });
|
|
229
|
+
});
|
|
230
|
+
$("#detailDrawer").addEventListener("keydown", (event) => {
|
|
231
|
+
const input = event.target.closest("[data-agent-command-draft]");
|
|
232
|
+
if (!input || event.key !== "Enter" || event.shiftKey || event.isComposing || event.keyCode === 229) return;
|
|
233
|
+
event.preventDefault();
|
|
234
|
+
input.closest("form")?.requestSubmit();
|
|
235
|
+
});
|
|
236
|
+
$("#detailDrawer").addEventListener("submit", (event) => {
|
|
237
|
+
const form = event.target.closest("[data-agent-command-form]");
|
|
238
|
+
if (!form) return;
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
dispatchAgentCommand(form.dataset.agentCommandForm, form);
|
|
144
241
|
});
|
|
145
242
|
document.addEventListener("keydown", (event) => {
|
|
146
243
|
trapDialogFocus(event);
|
|
147
|
-
|
|
244
|
+
const editable = event.target instanceof HTMLElement && Boolean(event.target.closest("input, textarea, select, [contenteditable='true']"));
|
|
245
|
+
const dialogOpen = Boolean(currentDialog?.());
|
|
246
|
+
const viewShortcuts = ["all", "active", "waiting", "runtime", "terminal", "tmux", "settings"];
|
|
247
|
+
if (!editable && !dialogOpen && (event.metaKey || event.ctrlKey) && /^[1-7]$/.test(event.key)) {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
selectView(viewShortcuts[Number(event.key) - 1], { focusMain: true });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!editable && !dialogOpen && event.key === "/" && !event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
if (state.view !== "all") selectView("all");
|
|
255
|
+
$("#searchInput").focus();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!editable && !dialogOpen && event.key.toLowerCase() === "n" && (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey) {
|
|
148
259
|
event.preventDefault();
|
|
149
260
|
if ($("#runModal").classList.contains("hidden")) openRunModal();
|
|
150
261
|
return;
|
|
151
262
|
}
|
|
152
263
|
if (event.key !== "Escape") return;
|
|
153
264
|
if (!$("#mobileToolsMenu").classList.contains("hidden")) {
|
|
154
|
-
$("#
|
|
155
|
-
$("#mobileMoreBtn").setAttribute("aria-expanded", "false");
|
|
156
|
-
$("#mobileMoreBtn").focus();
|
|
265
|
+
$("#mobileToolsCloseBtn")?.click();
|
|
157
266
|
} else if (!$("#runModal").classList.contains("hidden")) closeRunModal();
|
|
158
267
|
else closeDrawer();
|
|
159
268
|
});
|
|
@@ -3,42 +3,137 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createFilterEventBindings = function createFilterEventBindings(context = {}) {
|
|
6
|
-
const
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
7
|
+
const { $, state, setProviderVisible = () => {}, visibleSnapshot = () => state.snapshot, closeDrawer = () => {}, openDrawer = () => {}, renderSessions, render, renderWorkspaces, renderProviderOverview, renderProviderFilter, toggleProviderFilter, announceProviderFilter, filteredSessions, performUiAction, toast, announce, normalizedSearch = (value) => String(value || "").trim(), saveDashboardPreferences = () => {}, restoreDialogTrigger = () => {}, setDialogOpenState = () => {} } = context;
|
|
7
8
|
|
|
8
9
|
function bindFilterAndWorkspaceEvents() {
|
|
10
|
+
const syncFilterResetButton = () => {
|
|
11
|
+
const hasFilters = Boolean(
|
|
12
|
+
$("#searchInput").value || state.search || state.providerFilters.size || state.workspace !== "all" || state.sort !== "recent",
|
|
13
|
+
);
|
|
14
|
+
$("#resetFiltersBtn").classList.toggle("hidden", !hasFilters);
|
|
15
|
+
};
|
|
16
|
+
const moveFocus = (event, container, selector, previousKeys, nextKeys) => {
|
|
17
|
+
if (![...previousKeys, ...nextKeys, "Home", "End"].includes(event.key)) return false;
|
|
18
|
+
const items = Array.from(container.querySelectorAll(selector)).filter((item) => !item.disabled && !item.hidden);
|
|
19
|
+
if (!items.length) return false;
|
|
20
|
+
const current = Math.max(0, items.indexOf(event.target.closest(selector)));
|
|
21
|
+
const next = event.key === "Home"
|
|
22
|
+
? 0
|
|
23
|
+
: event.key === "End"
|
|
24
|
+
? items.length - 1
|
|
25
|
+
: (current + (nextKeys.includes(event.key) ? 1 : -1) + items.length) % items.length;
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
items[next].focus();
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
9
30
|
$("#loadMoreBtn").addEventListener("click", () => {
|
|
31
|
+
const previousCount = document.querySelectorAll("#sessionGrid [data-session-id]").length;
|
|
10
32
|
state.visibleLimit += 30;
|
|
11
33
|
renderSessions("load-more");
|
|
34
|
+
const cards = document.querySelectorAll("#sessionGrid [data-session-id]");
|
|
35
|
+
cards[Math.min(previousCount, cards.length - 1)]?.focus({ preventScroll: true });
|
|
36
|
+
announce(window.LoadToAgentI18n.t("filter.more_loaded", { count: Math.max(0, cards.length - previousCount) }));
|
|
12
37
|
});
|
|
13
|
-
$("#workspaceList")
|
|
38
|
+
const workspaceLists = [$("#workspaceList"), $("#mobileWorkspaceList")].filter(Boolean);
|
|
39
|
+
const handleWorkspaceClick = async (event) => {
|
|
40
|
+
const activeList = event.currentTarget;
|
|
14
41
|
const remove = event.target.closest("[data-remove-workspace]");
|
|
15
42
|
if (remove) {
|
|
16
43
|
event.stopPropagation();
|
|
17
|
-
const
|
|
44
|
+
const path = remove.dataset.removeWorkspace;
|
|
45
|
+
const workspaceItems = Array.from(activeList.querySelectorAll("[data-workspace]"));
|
|
46
|
+
const workspaceIndex = Math.max(0, workspaceItems.indexOf(remove.closest(".workspace-row")?.querySelector("[data-workspace]")));
|
|
47
|
+
const workspaces = await performUiAction(() => window.loadtoagent.removeWorkspace(remove.dataset.removeWorkspace), t("workspace.remove_failed"), remove);
|
|
18
48
|
if (!workspaces) return;
|
|
19
49
|
state.workspaces = workspaces;
|
|
20
50
|
if (state.workspace === remove.dataset.removeWorkspace) state.workspace = "all";
|
|
21
51
|
render();
|
|
52
|
+
syncFilterResetButton();
|
|
53
|
+
saveDashboardPreferences();
|
|
54
|
+
requestAnimationFrame(() => {
|
|
55
|
+
const nextItems = Array.from(activeList.querySelectorAll("[data-workspace]"));
|
|
56
|
+
nextItems[Math.min(workspaceIndex, nextItems.length - 1)]?.focus();
|
|
57
|
+
});
|
|
58
|
+
announce(window.LoadToAgentI18n.t("quality.workspace_removed", { name: path.split(/[\\/]/).filter(Boolean).pop() || path }));
|
|
22
59
|
return;
|
|
23
60
|
}
|
|
24
61
|
const item = event.target.closest("[data-workspace]");
|
|
25
62
|
if (item) {
|
|
63
|
+
const label = item.querySelector("strong")?.textContent.trim() || t("project.all");
|
|
26
64
|
state.workspace = item.dataset.workspace;
|
|
27
65
|
state.visibleLimit = 30;
|
|
28
66
|
renderWorkspaces();
|
|
29
67
|
renderSessions("filter");
|
|
68
|
+
syncFilterResetButton();
|
|
69
|
+
saveDashboardPreferences();
|
|
70
|
+
announce(t("filter.workspace_results", { project: label, count: filteredSessions().length }));
|
|
71
|
+
if (activeList.id === "mobileWorkspaceList") {
|
|
72
|
+
const menu = $("#mobileToolsMenu");
|
|
73
|
+
setDialogOpenState(menu, false);
|
|
74
|
+
menu?.classList.add("hidden");
|
|
75
|
+
$("#mobileMoreBtn")?.setAttribute("aria-expanded", "false");
|
|
76
|
+
const focusResults = () => ($("#liveSessionGrid")?.querySelector("[data-graph-focus], [data-open-session]")
|
|
77
|
+
|| $("#sessionGrid")?.querySelector("[data-session-id]")
|
|
78
|
+
|| $("#mainContent"))?.focus({ preventScroll: true });
|
|
79
|
+
restoreDialogTrigger();
|
|
80
|
+
focusResults();
|
|
81
|
+
requestAnimationFrame(focusResults);
|
|
82
|
+
}
|
|
30
83
|
}
|
|
84
|
+
};
|
|
85
|
+
workspaceLists.forEach((list) => {
|
|
86
|
+
list.addEventListener("click", handleWorkspaceClick);
|
|
87
|
+
list.addEventListener("keydown", (event) => {
|
|
88
|
+
moveFocus(event, event.currentTarget, "[data-workspace]", ["ArrowUp"], ["ArrowDown"]);
|
|
89
|
+
});
|
|
31
90
|
});
|
|
32
91
|
let searchTimer = null;
|
|
33
92
|
$("#searchInput").addEventListener("input", (event) => {
|
|
34
93
|
clearTimeout(searchTimer);
|
|
35
94
|
const value = event.target.value;
|
|
95
|
+
$("#searchClearBtn").classList.toggle("hidden", !value);
|
|
96
|
+
syncFilterResetButton();
|
|
36
97
|
searchTimer = setTimeout(() => {
|
|
37
|
-
state.search = value;
|
|
98
|
+
state.search = normalizedSearch(value);
|
|
38
99
|
state.visibleLimit = 30;
|
|
39
100
|
renderSessions("filter");
|
|
101
|
+
announce(window.LoadToAgentI18n.t("filter.search_results", { count: filteredSessions().length }));
|
|
102
|
+
syncFilterResetButton();
|
|
103
|
+
saveDashboardPreferences();
|
|
40
104
|
}, 120);
|
|
41
105
|
});
|
|
106
|
+
$("#searchClearBtn").addEventListener("click", () => {
|
|
107
|
+
clearTimeout(searchTimer);
|
|
108
|
+
$("#searchInput").value = "";
|
|
109
|
+
$("#searchClearBtn").classList.add("hidden");
|
|
110
|
+
state.search = "";
|
|
111
|
+
state.visibleLimit = 30;
|
|
112
|
+
renderSessions("filter");
|
|
113
|
+
announce(window.LoadToAgentI18n.t("filter.search_cleared"));
|
|
114
|
+
$("#searchInput").focus();
|
|
115
|
+
syncFilterResetButton();
|
|
116
|
+
saveDashboardPreferences();
|
|
117
|
+
});
|
|
118
|
+
$("#searchInput").addEventListener("keydown", (event) => {
|
|
119
|
+
if (event.key === "Escape" && event.currentTarget.value) {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
$("#searchClearBtn").click();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (event.key === "ArrowDown") {
|
|
125
|
+
const first = $("#sessionGrid [data-session-id]") || $("#liveSessionGrid button, #liveSessionGrid [tabindex='0']");
|
|
126
|
+
if (first) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
first.focus();
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (event.key === "Enter" && filteredSessions().length === 1) {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
openDrawer(filteredSessions()[0].id);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
42
137
|
$("#providerFilter").addEventListener("click", (event) => {
|
|
43
138
|
const chip = event.target.closest("[data-provider-filter]");
|
|
44
139
|
if (!chip) return;
|
|
@@ -48,26 +143,94 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
48
143
|
renderProviderOverview();
|
|
49
144
|
renderSessions("filter");
|
|
50
145
|
announceProviderFilter();
|
|
51
|
-
|
|
146
|
+
const next = $("#providerFilter").querySelector(`[data-provider-filter="${CSS.escape(chip.dataset.providerFilter)}"]`);
|
|
147
|
+
next?.classList.add("filter-clicked");
|
|
148
|
+
next?.focus();
|
|
149
|
+
syncFilterResetButton();
|
|
150
|
+
saveDashboardPreferences();
|
|
151
|
+
});
|
|
152
|
+
$("#providerFilter").addEventListener("keydown", (event) => {
|
|
153
|
+
moveFocus(event, event.currentTarget, "[data-provider-filter]", ["ArrowLeft", "ArrowUp"], ["ArrowRight", "ArrowDown"]);
|
|
52
154
|
});
|
|
53
155
|
$("#sortSelect").addEventListener("change", (event) => {
|
|
54
156
|
state.sort = event.target.value;
|
|
55
157
|
state.visibleLimit = 30;
|
|
56
158
|
renderSessions("filter");
|
|
159
|
+
const label = event.target.selectedOptions[0]?.textContent || event.target.value;
|
|
160
|
+
announce(window.LoadToAgentI18n.t("filter.sort_changed", { sort: label, count: filteredSessions().length }));
|
|
161
|
+
syncFilterResetButton();
|
|
162
|
+
saveDashboardPreferences();
|
|
57
163
|
});
|
|
58
|
-
$("#
|
|
59
|
-
|
|
164
|
+
$("#resetFiltersBtn").addEventListener("click", () => {
|
|
165
|
+
clearTimeout(searchTimer);
|
|
166
|
+
state.search = "";
|
|
167
|
+
state.providerFilters.clear();
|
|
168
|
+
state.workspace = "all";
|
|
169
|
+
state.sort = "recent";
|
|
170
|
+
state.visibleLimit = 30;
|
|
171
|
+
$("#searchInput").value = "";
|
|
172
|
+
$("#searchClearBtn").classList.add("hidden");
|
|
173
|
+
$("#sortSelect").value = "recent";
|
|
174
|
+
renderWorkspaces();
|
|
175
|
+
renderProviderFilter();
|
|
176
|
+
renderProviderOverview();
|
|
177
|
+
renderSessions("filter");
|
|
178
|
+
syncFilterResetButton();
|
|
179
|
+
saveDashboardPreferences();
|
|
180
|
+
announce(window.LoadToAgentI18n.t("filter.reset_done", { count: filteredSessions().length }));
|
|
181
|
+
$("#searchInput").focus();
|
|
182
|
+
});
|
|
183
|
+
$("#providerVisibilityList").addEventListener("change", async (event) => {
|
|
184
|
+
const input = event.target.closest("[data-provider-visibility]");
|
|
185
|
+
if (!input) return;
|
|
186
|
+
const providerId = input.dataset.providerVisibility;
|
|
187
|
+
const previousVisible = !input.checked;
|
|
188
|
+
const selectedBeforeChange = (state.rawSnapshot?.sessions || []).find((session) => session.id === state.selectedId)
|
|
189
|
+
|| state.details.get(state.selectedId);
|
|
190
|
+
setProviderVisible(providerId, input.checked);
|
|
191
|
+
state.visibleLimit = 30;
|
|
192
|
+
if (state.selectedId && selectedBeforeChange && state.hiddenProviders.has(selectedBeforeChange.provider)) closeDrawer();
|
|
193
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.updateSnapshot(visibleSnapshot(), state.workspaces);
|
|
194
|
+
render("filter");
|
|
195
|
+
try {
|
|
196
|
+
await Promise.resolve(window.loadtoagent.setProviderVisibility?.({ hidden: [...state.hiddenProviders] }));
|
|
197
|
+
} catch (error) {
|
|
198
|
+
window.LoadToAgentRendererUtils.reportRecoverableError("provider-visibility-persistence", error);
|
|
199
|
+
setProviderVisible(providerId, previousVisible);
|
|
200
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.updateSnapshot(visibleSnapshot(), state.workspaces);
|
|
201
|
+
render("filter");
|
|
202
|
+
toast(t("settings.providers.save_failed"));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
toast(t(input.checked ? "settings.providers.shown_toast" : "settings.providers.hidden_toast"));
|
|
206
|
+
});
|
|
207
|
+
const addWorkspaceButtons = [$("#addWorkspaceBtn"), $("#mobileAddWorkspaceBtn")].filter(Boolean);
|
|
208
|
+
const addWorkspace = async (event) => {
|
|
209
|
+
const trigger = event.currentTarget;
|
|
210
|
+
const previousPaths = new Set(state.workspaces.map((workspace) => workspace.path));
|
|
211
|
+
const workspaces = await performUiAction(() => window.loadtoagent.addWorkspaces(), t("workspace.add_failed"), trigger);
|
|
60
212
|
if (!workspaces) return;
|
|
61
213
|
state.workspaces = workspaces;
|
|
62
214
|
renderWorkspaces();
|
|
63
|
-
|
|
215
|
+
syncFilterResetButton();
|
|
216
|
+
const added = state.workspaces.find((workspace) => !previousPaths.has(workspace.path));
|
|
217
|
+
requestAnimationFrame(() => {
|
|
218
|
+
const targetList = trigger.id === "mobileAddWorkspaceBtn" ? $("#mobileWorkspaceList") : $("#workspaceList");
|
|
219
|
+
if (added) targetList?.querySelector(`[data-workspace="${CSS.escape(added.path)}"]`)?.focus();
|
|
220
|
+
else trigger.focus();
|
|
221
|
+
});
|
|
222
|
+
announce(window.LoadToAgentI18n.t("quality.workspace_added", { count: state.workspaces.length }));
|
|
223
|
+
};
|
|
224
|
+
addWorkspaceButtons.forEach((button) => button.addEventListener("click", addWorkspace));
|
|
64
225
|
$("#probeBtn").addEventListener("click", async () => {
|
|
65
|
-
const nextAvailability = await performUiAction(() => window.loadtoagent.probeProviders(), "
|
|
226
|
+
const nextAvailability = await performUiAction(() => window.loadtoagent.probeProviders(), t("run.cli_check_failed"), $("#probeBtn"));
|
|
66
227
|
if (!nextAvailability) return;
|
|
67
228
|
state.availability = nextAvailability;
|
|
68
229
|
render();
|
|
69
230
|
toast(window.LoadToAgentI18n.t("ui.ai_cli_connections_were_checked_again"));
|
|
70
231
|
});
|
|
232
|
+
$("#searchClearBtn").classList.toggle("hidden", !$("#searchInput").value);
|
|
233
|
+
syncFilterResetButton();
|
|
71
234
|
}
|
|
72
235
|
|
|
73
236
|
return { bindFilterAndWorkspaceEvents };
|