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.
- package/README.ko.md +175 -0
- package/README.md +175 -0
- package/README.zh-CN.md +175 -0
- package/bin/loadtoagent.js +171 -0
- package/docs/ARCHITECTURE.md +30 -0
- package/docs/PROVIDER-CONTRACTS.md +58 -0
- package/docs/assets/loadtoagent-dashboard.png +0 -0
- package/docs/assets/loadtoagent-demo.gif +0 -0
- package/main.js +493 -0
- package/package.json +133 -0
- package/preload.js +75 -0
- package/renderer/app-agent-actions.js +285 -0
- package/renderer/app-bootstrap.js +95 -0
- package/renderer/app-dashboard.js +361 -0
- package/renderer/app-drawer-content.js +203 -0
- package/renderer/app-drawer-data.js +41 -0
- package/renderer/app-drawer.js +183 -0
- package/renderer/app-events-dialogs.js +169 -0
- package/renderer/app-events-filters.js +74 -0
- package/renderer/app-events-navigation.js +96 -0
- package/renderer/app-events-sessions.js +195 -0
- package/renderer/app-events.js +16 -0
- package/renderer/app-graph-layout.js +71 -0
- package/renderer/app-graph-model.js +107 -0
- package/renderer/app-graph-orchestration.js +76 -0
- package/renderer/app-graph-view.js +544 -0
- package/renderer/app-run-modal.js +221 -0
- package/renderer/app-session-render.js +243 -0
- package/renderer/app-tmux-render.js +247 -0
- package/renderer/app.js +608 -0
- package/renderer/fonts/geist-ext.woff2 +0 -0
- package/renderer/fonts/geist.woff2 +0 -0
- package/renderer/i18n-messages.js +355 -0
- package/renderer/i18n.js +122 -0
- package/renderer/index.html +386 -0
- package/renderer/shared.js +26 -0
- package/renderer/styles-agent-map.css +401 -0
- package/renderer/styles-cards.css +600 -0
- package/renderer/styles-collaboration.css +534 -0
- package/renderer/styles-components.css +1293 -0
- package/renderer/styles-onboarding.css +344 -0
- package/renderer/styles-overlays.css +284 -0
- package/renderer/styles-product.css +686 -0
- package/renderer/styles-responsive-product.css +689 -0
- package/renderer/styles-responsive-runtime.css +718 -0
- package/renderer/styles-responsive-shell.css +533 -0
- package/renderer/styles-responsive-workflows.css +778 -0
- package/renderer/styles-run-composer.css +695 -0
- package/renderer/styles-settings.css +606 -0
- package/renderer/styles-terminal.css +1241 -0
- package/renderer/styles-tmux.css +1162 -0
- package/renderer/styles-workflow-map.css +513 -0
- package/renderer/styles-workflows.css +1217 -0
- package/renderer/styles.css +486 -0
- package/renderer/terminal-agent.js +152 -0
- package/renderer/terminal-events.js +226 -0
- package/renderer/terminal-workbench.js +464 -0
- package/renderer/terminal.js +497 -0
- package/src/agentMonitor/claudeParser.js +197 -0
- package/src/agentMonitor/codexCollaboration.js +95 -0
- package/src/agentMonitor/codexParser.js +447 -0
- package/src/agentMonitor/genericParser.js +244 -0
- package/src/agentMonitor/hierarchy.js +248 -0
- package/src/agentMonitor/sessionFiles.js +86 -0
- package/src/agentMonitor.js +591 -0
- package/src/agentRunner.js +465 -0
- package/src/bridgeServer.js +246 -0
- package/src/contracts.js +71 -0
- package/src/diagnostics.js +23 -0
- package/src/ipc/registerAgentIpc.js +12 -0
- package/src/ipc/registerAppIpc.js +20 -0
- package/src/ipc/registerTerminalIpc.js +50 -0
- package/src/ipc/registerTmuxIpc.js +30 -0
- package/src/ipc/registerWorkspaceIpc.js +14 -0
- package/src/monitorWorker.js +273 -0
- package/src/processMonitor.js +394 -0
- package/src/providerRegistry.js +120 -0
- package/src/terminalManager.js +438 -0
- package/src/tmuxController.js +183 -0
- package/src/tmuxMonitor.js +432 -0
- package/src/updateManager.js +339 -0
- package/src/workspaceStore.js +77 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
|
+
|
|
5
|
+
window.LoadToAgentAppFactories.createRunModal = function createRunModal(context = {}) {
|
|
6
|
+
const {
|
|
7
|
+
$,
|
|
8
|
+
esc,
|
|
9
|
+
uiLocale,
|
|
10
|
+
state,
|
|
11
|
+
motionPreference,
|
|
12
|
+
motionState,
|
|
13
|
+
markGuideStep,
|
|
14
|
+
rememberDialogTrigger,
|
|
15
|
+
restoreDialogTrigger,
|
|
16
|
+
providerInfo,
|
|
17
|
+
providerStyle,
|
|
18
|
+
} = context;
|
|
19
|
+
|
|
20
|
+
function providerPickerHtml() {
|
|
21
|
+
return state.providers
|
|
22
|
+
.map((provider) => {
|
|
23
|
+
const installed = !!state.availability[provider.id];
|
|
24
|
+
const selected = state.runProvider === provider.id;
|
|
25
|
+
return `<button type="button" class="run-provider-option ${selected ? "selected" : ""}"
|
|
26
|
+
data-run-provider="${esc(provider.id)}"
|
|
27
|
+
style="${providerStyle(provider.id)}"
|
|
28
|
+
aria-pressed="${selected ? "true" : "false"}"
|
|
29
|
+
${installed ? "" : "disabled"}>
|
|
30
|
+
<span class="provider-mini-mark">${esc(provider.mark)}</span>
|
|
31
|
+
<span class="run-provider-copy">
|
|
32
|
+
<b>${esc(provider.label)}</b>
|
|
33
|
+
<small>${esc(installed ? `${provider.company} · CLI 발견됨` : window.LoadToAgentI18n.t("ui.setup_required"))}</small>
|
|
34
|
+
</span>
|
|
35
|
+
<span class="run-provider-check" aria-hidden="true">✓</span>
|
|
36
|
+
</button>`;
|
|
37
|
+
})
|
|
38
|
+
.join("");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function runProviderHelpHtml() {
|
|
42
|
+
const available = state.providers.filter((provider) => state.availability[provider.id]);
|
|
43
|
+
if (available.length) return "";
|
|
44
|
+
const docs = state.providers
|
|
45
|
+
.map(
|
|
46
|
+
(provider) => `<button type="button" data-provider-docs="${esc(provider.id)}">
|
|
47
|
+
<span class="provider-mini-mark" style="${providerStyle(provider.id)}">${esc(provider.mark)}</span>
|
|
48
|
+
<span>
|
|
49
|
+
<b>${esc(window.LoadToAgentI18n.t("provider.install_guide", { provider: provider.label }))}</b>
|
|
50
|
+
<small>공식 문서에서 CLI 설치와 로그인을 확인하세요.</small>
|
|
51
|
+
</span>
|
|
52
|
+
<i aria-hidden="true">↗</i>
|
|
53
|
+
</button>`,
|
|
54
|
+
)
|
|
55
|
+
.join("");
|
|
56
|
+
return `<div class="run-provider-help-copy">
|
|
57
|
+
<b>먼저 AI CLI 한 개를 준비해 주세요</b>
|
|
58
|
+
<p>1. 아래 공식 설치 안내 열기 → 2. 터미널에서 로그인 → 3. 설치 확인을 누르면 됩니다.</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="run-provider-docs">${docs}</div>
|
|
61
|
+
<button type="button" class="provider-recheck" data-provider-recheck>↻ 설치 상태 다시 확인</button>`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function runWorkspaceSuggestionsHtml() {
|
|
65
|
+
const selected = String(($("#runCwd") && $("#runCwd").value) || "");
|
|
66
|
+
return state.workspaces
|
|
67
|
+
.slice(0, 4)
|
|
68
|
+
.map((workspace) => {
|
|
69
|
+
const path = workspace.path || workspace.name || "";
|
|
70
|
+
const active = path === selected;
|
|
71
|
+
return `<button type="button" data-run-workspace="${esc(path)}" class="${active ? "selected" : ""}" title="${esc(path)}">
|
|
72
|
+
<span aria-hidden="true">⌘</span>
|
|
73
|
+
${esc(workspace.name || path.split(/[\\/]/).filter(Boolean).pop() || window.LoadToAgentI18n.t("ui.workspaces"))}
|
|
74
|
+
</button>`;
|
|
75
|
+
})
|
|
76
|
+
.join("");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function syncRunComposer() {
|
|
80
|
+
const prompt = $("#runPrompt");
|
|
81
|
+
const count = $("#runPromptCount");
|
|
82
|
+
if (prompt && count) count.textContent = `${prompt.value.length.toLocaleString(uiLocale())} / 8,000`;
|
|
83
|
+
const submitLabel = $("#runSubmitLabel");
|
|
84
|
+
const submit = $('#runForm button[type="submit"]');
|
|
85
|
+
const hasProvider = Boolean(state.availability[state.runProvider]);
|
|
86
|
+
const providerHelp = $("#runProviderHelp");
|
|
87
|
+
if (providerHelp) {
|
|
88
|
+
providerHelp.innerHTML = runProviderHelpHtml();
|
|
89
|
+
providerHelp.classList.toggle(
|
|
90
|
+
"hidden",
|
|
91
|
+
state.providers.some((provider) => state.availability[provider.id]),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (submit) submit.disabled = submit.dataset.submitting === "true" || !hasProvider;
|
|
95
|
+
if (submitLabel && submit.dataset.submitting !== "true")
|
|
96
|
+
submitLabel.textContent = hasProvider
|
|
97
|
+
? window.LoadToAgentI18n.t("provider.assign", { provider: providerInfo(state.runProvider).label })
|
|
98
|
+
: "AI 설치가 필요합니다";
|
|
99
|
+
const writeIntent = /(고치|수정|추가|구현|변경|삭제|작성|리팩터|fix|implement|update|edit|refactor)/i.test((prompt && prompt.value) || "");
|
|
100
|
+
const permissionHint = $("#runPermissionHint");
|
|
101
|
+
const permissionNeeded = writeIntent && !$("#allowWrites").checked;
|
|
102
|
+
permissionHint.classList.toggle("hidden", !permissionNeeded);
|
|
103
|
+
if (permissionNeeded) $("#allowWrites").setAttribute("aria-describedby", "runPermissionHint");
|
|
104
|
+
else $("#allowWrites").removeAttribute("aria-describedby");
|
|
105
|
+
const suggestions = $("#runWorkspaceSuggestions");
|
|
106
|
+
if (suggestions) suggestions.innerHTML = runWorkspaceSuggestionsHtml();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function setRunSubmitting(submitting) {
|
|
110
|
+
const submit = $('#runForm button[type="submit"]');
|
|
111
|
+
if (!submit) return;
|
|
112
|
+
submit.dataset.submitting = submitting ? "true" : "false";
|
|
113
|
+
submit.disabled = submitting || !state.availability[state.runProvider];
|
|
114
|
+
submit.setAttribute("aria-busy", submitting ? "true" : "false");
|
|
115
|
+
const label = $("#runSubmitLabel");
|
|
116
|
+
if (label) label.textContent = submitting
|
|
117
|
+
? "AI 작업을 준비하는 중…"
|
|
118
|
+
: window.LoadToAgentI18n.t("provider.assign", { provider: providerInfo(state.runProvider).label });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function openRunModal() {
|
|
122
|
+
rememberDialogTrigger();
|
|
123
|
+
const installed = state.providers.find((provider) => state.availability[provider.id]);
|
|
124
|
+
if (!state.availability[state.runProvider] && installed) state.runProvider = installed.id;
|
|
125
|
+
$("#runProviderPicker").innerHTML = providerPickerHtml();
|
|
126
|
+
if (!$("#runCwd").value) $("#runCwd").value = state.workspace !== "all" ? state.workspace : (state.workspaces[0] && state.workspaces[0].path) || "";
|
|
127
|
+
$("#runCwd").placeholder = state.platform.id === "win32" ? "D:\\project" : "/Users/me/project";
|
|
128
|
+
const advanced = $("#runForm .run-advanced");
|
|
129
|
+
if (advanced) advanced.open = Boolean($("#runModel").value.trim());
|
|
130
|
+
$("#runError").classList.add("hidden");
|
|
131
|
+
syncRunComposer();
|
|
132
|
+
clearTimeout(motionState.modalTimer);
|
|
133
|
+
$("#runModal").classList.remove("hidden", "closing");
|
|
134
|
+
setTimeout(() => $("#runPrompt").focus(), 0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function closeRunModal() {
|
|
138
|
+
const modal = $("#runModal");
|
|
139
|
+
if (modal.classList.contains("hidden") || modal.classList.contains("closing")) return;
|
|
140
|
+
modal.classList.add("closing");
|
|
141
|
+
clearTimeout(motionState.modalTimer);
|
|
142
|
+
motionState.modalTimer = setTimeout(
|
|
143
|
+
() => {
|
|
144
|
+
modal.classList.add("hidden");
|
|
145
|
+
modal.classList.remove("closing");
|
|
146
|
+
restoreDialogTrigger();
|
|
147
|
+
},
|
|
148
|
+
motionPreference.matches ? 0 : 220,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function toast(message) {
|
|
153
|
+
const el = $("#toast");
|
|
154
|
+
el.textContent = message;
|
|
155
|
+
el.classList.remove("hidden", "leaving");
|
|
156
|
+
clearTimeout(motionState.toastTimer);
|
|
157
|
+
motionState.toastTimer = setTimeout(() => {
|
|
158
|
+
el.classList.add("leaving");
|
|
159
|
+
motionState.toastTimer = setTimeout(
|
|
160
|
+
() => {
|
|
161
|
+
el.classList.add("hidden");
|
|
162
|
+
el.classList.remove("leaving");
|
|
163
|
+
},
|
|
164
|
+
motionPreference.matches ? 0 : 220,
|
|
165
|
+
);
|
|
166
|
+
}, 3200);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function performUiAction(action, failureMessage) {
|
|
170
|
+
try {
|
|
171
|
+
return await action();
|
|
172
|
+
} catch (error) {
|
|
173
|
+
toast((error && error.message) || failureMessage);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function handleRun(event) {
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
if (!state.availability[state.runProvider]) {
|
|
181
|
+
$("#runError").textContent = window.LoadToAgentI18n.t("ui.no_ai_cli_is_ready_follow_the_official_setup_guide");
|
|
182
|
+
$("#runError").classList.remove("hidden");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
setRunSubmitting(true);
|
|
186
|
+
$("#runError").classList.add("hidden");
|
|
187
|
+
try {
|
|
188
|
+
const result = await window.loadtoagent.runAgent({
|
|
189
|
+
provider: state.runProvider,
|
|
190
|
+
cwd: $("#runCwd").value.trim(),
|
|
191
|
+
model: $("#runModel").value.trim(),
|
|
192
|
+
prompt: $("#runPrompt").value.trim(),
|
|
193
|
+
allowWrites: $("#allowWrites").checked,
|
|
194
|
+
});
|
|
195
|
+
if (!result.ok) throw new Error(result.error || window.LoadToAgentI18n.t("ui.could_not_start_the_task"));
|
|
196
|
+
markGuideStep("create");
|
|
197
|
+
closeRunModal();
|
|
198
|
+
$("#runPrompt").value = "";
|
|
199
|
+
syncRunComposer();
|
|
200
|
+
toast(window.LoadToAgentI18n.t("provider.started", { provider: providerInfo(state.runProvider).label }));
|
|
201
|
+
} catch (error) {
|
|
202
|
+
$("#runError").textContent = error.message;
|
|
203
|
+
$("#runError").classList.remove("hidden");
|
|
204
|
+
} finally {
|
|
205
|
+
setRunSubmitting(false);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
providerPickerHtml,
|
|
211
|
+
runProviderHelpHtml,
|
|
212
|
+
runWorkspaceSuggestionsHtml,
|
|
213
|
+
syncRunComposer,
|
|
214
|
+
setRunSubmitting,
|
|
215
|
+
openRunModal,
|
|
216
|
+
closeRunModal,
|
|
217
|
+
toast,
|
|
218
|
+
performUiAction,
|
|
219
|
+
handleRun,
|
|
220
|
+
};
|
|
221
|
+
};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
|
+
|
|
5
|
+
window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRenderer(context = {}) {
|
|
6
|
+
const {
|
|
7
|
+
$,
|
|
8
|
+
esc,
|
|
9
|
+
state,
|
|
10
|
+
STATUS,
|
|
11
|
+
VIEW_TITLES,
|
|
12
|
+
captureMotionLayout,
|
|
13
|
+
playMotionLayout,
|
|
14
|
+
animateVisibleSections,
|
|
15
|
+
renderGuide,
|
|
16
|
+
syncViewChrome,
|
|
17
|
+
readablePreview,
|
|
18
|
+
compact,
|
|
19
|
+
fullNumber,
|
|
20
|
+
timeAgo,
|
|
21
|
+
providerInfo,
|
|
22
|
+
providerStyle,
|
|
23
|
+
statusClass,
|
|
24
|
+
currentActivity,
|
|
25
|
+
isLiveSession,
|
|
26
|
+
latestWorkCopy,
|
|
27
|
+
statusIcon,
|
|
28
|
+
renderProviderRail,
|
|
29
|
+
isProjectlessSession,
|
|
30
|
+
sessionWorkspaceLabel,
|
|
31
|
+
renderWorkspaces,
|
|
32
|
+
renderGlobalStats,
|
|
33
|
+
renderUpdateSettings,
|
|
34
|
+
renderProviderOverview,
|
|
35
|
+
renderProviderFilter,
|
|
36
|
+
filteredSessions,
|
|
37
|
+
graphFilteredSessions,
|
|
38
|
+
executionModeBadge,
|
|
39
|
+
renderAgentMap,
|
|
40
|
+
renderTmuxMap,
|
|
41
|
+
} = context;
|
|
42
|
+
|
|
43
|
+
function recentConversation(session) {
|
|
44
|
+
const messages = (session.messages || []).filter((message) => message && message.text && message.role !== "system");
|
|
45
|
+
const user = [...messages].reverse().find((message) => message.role === "user");
|
|
46
|
+
const assistant = [...messages].reverse().find((message) => message.role === "assistant");
|
|
47
|
+
const tool = [...messages].reverse().find((message) => message.role === "tool");
|
|
48
|
+
const rows = [];
|
|
49
|
+
if (user) rows.push({ label: "나", text: readablePreview(user.text, 140).text, tone: "user" });
|
|
50
|
+
if (assistant) rows.push({ label: providerInfo(session.provider).label, text: readablePreview(assistant.text, 140).text, tone: "assistant" });
|
|
51
|
+
else if (tool) rows.push({ label: tool.title || "도구", text: readablePreview(tool.text, 140).text, tone: "tool" });
|
|
52
|
+
if (!rows.length) rows.push({ label: "상태", text: session.statusDetail || "대화 이벤트를 기다리는 중입니다.", tone: "system" });
|
|
53
|
+
return rows.slice(-2);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sessionCard(session, opts = {}) {
|
|
57
|
+
const provider = providerInfo(session.provider);
|
|
58
|
+
const usage = session.usage || {};
|
|
59
|
+
const context = session.context || {};
|
|
60
|
+
const activity = currentActivity(session);
|
|
61
|
+
const running = session.status === "running" || session.status === "starting";
|
|
62
|
+
const children = session.childIds || [];
|
|
63
|
+
const model = session.model || "사용 모델 정보 없음";
|
|
64
|
+
const contextPercent = Math.max(0, Math.min(100, Number(context.percent || 0)));
|
|
65
|
+
const remaining = context.window ? Math.max(0, Number(context.window) - Number(context.used || 0)) : 0;
|
|
66
|
+
const gaugeTone = contextPercent >= 90 ? "critical" : contextPercent >= 75 ? "warning" : "safe";
|
|
67
|
+
const conversation = recentConversation(session);
|
|
68
|
+
const runtime = session.runtimePresence || [];
|
|
69
|
+
const titlePreview = readablePreview(session.title, 96);
|
|
70
|
+
const activityCopy = latestWorkCopy(session) || session.statusDetail || "새 이벤트 대기";
|
|
71
|
+
const activityPreview = readablePreview(activityCopy, 116);
|
|
72
|
+
const accessibleId = `session-${String(session.id || "").replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
73
|
+
return `<article class="session-card ${opts.live ? "live-card" : ""} ${statusClass(session.status)} ${session.parentId ? "subagent" : ""}"
|
|
74
|
+
data-session-id="${esc(session.id)}"
|
|
75
|
+
data-motion-key="session:${esc(session.id)}"
|
|
76
|
+
data-motion-value="${esc(session.updatedAt || "")}:${usage.total || 0}:${esc(session.status || "")}"
|
|
77
|
+
style="${providerStyle(session.provider)}"
|
|
78
|
+
role="button" tabindex="0"
|
|
79
|
+
aria-labelledby="${accessibleId}-title" aria-describedby="${accessibleId}-summary">
|
|
80
|
+
<div class="card-head">
|
|
81
|
+
<span class="provider-mark">${esc(provider.mark)}</span>
|
|
82
|
+
<div class="card-head-main"><div class="card-provider-line"><b>${esc(provider.label)}</b><span>${esc(provider.company)}</span></div></div>
|
|
83
|
+
${executionModeBadge(session, true)}
|
|
84
|
+
<span class="status-pill ${statusClass(session.status)}">${esc(STATUS[session.status] || session.status)}</span>
|
|
85
|
+
</div>
|
|
86
|
+
<h3 id="${accessibleId}-title" class="card-title" title="${esc(titlePreview.full)}">${esc(titlePreview.text)}</h3>
|
|
87
|
+
<div class="card-subtitle">
|
|
88
|
+
<span>${esc(model)}</span>
|
|
89
|
+
<i>
|
|
90
|
+
</i>
|
|
91
|
+
<span title="${esc(isProjectlessSession(session) ? window.LoadToAgentI18n.t("ui.session_not_linked_to_a_specific_project") : session.cwd)}">
|
|
92
|
+
${esc(sessionWorkspaceLabel(session))}
|
|
93
|
+
</span>${
|
|
94
|
+
session.agentName
|
|
95
|
+
? `<i>
|
|
96
|
+
</i>
|
|
97
|
+
<span>${esc(session.agentName)}</span>`
|
|
98
|
+
: ""
|
|
99
|
+
}</div>
|
|
100
|
+
<div id="${accessibleId}-summary" class="now-strip ${running ? "is-live" : ""}">
|
|
101
|
+
<span class="now-strip-icon">${statusIcon(activity.type)}</span>
|
|
102
|
+
<div><b>${running ? "지금: " : ""}${esc(activity.title)}</b><span title="${esc(activityPreview.full)}">${esc(activityPreview.text)}</span></div>
|
|
103
|
+
${running ? '<span class="activity-wave"><i></i><i></i><i></i><i></i><i></i></span>' : ""}
|
|
104
|
+
</div>
|
|
105
|
+
${
|
|
106
|
+
runtime.length
|
|
107
|
+
? `<div class="runtime-strip">
|
|
108
|
+
<span class="runtime-pulse">
|
|
109
|
+
</span>
|
|
110
|
+
<b>실제로 실행 중인 프로그램 ${runtime.length}개</b>
|
|
111
|
+
<span>${esc(runtime.map((item) => item.label || `프로그램 ${item.pid}`).join(" · "))}</span>
|
|
112
|
+
</div>`
|
|
113
|
+
: ""
|
|
114
|
+
}
|
|
115
|
+
<div class="conversation-preview">
|
|
116
|
+
${conversation.map((row) => `<div class="preview-line ${row.tone}"><b>${esc(row.label)}</b><span>${esc(row.text)}</span></div>`).join("")}
|
|
117
|
+
</div>
|
|
118
|
+
<div class="context-meter ${gaugeTone}">
|
|
119
|
+
<div class="context-meter-head">
|
|
120
|
+
<div>
|
|
121
|
+
<span>AI의 기억 공간 사용량</span>
|
|
122
|
+
<strong>${context.window ? `${fullNumber(context.used)} / ${fullNumber(context.window)}` : `${fullNumber(context.used)} / --`}</strong>
|
|
123
|
+
</div>
|
|
124
|
+
<b>${context.window ? `${contextPercent.toFixed(1)}%` : "--"}</b>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="context-meter-track"><span style="width:${contextPercent}%"></span><i style="left:75%"></i><i style="left:90%"></i></div>
|
|
127
|
+
<div class="context-meter-foot">
|
|
128
|
+
<span>${context.window ? `아직 ${compact(remaining)} 토큰만큼 기억 가능` : "기억 공간 크기 정보 없음"}</span>
|
|
129
|
+
<span>지금까지 ${compact(usage.total)} 토큰 사용</span>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="token-row">
|
|
133
|
+
<div><span>받은 글</span><b>${compact(usage.input)}</b></div>
|
|
134
|
+
<div><span>AI가 쓴 글</span><b>${compact(usage.output)}</b></div>
|
|
135
|
+
<div><span>다시 쓴 기억</span><b>${compact(usage.cachedInput)}</b></div>
|
|
136
|
+
<div class="total"><span>전체 사용</span><b>${compact(usage.total)}</b></div>
|
|
137
|
+
</div>
|
|
138
|
+
${
|
|
139
|
+
children.length
|
|
140
|
+
? `<div class="child-row">
|
|
141
|
+
<b>⑂</b>
|
|
142
|
+
<span>서브에이전트 ${children.length}개 누적 생성</span>
|
|
143
|
+
<span class="child-dots">${children
|
|
144
|
+
.slice(0, 4)
|
|
145
|
+
.map(() => "<i></i>")
|
|
146
|
+
.join("")}</span>
|
|
147
|
+
</div>`
|
|
148
|
+
: ""
|
|
149
|
+
}
|
|
150
|
+
<footer class="card-footer">
|
|
151
|
+
<span class="source-tag">${esc(session.sourceLabel || "내 PC의 작업 기록")}</span>
|
|
152
|
+
<span>${esc(timeAgo(session.updatedAt))}</span>
|
|
153
|
+
</footer>
|
|
154
|
+
</article>`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function renderSessions(motionKind = "refresh", deferMotion = false) {
|
|
158
|
+
const previousLayout = deferMotion ? null : captureMotionLayout();
|
|
159
|
+
syncViewChrome();
|
|
160
|
+
renderGuide();
|
|
161
|
+
const tmuxView = state.view === "tmux";
|
|
162
|
+
const terminalView = state.view === "terminal";
|
|
163
|
+
const settingsView = state.view === "settings";
|
|
164
|
+
$("#terminalSection").classList.toggle("hidden", !terminalView);
|
|
165
|
+
$("#tmuxSection").classList.toggle("hidden", !tmuxView);
|
|
166
|
+
$("#settingsSection").classList.toggle("hidden", !settingsView);
|
|
167
|
+
$("#globalStats").classList.toggle("hidden", tmuxView || terminalView || settingsView);
|
|
168
|
+
$("#providerOverview").classList.toggle("hidden", tmuxView || terminalView || settingsView);
|
|
169
|
+
$("#sessionSection").classList.toggle("hidden", tmuxView || terminalView || settingsView);
|
|
170
|
+
const guideVisible = state.view === "all" && state.guideExpanded && !state.graphFocusId;
|
|
171
|
+
$("#beginnerGuide").classList.toggle("hidden", !guideVisible);
|
|
172
|
+
$("#guideBtn").setAttribute("aria-expanded", guideVisible ? "true" : "false");
|
|
173
|
+
renderUpdateSettings();
|
|
174
|
+
if (settingsView) {
|
|
175
|
+
$("#liveSection").classList.add("hidden");
|
|
176
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.deactivate();
|
|
177
|
+
if (!deferMotion) playMotionLayout(previousLayout, motionKind);
|
|
178
|
+
if (motionKind === "view") animateVisibleSections();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (terminalView) {
|
|
182
|
+
$("#liveSection").classList.add("hidden");
|
|
183
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.activate(state.snapshot, state.workspaces, "general");
|
|
184
|
+
if (!deferMotion) playMotionLayout(previousLayout, motionKind);
|
|
185
|
+
if (motionKind === "view") animateVisibleSections();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (tmuxView) {
|
|
189
|
+
$("#liveSection").classList.add("hidden");
|
|
190
|
+
renderTmuxMap();
|
|
191
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.activate(state.snapshot, state.workspaces, "tmux");
|
|
192
|
+
if (!deferMotion) playMotionLayout(previousLayout, motionKind);
|
|
193
|
+
if (motionKind === "view") animateVisibleSections();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.deactivate();
|
|
197
|
+
const sessions = filteredSessions();
|
|
198
|
+
const showMap = ["all", "active"].includes(state.view);
|
|
199
|
+
const graphLiveCount = showMap ? renderAgentMap(graphFilteredSessions(), motionKind) : 0;
|
|
200
|
+
const regular = state.view === "all" ? sessions.filter((session) => !isLiveSession(session)) : state.view === "active" ? [] : sessions;
|
|
201
|
+
const visible = regular.slice(0, state.visibleLimit);
|
|
202
|
+
$("#liveSection").classList.toggle("hidden", graphLiveCount === 0);
|
|
203
|
+
$("#viewTitle").textContent = VIEW_TITLES[state.view] || window.LoadToAgentI18n.t("ui.recent_conversations_and_tasks");
|
|
204
|
+
$("#sessionGrid").innerHTML = visible.map((session) => sessionCard(session)).join("");
|
|
205
|
+
$("#sessionGrid").classList.toggle("hidden", visible.length === 0);
|
|
206
|
+
$("#loadMoreBtn").classList.toggle("hidden", regular.length <= state.visibleLimit);
|
|
207
|
+
$("#loadMoreBtn").textContent = window.LoadToAgentI18n.t("common.remaining", { count: regular.length - state.visibleLimit });
|
|
208
|
+
$("#emptyState").classList.toggle("hidden", graphLiveCount + regular.length !== 0);
|
|
209
|
+
if (graphLiveCount + regular.length === 0) {
|
|
210
|
+
const emptyCopy = state.search
|
|
211
|
+
? [window.LoadToAgentI18n.t("ui.no_search_results"), window.LoadToAgentI18n.t("ui.clear_the_search_or_change_the_ai_and_workspace_filters")]
|
|
212
|
+
: state.view === "active"
|
|
213
|
+
? [window.LoadToAgentI18n.t("ui.no_tasks_are_currently_active"), window.LoadToAgentI18n.t("ui.new_tasks_will_show_their_progress_here_immediately")]
|
|
214
|
+
: state.view === "waiting"
|
|
215
|
+
? [window.LoadToAgentI18n.t("ui.all_caught_up"), window.LoadToAgentI18n.t("ui.no_tasks_are_waiting_for_your_response_or_choice")]
|
|
216
|
+
: [window.LoadToAgentI18n.t("ui.no_tasks_to_show_yet"), window.LoadToAgentI18n.t("ui.check_ai_readiness_then_start_your_first_task")];
|
|
217
|
+
$("#emptyState h3").textContent = emptyCopy[0];
|
|
218
|
+
$("#emptyState p").textContent = emptyCopy[1];
|
|
219
|
+
}
|
|
220
|
+
if (!deferMotion) playMotionLayout(previousLayout, motionKind);
|
|
221
|
+
if (motionKind === "view") animateVisibleSections();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function render(motionKind = "refresh") {
|
|
225
|
+
const previousLayout = captureMotionLayout();
|
|
226
|
+
renderProviderRail();
|
|
227
|
+
renderWorkspaces();
|
|
228
|
+
renderGlobalStats();
|
|
229
|
+
renderProviderOverview();
|
|
230
|
+
renderProviderFilter();
|
|
231
|
+
renderSessions(motionKind, true);
|
|
232
|
+
if (state.selectedId && $("#detailDrawer").classList.contains("open")) context.renderDrawer();
|
|
233
|
+
playMotionLayout(previousLayout, motionKind);
|
|
234
|
+
if (motionKind === "view") animateVisibleSections();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
recentConversation,
|
|
239
|
+
sessionCard,
|
|
240
|
+
renderSessions,
|
|
241
|
+
render,
|
|
242
|
+
};
|
|
243
|
+
};
|