loadtoagent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.ko.md +175 -0
  2. package/README.md +175 -0
  3. package/README.zh-CN.md +175 -0
  4. package/bin/loadtoagent.js +171 -0
  5. package/docs/ARCHITECTURE.md +30 -0
  6. package/docs/PROVIDER-CONTRACTS.md +58 -0
  7. package/docs/assets/loadtoagent-dashboard.png +0 -0
  8. package/docs/assets/loadtoagent-demo.gif +0 -0
  9. package/main.js +493 -0
  10. package/package.json +133 -0
  11. package/preload.js +75 -0
  12. package/renderer/app-agent-actions.js +285 -0
  13. package/renderer/app-bootstrap.js +95 -0
  14. package/renderer/app-dashboard.js +361 -0
  15. package/renderer/app-drawer-content.js +203 -0
  16. package/renderer/app-drawer-data.js +41 -0
  17. package/renderer/app-drawer.js +183 -0
  18. package/renderer/app-events-dialogs.js +169 -0
  19. package/renderer/app-events-filters.js +74 -0
  20. package/renderer/app-events-navigation.js +96 -0
  21. package/renderer/app-events-sessions.js +195 -0
  22. package/renderer/app-events.js +16 -0
  23. package/renderer/app-graph-layout.js +71 -0
  24. package/renderer/app-graph-model.js +107 -0
  25. package/renderer/app-graph-orchestration.js +76 -0
  26. package/renderer/app-graph-view.js +544 -0
  27. package/renderer/app-run-modal.js +221 -0
  28. package/renderer/app-session-render.js +243 -0
  29. package/renderer/app-tmux-render.js +247 -0
  30. package/renderer/app.js +608 -0
  31. package/renderer/fonts/geist-ext.woff2 +0 -0
  32. package/renderer/fonts/geist.woff2 +0 -0
  33. package/renderer/i18n-messages.js +355 -0
  34. package/renderer/i18n.js +122 -0
  35. package/renderer/index.html +386 -0
  36. package/renderer/shared.js +26 -0
  37. package/renderer/styles-agent-map.css +401 -0
  38. package/renderer/styles-cards.css +600 -0
  39. package/renderer/styles-collaboration.css +534 -0
  40. package/renderer/styles-components.css +1293 -0
  41. package/renderer/styles-onboarding.css +344 -0
  42. package/renderer/styles-overlays.css +284 -0
  43. package/renderer/styles-product.css +686 -0
  44. package/renderer/styles-responsive-product.css +689 -0
  45. package/renderer/styles-responsive-runtime.css +718 -0
  46. package/renderer/styles-responsive-shell.css +533 -0
  47. package/renderer/styles-responsive-workflows.css +778 -0
  48. package/renderer/styles-run-composer.css +695 -0
  49. package/renderer/styles-settings.css +606 -0
  50. package/renderer/styles-terminal.css +1241 -0
  51. package/renderer/styles-tmux.css +1162 -0
  52. package/renderer/styles-workflow-map.css +513 -0
  53. package/renderer/styles-workflows.css +1217 -0
  54. package/renderer/styles.css +486 -0
  55. package/renderer/terminal-agent.js +152 -0
  56. package/renderer/terminal-events.js +226 -0
  57. package/renderer/terminal-workbench.js +464 -0
  58. package/renderer/terminal.js +497 -0
  59. package/src/agentMonitor/claudeParser.js +197 -0
  60. package/src/agentMonitor/codexCollaboration.js +95 -0
  61. package/src/agentMonitor/codexParser.js +447 -0
  62. package/src/agentMonitor/genericParser.js +244 -0
  63. package/src/agentMonitor/hierarchy.js +248 -0
  64. package/src/agentMonitor/sessionFiles.js +86 -0
  65. package/src/agentMonitor.js +591 -0
  66. package/src/agentRunner.js +465 -0
  67. package/src/bridgeServer.js +246 -0
  68. package/src/contracts.js +71 -0
  69. package/src/diagnostics.js +23 -0
  70. package/src/ipc/registerAgentIpc.js +12 -0
  71. package/src/ipc/registerAppIpc.js +20 -0
  72. package/src/ipc/registerTerminalIpc.js +50 -0
  73. package/src/ipc/registerTmuxIpc.js +30 -0
  74. package/src/ipc/registerWorkspaceIpc.js +14 -0
  75. package/src/monitorWorker.js +273 -0
  76. package/src/processMonitor.js +394 -0
  77. package/src/providerRegistry.js +120 -0
  78. package/src/terminalManager.js +438 -0
  79. package/src/tmuxController.js +183 -0
  80. package/src/tmuxMonitor.js +432 -0
  81. package/src/updateManager.js +339 -0
  82. package/src/workspaceStore.js +77 -0
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+
3
+ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
+
5
+ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}) {
6
+ const {
7
+ $, $$, esc, state, motionPreference, motionState, STATUS, markGuideStep, rememberDialogTrigger, restoreDialogTrigger,
8
+ providerInfo, isLiveSession, subagentWorkState, subagentWorkLabel, isProjectlessSession, sessionWorkspaceLabel,
9
+ agentResumeSupport, originAppInfo, selectedSession, snapshotSession, loadSessionDetail, loadSubagentParentDetail,
10
+ chatHtml, lifecycleHtml, tokensHtml, subagentCommunicationEvents, subagentConversationHtml,
11
+ } = context;
12
+
13
+ function openDrawer(id) {
14
+ rememberDialogTrigger();
15
+ markGuideStep("detail");
16
+ state.selectedId = id;
17
+ state.drawerMode = "session";
18
+ state.drawerTab = "chat";
19
+ state.drawerForceLatest = true;
20
+ clearTimeout(motionState.drawerTimer);
21
+ $("#drawerBackdrop").classList.remove("hidden");
22
+ $("#drawerBackdrop").classList.remove("closing");
23
+ $("#detailDrawer").classList.add("open");
24
+ $("#detailDrawer").removeAttribute("inert");
25
+ $("#detailDrawer").setAttribute("aria-hidden", "false");
26
+ renderDrawer();
27
+ loadSessionDetail(id, true);
28
+ setTimeout(() => $("#closeDrawerBtn").focus(), 0);
29
+ }
30
+
31
+ function openSubagentConversation(id) {
32
+ const child = snapshotSession(id) || state.details.get(id);
33
+ if (!child || !child.parentId) return openDrawer(id);
34
+ rememberDialogTrigger();
35
+ markGuideStep("detail");
36
+ state.selectedId = id;
37
+ state.drawerMode = "subagent";
38
+ state.drawerTab = "chat";
39
+ state.drawerForceLatest = true;
40
+ clearTimeout(motionState.drawerTimer);
41
+ $("#drawerBackdrop").classList.remove("hidden");
42
+ $("#drawerBackdrop").classList.remove("closing");
43
+ $("#detailDrawer").classList.add("open");
44
+ $("#detailDrawer").removeAttribute("inert");
45
+ $("#detailDrawer").setAttribute("aria-hidden", "false");
46
+ renderDrawer();
47
+ loadSubagentParentDetail(child);
48
+ setTimeout(() => $("#closeDrawerBtn").focus(), 0);
49
+ }
50
+
51
+ function closeDrawer(restoreFocus = true) {
52
+ if (!$("#detailDrawer").classList.contains("open")) return;
53
+ $("#detailDrawer").classList.remove("open");
54
+ $("#detailDrawer").setAttribute("aria-hidden", "true");
55
+ $("#detailDrawer").setAttribute("inert", "");
56
+ $("#drawerBackdrop").classList.add("closing");
57
+ clearTimeout(motionState.drawerTimer);
58
+ motionState.drawerTimer = setTimeout(
59
+ () => {
60
+ $("#drawerBackdrop").classList.add("hidden");
61
+ $("#drawerBackdrop").classList.remove("closing");
62
+ if (restoreFocus) restoreDialogTrigger();
63
+ else motionState.activeDialogTrigger = null;
64
+ },
65
+ motionPreference.matches ? 0 : 260,
66
+ );
67
+ }
68
+
69
+ function renderDrawer() {
70
+ const session = selectedSession();
71
+ if (!session) return closeDrawer();
72
+ const provider = providerInfo(session.provider);
73
+ const subagentMode = state.drawerMode === "subagent" && Boolean(session.parentId);
74
+ const detailLoading = !subagentMode && state.detailLoadingIds.has(state.selectedId);
75
+ $("#detailDrawer").style.setProperty("--drawer-provider", provider.accent);
76
+ $("#drawerProviderMark").style.setProperty("--provider", provider.accent);
77
+ $("#drawerProviderMark").textContent = provider.mark;
78
+ $("#drawerProvider").textContent = subagentMode
79
+ ? `${session.agentName || provider.label} · 메인 AI와의 소통`
80
+ : `${provider.company} · ${STATUS[session.status] || session.status}`;
81
+ $("#drawerTitle").textContent = subagentMode ? session.taskName || (session.delegation && session.delegation.taskName) || session.title : session.title;
82
+ const stopping = session.runId && state.stopRequests.has(session.runId);
83
+ const stop =
84
+ session.runId && (session.status === "running" || session.status === "starting")
85
+ ? `<button class="meta-chip stop-run" data-stop-run="${esc(session.runId)}"
86
+ ${stopping ? 'disabled aria-busy="true"' : ""}>
87
+ ${stopping ? "중지 요청 중…" : "■ 실행 중지"}</button>`
88
+ : "";
89
+ const runtime = session.runtimePresence || [];
90
+ const resume =
91
+ !isLiveSession(session) && agentResumeSupport(session).supported
92
+ ? `<button class="meta-chip resume-agent" data-resume-agent="${esc(session.id)}">▶
93
+ <b>${originAppInfo(session) ? "백그라운드 터미널로 이어가기" : "터미널로 다시 일 시키기"}</b>
94
+ </button>`
95
+ : "";
96
+ const communicationCount = subagentMode ? subagentCommunicationEvents(session).length : 0;
97
+ $("#drawerMeta").innerHTML = subagentMode
98
+ ? `<span class="meta-chip work-state ${subagentWorkState(session)}">
99
+ <b>${esc(subagentWorkLabel(session))}</b>
100
+ </span>
101
+ <span class="meta-chip">사용 모델 <b>${esc(session.model || "정보 없음")}</b>
102
+ </span>
103
+ <span class="meta-chip">메인과 소통 <b>${communicationCount}건</b>
104
+ </span>${resume}`
105
+ : `<span class="meta-chip">사용 모델 <b>${esc(session.model || "정보 없음")}</b>
106
+ </span>
107
+ <span class="meta-chip">작업 폴더
108
+ <b title="${esc(isProjectlessSession(session) ? window.LoadToAgentI18n.t("ui.session_not_linked_to_a_specific_project") : session.cwd)}">
109
+ ${esc(sessionWorkspaceLabel(session))}
110
+ </b>
111
+ </span>
112
+ <span class="meta-chip">작업 번호 <b>${esc(String(session.externalId || "").slice(0, 12) || "정보 없음")}</b>
113
+ </span>${
114
+ session.parentId
115
+ ? `<span class="meta-chip">⑂ <b>도움을 맡은 AI</b>
116
+ </span>`
117
+ : ""
118
+ }${
119
+ runtime.length
120
+ ? `<span class="meta-chip runtime-meta">● <b>실행 중인 프로그램 ${runtime.length}개</b>
121
+ </span>`
122
+ : ""
123
+ }${resume}${stop}`;
124
+ $$(".drawer-tab").forEach((tab) => {
125
+ const hidden = subagentMode && tab.dataset.tab !== "chat";
126
+ tab.classList.toggle("hidden", hidden);
127
+ if (tab.dataset.tab === "chat") tab.textContent = subagentMode ? "메인과의 대화" : window.LoadToAgentI18n.t("ui.conversation");
128
+ const active = tab.dataset.tab === state.drawerTab;
129
+ tab.classList.toggle("active", active);
130
+ tab.setAttribute("aria-selected", active ? "true" : "false");
131
+ tab.tabIndex = active ? 0 : -1;
132
+ });
133
+ const activeTab = $(`.drawer-tab[data-tab="${state.drawerTab}"]`);
134
+ if (activeTab) $("#drawerContent").setAttribute("aria-labelledby", activeTab.id);
135
+ const content = $("#drawerContent");
136
+ const previousTop = content.scrollTop;
137
+ const wasNearBottom = content.scrollHeight - content.scrollTop - content.clientHeight < 90;
138
+ const renderKey = `${state.drawerMode}:${state.selectedId}:${state.drawerTab}:${detailLoading ? "loading" : "ready"}`;
139
+ const previousRenderKey = motionState.drawerRenderKey;
140
+ const shouldAnimateContent = previousRenderKey !== renderKey;
141
+ const tabChanged = Boolean(motionState.drawerTab) && motionState.drawerTab !== state.drawerTab;
142
+ motionState.drawerRenderKey = renderKey;
143
+ motionState.drawerTab = state.drawerTab;
144
+ const detailError = state.detailErrors.get(state.selectedId);
145
+ content.innerHTML = detailLoading
146
+ ? '<div class="drawer-loading"><span></span><b>전체 작업 기록을 불러오는 중</b><small>잠시만 기다리면 대화와 진행 과정을 볼 수 있어요.</small></div>'
147
+ : detailError && !subagentMode
148
+ ? `<div class="drawer-error">
149
+ <b>작업 기록을 불러오지 못했습니다</b>
150
+ <span>${esc(detailError)}</span>
151
+ <button type="button" data-retry-detail="${esc(state.selectedId)}">다시 시도</button>
152
+ </div>`
153
+ : subagentMode
154
+ ? subagentConversationHtml(session)
155
+ : state.drawerTab === "chat"
156
+ ? chatHtml(session)
157
+ : state.drawerTab === "lifecycle"
158
+ ? lifecycleHtml(session)
159
+ : tokensHtml(session);
160
+ content.classList.toggle("motion-content-in", shouldAnimateContent && !motionPreference.matches);
161
+ clearTimeout(motionState.drawerContentTimer);
162
+ if (shouldAnimateContent)
163
+ motionState.drawerContentTimer = setTimeout(() => content.classList.remove("motion-content-in"), motionPreference.matches ? 0 : 520);
164
+ if (!detailLoading)
165
+ requestAnimationFrame(() => {
166
+ const forceLatest = state.drawerForceLatest;
167
+ if (state.drawerTab === "chat" && forceLatest) {
168
+ const rows = [...content.querySelectorAll(".chat-row")];
169
+ const latest = rows[rows.length - 1];
170
+ if (latest && latest.offsetHeight > content.clientHeight - 90) {
171
+ const contentTop = content.getBoundingClientRect().top;
172
+ const stickyHeight = content.querySelector(".chat-history-head")?.getBoundingClientRect().height || 0;
173
+ content.scrollTop = Math.max(0, content.scrollTop + latest.getBoundingClientRect().top - contentTop - stickyHeight - 12);
174
+ } else content.scrollTop = content.scrollHeight;
175
+ } else if (tabChanged) content.scrollTop = 0;
176
+ else if (state.drawerTab === "chat" && wasNearBottom) content.scrollTop = content.scrollHeight;
177
+ else content.scrollTop = Math.min(previousTop, Math.max(0, content.scrollHeight - content.clientHeight));
178
+ state.drawerForceLatest = false;
179
+ });
180
+ }
181
+
182
+ return { openDrawer, openSubagentConversation, closeDrawer, renderDrawer };
183
+ };
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+
3
+ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
+
5
+ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialogEventBindings(context = {}) {
6
+ const {
7
+ $, $$, state, providerInfo, renderProviderRail, scheduleAgentWorkflowConnections, resumeAgentTerminal, loadSessionDetail,
8
+ closeDrawer, renderDrawer, providerPickerHtml, syncRunComposer, openRunModal, closeRunModal, toast, performUiAction,
9
+ handleRun, trapDialogFocus,
10
+ } = context;
11
+
12
+ function bindRunComposerEvents() {
13
+ $("#newRunBtn").addEventListener("click", openRunModal);
14
+ $$("[data-open-run]").forEach((button) => button.addEventListener("click", openRunModal));
15
+ $("#closeRunModalBtn").addEventListener("click", closeRunModal);
16
+ $("#cancelRunBtn").addEventListener("click", closeRunModal);
17
+ $("#runModal").addEventListener("click", (event) => {
18
+ if (event.target === $("#runModal")) closeRunModal();
19
+ });
20
+ $("#runProviderPicker").addEventListener("click", (event) => {
21
+ const button = event.target.closest("[data-run-provider]");
22
+ if (!button || button.disabled) return;
23
+ state.runProvider = button.dataset.runProvider;
24
+ $("#runProviderPicker").innerHTML = providerPickerHtml();
25
+ syncRunComposer();
26
+ });
27
+ $("#runProviderHelp").addEventListener("click", async (event) => {
28
+ const docs = event.target.closest("[data-provider-docs]");
29
+ if (docs) {
30
+ const provider = providerInfo(docs.dataset.providerDocs);
31
+ if (provider.docs)
32
+ await performUiAction(async () => {
33
+ const result = await window.loadtoagent.openExternal(provider.docs);
34
+ if (!result || result.ok === false) throw new Error("공식 설치 문서를 열지 못했습니다.");
35
+ }, "공식 설치 문서를 열지 못했습니다.");
36
+ return;
37
+ }
38
+ if (event.target.closest("[data-provider-recheck]")) {
39
+ const nextAvailability = await performUiAction(() => window.loadtoagent.probeProviders(), "AI CLI 연결 상태를 확인하지 못했습니다.");
40
+ if (!nextAvailability) return;
41
+ state.availability = nextAvailability;
42
+ const installed = state.providers.find((provider) => state.availability[provider.id]);
43
+ if (installed) state.runProvider = installed.id;
44
+ $("#runProviderPicker").innerHTML = providerPickerHtml();
45
+ renderProviderRail();
46
+ syncRunComposer();
47
+ toast(installed ? `${installed.label} CLI를 찾았습니다. 이제 작업을 시작할 수 있어요.` : "아직 설치된 AI CLI를 찾지 못했습니다. 설치와 로그인을 확인해 주세요.");
48
+ }
49
+ });
50
+ $("#runPrompt").addEventListener("input", syncRunComposer);
51
+ $(".run-prompt-examples").addEventListener("click", (event) => {
52
+ const example = event.target.closest("[data-run-prompt-example]");
53
+ if (!example) return;
54
+ const input = $("#runPrompt");
55
+ const text = example.dataset.runPromptExample;
56
+ if (!input.value.trim()) input.value = text;
57
+ else input.setRangeText(`${input.selectionStart ? "\n\n" : ""}${text}`, input.selectionStart, input.selectionEnd, "end");
58
+ syncRunComposer();
59
+ input.focus();
60
+ });
61
+ $("#runWorkspaceSuggestions").addEventListener("click", (event) => {
62
+ const workspace = event.target.closest("[data-run-workspace]");
63
+ if (!workspace) return;
64
+ $("#runCwd").value = workspace.dataset.runWorkspace;
65
+ syncRunComposer();
66
+ });
67
+ $("#runCwd").addEventListener("input", syncRunComposer);
68
+ $("#allowWrites").addEventListener("change", syncRunComposer);
69
+ $("#pickRunCwdBtn").addEventListener("click", async () => {
70
+ const folder = await performUiAction(() => window.loadtoagent.pickWorkspace(), "작업 폴더를 선택하지 못했습니다.");
71
+ if (folder) {
72
+ $("#runCwd").value = folder;
73
+ syncRunComposer();
74
+ }
75
+ });
76
+ $("#runForm").addEventListener("keydown", (event) => {
77
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
78
+ event.preventDefault();
79
+ $("#runForm").requestSubmit();
80
+ }
81
+ });
82
+ $("#runForm").addEventListener("submit", handleRun);
83
+ }
84
+
85
+ function bindDrawerAndGlobalEvents() {
86
+ $("#closeDrawerBtn").addEventListener("click", closeDrawer);
87
+ $("#drawerBackdrop").addEventListener("click", closeDrawer);
88
+ $(".drawer-tabs").addEventListener("click", (event) => {
89
+ const tab = event.target.closest("[data-tab]");
90
+ if (tab) {
91
+ state.drawerTab = tab.dataset.tab;
92
+ state.drawerForceLatest = tab.dataset.tab === "chat";
93
+ renderDrawer();
94
+ }
95
+ });
96
+ $(".drawer-tabs").addEventListener("keydown", (event) => {
97
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
98
+ const tabs = $$(".drawer-tab:not(.hidden)");
99
+ const current = Math.max(0, tabs.indexOf(event.target.closest(".drawer-tab")));
100
+ const next =
101
+ event.key === "Home"
102
+ ? 0
103
+ : event.key === "End"
104
+ ? tabs.length - 1
105
+ : (current + (event.key === "ArrowRight" ? 1 : -1) + tabs.length) % tabs.length;
106
+ event.preventDefault();
107
+ state.drawerTab = tabs[next].dataset.tab;
108
+ state.drawerForceLatest = state.drawerTab === "chat";
109
+ renderDrawer();
110
+ $(`.drawer-tab[data-tab="${state.drawerTab}"]`)?.focus();
111
+ });
112
+ $("#detailDrawer").addEventListener("click", async (event) => {
113
+ const resume = event.target.closest("[data-resume-agent]");
114
+ if (resume) {
115
+ await resumeAgentTerminal(resume.dataset.resumeAgent);
116
+ return;
117
+ }
118
+ const retry = event.target.closest("[data-retry-detail]");
119
+ if (retry) {
120
+ loadSessionDetail(retry.dataset.retryDetail, true);
121
+ return;
122
+ }
123
+ const latest = event.target.closest("[data-scroll-latest]");
124
+ if (latest) {
125
+ const content = $("#drawerContent");
126
+ content.scrollTo({ top: content.scrollHeight, behavior: "smooth" });
127
+ return;
128
+ }
129
+ const stop = event.target.closest("[data-stop-run]");
130
+ if (!stop) return;
131
+ const runId = stop.dataset.stopRun;
132
+ if (state.stopRequests.has(runId)) return;
133
+ state.stopRequests.add(runId);
134
+ renderDrawer();
135
+ try {
136
+ const result = await window.loadtoagent.stopAgent(runId);
137
+ toast(result.ok ? window.LoadToAgentI18n.t("ui.stop_request_sent") : result.error);
138
+ } catch (error) {
139
+ toast((error && error.message) || window.LoadToAgentI18n.t("ui.could_not_send_the_stop_request"));
140
+ } finally {
141
+ state.stopRequests.delete(runId);
142
+ if (state.selectedId) renderDrawer();
143
+ }
144
+ });
145
+ document.addEventListener("keydown", (event) => {
146
+ trapDialogFocus(event);
147
+ if (event.key.toLowerCase() === "n" && (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey) {
148
+ event.preventDefault();
149
+ if ($("#runModal").classList.contains("hidden")) openRunModal();
150
+ return;
151
+ }
152
+ if (event.key !== "Escape") return;
153
+ if (!$("#mobileToolsMenu").classList.contains("hidden")) {
154
+ $("#mobileToolsMenu").classList.add("hidden");
155
+ $("#mobileMoreBtn").setAttribute("aria-expanded", "false");
156
+ $("#mobileMoreBtn").focus();
157
+ } else if (!$("#runModal").classList.contains("hidden")) closeRunModal();
158
+ else closeDrawer();
159
+ });
160
+ window.addEventListener("resize", scheduleAgentWorkflowConnections);
161
+ }
162
+
163
+ function bindDialogAndGlobalEvents() {
164
+ bindRunComposerEvents();
165
+ bindDrawerAndGlobalEvents();
166
+ }
167
+
168
+ return { bindDialogAndGlobalEvents };
169
+ };
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+
3
+ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
+
5
+ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilterEventBindings(context = {}) {
6
+ const { $, state, renderSessions, render, renderWorkspaces, renderProviderOverview, renderProviderFilter, toggleProviderFilter, announceProviderFilter, performUiAction, toast } = context;
7
+
8
+ function bindFilterAndWorkspaceEvents() {
9
+ $("#loadMoreBtn").addEventListener("click", () => {
10
+ state.visibleLimit += 30;
11
+ renderSessions("load-more");
12
+ });
13
+ $("#workspaceList").addEventListener("click", async (event) => {
14
+ const remove = event.target.closest("[data-remove-workspace]");
15
+ if (remove) {
16
+ event.stopPropagation();
17
+ const workspaces = await performUiAction(() => window.loadtoagent.removeWorkspace(remove.dataset.removeWorkspace), "작업 폴더를 제거하지 못했습니다.");
18
+ if (!workspaces) return;
19
+ state.workspaces = workspaces;
20
+ if (state.workspace === remove.dataset.removeWorkspace) state.workspace = "all";
21
+ render();
22
+ return;
23
+ }
24
+ const item = event.target.closest("[data-workspace]");
25
+ if (item) {
26
+ state.workspace = item.dataset.workspace;
27
+ state.visibleLimit = 30;
28
+ renderWorkspaces();
29
+ renderSessions("filter");
30
+ }
31
+ });
32
+ let searchTimer = null;
33
+ $("#searchInput").addEventListener("input", (event) => {
34
+ clearTimeout(searchTimer);
35
+ const value = event.target.value;
36
+ searchTimer = setTimeout(() => {
37
+ state.search = value;
38
+ state.visibleLimit = 30;
39
+ renderSessions("filter");
40
+ }, 120);
41
+ });
42
+ $("#providerFilter").addEventListener("click", (event) => {
43
+ const chip = event.target.closest("[data-provider-filter]");
44
+ if (!chip) return;
45
+ toggleProviderFilter(chip.dataset.providerFilter);
46
+ state.visibleLimit = 30;
47
+ renderProviderFilter();
48
+ renderProviderOverview();
49
+ renderSessions("filter");
50
+ announceProviderFilter();
51
+ requestAnimationFrame(() => $("#providerFilter").querySelector(`[data-provider-filter="${CSS.escape(chip.dataset.providerFilter)}"]`)?.focus());
52
+ });
53
+ $("#sortSelect").addEventListener("change", (event) => {
54
+ state.sort = event.target.value;
55
+ state.visibleLimit = 30;
56
+ renderSessions("filter");
57
+ });
58
+ $("#addWorkspaceBtn").addEventListener("click", async () => {
59
+ const workspaces = await performUiAction(() => window.loadtoagent.addWorkspaces(), "작업 폴더를 추가하지 못했습니다.");
60
+ if (!workspaces) return;
61
+ state.workspaces = workspaces;
62
+ renderWorkspaces();
63
+ });
64
+ $("#probeBtn").addEventListener("click", async () => {
65
+ const nextAvailability = await performUiAction(() => window.loadtoagent.probeProviders(), "AI CLI 연결 상태를 확인하지 못했습니다.");
66
+ if (!nextAvailability) return;
67
+ state.availability = nextAvailability;
68
+ render();
69
+ toast(window.LoadToAgentI18n.t("ui.ai_cli_connections_were_checked_again"));
70
+ });
71
+ }
72
+
73
+ return { bindFilterAndWorkspaceEvents };
74
+ };
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+
3
+ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
+
5
+ window.LoadToAgentAppFactories.createNavigationEventBindings = function createNavigationEventBindings(context = {}) {
6
+ const {
7
+ $, state, motionPreference, saveGuideState, selectView, renderUpdateSettings,
8
+ filteredSessions, renderSessions, openRunModal, openDrawer, toast,
9
+ } = context;
10
+
11
+ function bindNavigationAndUpdateEvents() {
12
+ $(".view-nav").addEventListener("click", (event) => {
13
+ const button = event.target.closest(".nav-item");
14
+ if (!button || !button.dataset.view) return;
15
+ selectView(button.dataset.view);
16
+ });
17
+ $("#updateNoticeBtn").addEventListener("click", () => {
18
+ selectView("settings");
19
+ });
20
+ $("#guideBtn").addEventListener("click", () => {
21
+ state.guideExpanded = !state.guideExpanded || state.view !== "all";
22
+ saveGuideState();
23
+ if (state.view !== "all") selectView("all");
24
+ else renderSessions("guide");
25
+ if (state.guideExpanded) {
26
+ setTimeout(
27
+ () => $("#beginnerGuide").scrollIntoView({ behavior: motionPreference.matches ? "auto" : "smooth", block: "start" }),
28
+ 0,
29
+ );
30
+ }
31
+ });
32
+ $("#dismissGuideBtn").addEventListener("click", () => {
33
+ state.guideExpanded = false;
34
+ saveGuideState();
35
+ renderSessions("guide");
36
+ $("#guideBtn").focus();
37
+ });
38
+ $("#beginnerGuide").addEventListener("click", (event) => {
39
+ const action = event.target.closest("[data-guide-action]")?.dataset.guideAction;
40
+ if (!action) return;
41
+ if (action === "create") return openRunModal();
42
+ if (action === "active" || action === "waiting") return selectView(action, { focusMain: true });
43
+ if (action === "detail") {
44
+ const first = filteredSessions()[0] || ((state.snapshot && state.snapshot.sessions) || [])[0];
45
+ if (first) openDrawer(first.id);
46
+ else {
47
+ toast("아직 열어볼 작업이 없어요. 먼저 AI에게 첫 일을 맡겨보세요.");
48
+ openRunModal();
49
+ }
50
+ }
51
+ });
52
+ $("#mobileMoreBtn").addEventListener("click", () => {
53
+ const menu = $("#mobileToolsMenu");
54
+ const opening = menu.classList.contains("hidden");
55
+ menu.classList.toggle("hidden", !opening);
56
+ $("#mobileMoreBtn").setAttribute("aria-expanded", opening ? "true" : "false");
57
+ if (opening) setTimeout(() => menu.querySelector("button")?.focus(), 0);
58
+ });
59
+ $("#mobileToolsMenu").addEventListener("click", (event) => {
60
+ const button = event.target.closest("[data-mobile-view]");
61
+ if (button) selectView(button.dataset.mobileView, { focusMain: true });
62
+ });
63
+ $("#checkUpdateBtn").addEventListener("click", async () => {
64
+ try {
65
+ state.update = { ...(state.update || {}), status: "checking", error: "" };
66
+ renderUpdateSettings();
67
+ state.update = await window.loadtoagent.checkForUpdate();
68
+ renderUpdateSettings();
69
+ } catch (error) {
70
+ toast((error && error.message) || window.LoadToAgentI18n.t("ui.could_not_check_for_updates"));
71
+ }
72
+ });
73
+ $("#installUpdateBtn").addEventListener("click", async () => {
74
+ try {
75
+ if (state.update && state.update.status === "downloaded") {
76
+ await window.loadtoagent.openDownloadedUpdate();
77
+ toast("설치 파일을 열었습니다. 화면의 안내에 따라 업데이트를 마무리하세요.");
78
+ } else {
79
+ state.update = await window.loadtoagent.downloadUpdate();
80
+ renderUpdateSettings();
81
+ }
82
+ } catch (error) {
83
+ toast((error && error.message) || window.LoadToAgentI18n.t("ui.could_not_prepare_the_update_file"));
84
+ }
85
+ });
86
+ $("#openReleaseBtn").addEventListener("click", async () => {
87
+ try {
88
+ await window.loadtoagent.openUpdateRelease();
89
+ } catch (error) {
90
+ toast((error && error.message) || window.LoadToAgentI18n.t("ui.could_not_open_the_github_release_page"));
91
+ }
92
+ });
93
+ }
94
+
95
+ return { bindNavigationAndUpdateEvents };
96
+ };