claude-relay 2.3.0 → 2.4.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.md +21 -5
- package/bin/cli.js +214 -9
- package/lib/cli-sessions.js +270 -0
- package/lib/config.js +3 -2
- package/lib/daemon.js +45 -1
- package/lib/pages.js +8 -1
- package/lib/project.js +121 -12
- package/lib/public/app.js +411 -87
- package/lib/public/css/base.css +41 -7
- package/lib/public/css/diff.css +6 -6
- package/lib/public/css/filebrowser.css +62 -52
- package/lib/public/css/highlight.css +144 -0
- package/lib/public/css/input.css +11 -9
- package/lib/public/css/menus.css +82 -23
- package/lib/public/css/messages.css +183 -35
- package/lib/public/css/overlays.css +166 -50
- package/lib/public/css/rewind.css +17 -17
- package/lib/public/css/sidebar.css +210 -137
- package/lib/public/index.html +75 -42
- package/lib/public/modules/filebrowser.js +2 -1
- package/lib/public/modules/markdown.js +10 -10
- package/lib/public/modules/notifications.js +38 -1
- package/lib/public/modules/sidebar.js +109 -31
- package/lib/public/modules/terminal.js +84 -23
- package/lib/public/modules/theme.js +622 -0
- package/lib/public/modules/tools.js +247 -4
- package/lib/public/modules/utils.js +21 -5
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +95 -0
- package/lib/server.js +45 -3
- package/lib/sessions.js +16 -3
- package/lib/themes/ayu-light.json +9 -0
- package/lib/themes/catppuccin-latte.json +9 -0
- package/lib/themes/catppuccin-mocha.json +9 -0
- package/lib/themes/claude-light.json +9 -0
- package/lib/themes/claude.json +9 -0
- package/lib/themes/dracula.json +9 -0
- package/lib/themes/everforest-light.json +9 -0
- package/lib/themes/everforest.json +9 -0
- package/lib/themes/github-light.json +9 -0
- package/lib/themes/gruvbox-dark.json +9 -0
- package/lib/themes/gruvbox-light.json +9 -0
- package/lib/themes/monokai.json +9 -0
- package/lib/themes/nord-light.json +9 -0
- package/lib/themes/nord.json +9 -0
- package/lib/themes/one-dark.json +9 -0
- package/lib/themes/one-light.json +9 -0
- package/lib/themes/rose-pine-dawn.json +9 -0
- package/lib/themes/rose-pine.json +9 -0
- package/lib/themes/solarized-dark.json +9 -0
- package/lib/themes/solarized-light.json +9 -0
- package/lib/themes/tokyo-night-light.json +9 -0
- package/lib/themes/tokyo-night.json +9 -0
- package/package.json +2 -1
package/lib/public/app.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
|
|
2
2
|
import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
|
|
3
3
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal } from './modules/markdown.js';
|
|
4
|
-
import { initSidebar, renderSessionList, handleSearchResults, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline } from './modules/sidebar.js';
|
|
4
|
+
import { initSidebar, renderSessionList, handleSearchResults, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList } from './modules/sidebar.js';
|
|
5
5
|
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid } from './modules/rewind.js';
|
|
6
6
|
import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
|
|
7
7
|
import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands } from './modules/input.js';
|
|
8
8
|
import { initQrCode } from './modules/qrcode.js';
|
|
9
9
|
import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer } from './modules/filebrowser.js';
|
|
10
10
|
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed } from './modules/terminal.js';
|
|
11
|
-
import {
|
|
11
|
+
import { initTheme, getThemeColor, getComputedVar, onThemeChange } from './modules/theme.js';
|
|
12
|
+
import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
|
|
12
13
|
|
|
13
14
|
// --- Base path for multi-project routing ---
|
|
14
15
|
var slugMatch = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
|
|
@@ -21,7 +22,8 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
21
22
|
var inputEl = $("input");
|
|
22
23
|
var sendBtn = $("send-btn");
|
|
23
24
|
var statusDot = $("status-dot");
|
|
24
|
-
var
|
|
25
|
+
var headerTitleEl = $("header-title");
|
|
26
|
+
var headerRenameBtn = $("header-rename-btn");
|
|
25
27
|
var slashMenu = $("slash-menu");
|
|
26
28
|
var sidebar = $("sidebar");
|
|
27
29
|
var sidebarOverlay = $("sidebar-overlay");
|
|
@@ -36,25 +38,21 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
36
38
|
var connectVerbEl = $("connect-verb");
|
|
37
39
|
var connectStatusEl = $("connect-status");
|
|
38
40
|
|
|
39
|
-
// --- Project
|
|
40
|
-
var
|
|
41
|
-
var
|
|
42
|
-
var
|
|
43
|
-
var projectDropdownList = $("project-dropdown-list");
|
|
44
|
-
var projectSwitcherName = $("project-switcher-name");
|
|
45
|
-
var projectSwitcherCount = $("project-switcher-count");
|
|
41
|
+
// --- Project List ---
|
|
42
|
+
var projectListSection = $("project-list-section");
|
|
43
|
+
var projectListEl = $("project-list");
|
|
44
|
+
var projectListAddBtn = $("project-list-add");
|
|
46
45
|
var projectHint = $("project-hint");
|
|
47
46
|
var projectHintDismiss = $("project-hint-dismiss");
|
|
48
47
|
var cachedProjects = [];
|
|
49
48
|
var cachedProjectCount = 0;
|
|
50
49
|
var currentSlug = slugMatch ? slugMatch[1] : null;
|
|
51
50
|
|
|
52
|
-
function
|
|
51
|
+
function updateProjectList(msg) {
|
|
53
52
|
if (typeof msg.projectCount === "number") cachedProjectCount = msg.projectCount;
|
|
54
53
|
if (msg.projects) cachedProjects = msg.projects;
|
|
55
54
|
var count = cachedProjectCount || 0;
|
|
56
|
-
|
|
57
|
-
projectSwitcherCount.textContent = count ? count + (count === 1 ? " project" : " projects") : "";
|
|
55
|
+
renderProjectList();
|
|
58
56
|
if (count === 1 && projectHint) {
|
|
59
57
|
try {
|
|
60
58
|
if (!localStorage.getItem("claude-relay-project-hint-dismissed")) {
|
|
@@ -66,14 +64,15 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
66
64
|
}
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
function
|
|
70
|
-
|
|
67
|
+
function renderProjectList() {
|
|
68
|
+
if (!projectListEl) return;
|
|
69
|
+
projectListEl.innerHTML = "";
|
|
71
70
|
for (var i = 0; i < cachedProjects.length; i++) {
|
|
72
71
|
var p = cachedProjects[i];
|
|
73
72
|
var isCurrent = p.slug === currentSlug;
|
|
74
73
|
var displayName = p.title || p.project;
|
|
75
74
|
var item = document.createElement("a");
|
|
76
|
-
item.className = "project-
|
|
75
|
+
item.className = "project-list-item" + (isCurrent ? " current" : "");
|
|
77
76
|
item.href = "/p/" + p.slug + "/";
|
|
78
77
|
|
|
79
78
|
var indicator = document.createElement("span");
|
|
@@ -85,60 +84,35 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
85
84
|
name.textContent = displayName;
|
|
86
85
|
item.appendChild(name);
|
|
87
86
|
|
|
88
|
-
var
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
var removeBtn = document.createElement("button");
|
|
88
|
+
removeBtn.className = "pd-remove";
|
|
89
|
+
removeBtn.type = "button";
|
|
90
|
+
removeBtn.title = "Remove project";
|
|
91
|
+
removeBtn.innerHTML = '<i data-lucide="trash-2"></i>';
|
|
92
|
+
removeBtn.dataset.slug = p.slug;
|
|
93
|
+
removeBtn.dataset.name = displayName;
|
|
94
|
+
removeBtn.addEventListener("click", function (e) {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
e.stopPropagation();
|
|
97
|
+
var s = this.dataset.slug;
|
|
98
|
+
var n = this.dataset.name;
|
|
99
|
+
confirmRemoveProject(s, n);
|
|
100
|
+
});
|
|
101
|
+
item.appendChild(removeBtn);
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
var addBtn = document.getElementById("project-dropdown-add");
|
|
98
|
-
if (addBtn) {
|
|
99
|
-
addBtn.onclick = function () {
|
|
100
|
-
closeProjectDropdown();
|
|
101
|
-
var hint = document.getElementById("project-hint");
|
|
102
|
-
if (hint) {
|
|
103
|
-
hint.classList.remove("hidden");
|
|
104
|
-
try { localStorage.removeItem("claude-relay-project-hint-dismissed"); } catch (e) {}
|
|
105
|
-
}
|
|
106
|
-
};
|
|
103
|
+
projectListEl.appendChild(item);
|
|
107
104
|
}
|
|
108
105
|
refreshIcons();
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
projectDropdown.classList.remove("hidden");
|
|
115
|
-
projectSwitcher.classList.add("open");
|
|
116
|
-
} else {
|
|
117
|
-
closeProjectDropdown();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function closeProjectDropdown() {
|
|
122
|
-
projectDropdown.classList.add("hidden");
|
|
123
|
-
projectSwitcher.classList.remove("open");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (projectSwitcherBtn) {
|
|
127
|
-
projectSwitcherBtn.addEventListener("click", function (e) {
|
|
128
|
-
e.stopPropagation();
|
|
129
|
-
toggleProjectDropdown();
|
|
108
|
+
if (projectListAddBtn) {
|
|
109
|
+
projectListAddBtn.addEventListener("click", function () {
|
|
110
|
+
openAddProjectModal();
|
|
130
111
|
});
|
|
131
112
|
}
|
|
132
113
|
|
|
133
|
-
document.addEventListener("click", function (e) {
|
|
134
|
-
if (projectSwitcher && !projectSwitcher.contains(e.target)) {
|
|
135
|
-
closeProjectDropdown();
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
114
|
document.addEventListener("keydown", function (e) {
|
|
140
115
|
if (e.key === "Escape") {
|
|
141
|
-
closeProjectDropdown();
|
|
142
116
|
closeImageModal();
|
|
143
117
|
}
|
|
144
118
|
});
|
|
@@ -185,6 +159,8 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
185
159
|
// isComposing -> modules/input.js
|
|
186
160
|
var reconnectTimer = null;
|
|
187
161
|
var reconnectDelay = 1000;
|
|
162
|
+
var disconnectNotifTimer = null;
|
|
163
|
+
var disconnectNotifShown = false;
|
|
188
164
|
var activityEl = null;
|
|
189
165
|
var currentMsgEl = null;
|
|
190
166
|
var currentFullText = "";
|
|
@@ -215,6 +191,45 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
215
191
|
|
|
216
192
|
// builtinCommands -> modules/input.js
|
|
217
193
|
|
|
194
|
+
// --- Header session rename ---
|
|
195
|
+
if (headerRenameBtn) {
|
|
196
|
+
headerRenameBtn.addEventListener("click", function () {
|
|
197
|
+
if (!activeSessionId) return;
|
|
198
|
+
var currentText = headerTitleEl.textContent;
|
|
199
|
+
var input = document.createElement("input");
|
|
200
|
+
input.type = "text";
|
|
201
|
+
input.className = "header-rename-input";
|
|
202
|
+
input.value = currentText;
|
|
203
|
+
headerTitleEl.style.display = "none";
|
|
204
|
+
headerRenameBtn.style.display = "none";
|
|
205
|
+
headerTitleEl.parentNode.insertBefore(input, headerTitleEl.nextSibling);
|
|
206
|
+
input.focus();
|
|
207
|
+
input.select();
|
|
208
|
+
|
|
209
|
+
function commit() {
|
|
210
|
+
var newTitle = input.value.trim();
|
|
211
|
+
if (newTitle && newTitle !== currentText && ws && ws.readyState === 1) {
|
|
212
|
+
ws.send(JSON.stringify({ type: "rename_session", id: activeSessionId, title: newTitle }));
|
|
213
|
+
headerTitleEl.textContent = newTitle;
|
|
214
|
+
}
|
|
215
|
+
input.remove();
|
|
216
|
+
headerTitleEl.style.display = "";
|
|
217
|
+
headerRenameBtn.style.display = "";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
input.addEventListener("keydown", function (e) {
|
|
221
|
+
if (e.key === "Enter") { e.preventDefault(); commit(); }
|
|
222
|
+
if (e.key === "Escape") {
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
input.remove();
|
|
225
|
+
headerTitleEl.style.display = "";
|
|
226
|
+
headerRenameBtn.style.display = "";
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
input.addEventListener("blur", commit);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
218
233
|
// --- Confirm modal ---
|
|
219
234
|
var confirmModal = $("confirm-modal");
|
|
220
235
|
var confirmText = $("confirm-text");
|
|
@@ -265,6 +280,9 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
265
280
|
addSystemMessage: addSystemMessage,
|
|
266
281
|
});
|
|
267
282
|
|
|
283
|
+
// --- Theme (module) ---
|
|
284
|
+
initTheme();
|
|
285
|
+
|
|
268
286
|
// --- Sidebar (module) ---
|
|
269
287
|
initSidebar({
|
|
270
288
|
$: $,
|
|
@@ -280,6 +298,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
280
298
|
hamburgerBtn: hamburgerBtn,
|
|
281
299
|
newSessionBtn: newSessionBtn,
|
|
282
300
|
resumeSessionBtn: resumeSessionBtn,
|
|
301
|
+
headerTitleEl: headerTitleEl,
|
|
283
302
|
showConfirm: showConfirm,
|
|
284
303
|
onFilesTabOpen: function () { loadRootDirectory(); },
|
|
285
304
|
});
|
|
@@ -337,9 +356,9 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
337
356
|
];
|
|
338
357
|
|
|
339
358
|
var CELL = 12;
|
|
340
|
-
var accent = "
|
|
341
|
-
var eye = "
|
|
342
|
-
var antenna = "
|
|
359
|
+
var accent = getThemeColor("base09");
|
|
360
|
+
var eye = getThemeColor("base00");
|
|
361
|
+
var antenna = getThemeColor("base06");
|
|
343
362
|
|
|
344
363
|
for (var r = 0; r < grid.length; r++) {
|
|
345
364
|
for (var c = 0; c < grid[r].length; c++) {
|
|
@@ -357,6 +376,23 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
357
376
|
}
|
|
358
377
|
})();
|
|
359
378
|
|
|
379
|
+
// Update pixel mascot colors when theme changes
|
|
380
|
+
onThemeChange(function () {
|
|
381
|
+
var newAccent = getThemeColor("base09");
|
|
382
|
+
var newEye = getThemeColor("base00");
|
|
383
|
+
var newAntenna = getThemeColor("base06");
|
|
384
|
+
for (var i = 0; i < pixelBlocks.length; i++) {
|
|
385
|
+
var el = pixelBlocks[i];
|
|
386
|
+
var bg = el.style.background;
|
|
387
|
+
if (bg === accent || bg === newAccent) el.style.background = newAccent;
|
|
388
|
+
else if (bg === eye || bg === newEye) el.style.background = newEye;
|
|
389
|
+
else if (bg === antenna || bg === newAntenna) el.style.background = newAntenna;
|
|
390
|
+
}
|
|
391
|
+
accent = newAccent;
|
|
392
|
+
eye = newEye;
|
|
393
|
+
antenna = newAntenna;
|
|
394
|
+
});
|
|
395
|
+
|
|
360
396
|
function pixelScatter() {
|
|
361
397
|
stopSpark();
|
|
362
398
|
for (var i = 0; i < pixelBlocks.length; i++) {
|
|
@@ -396,7 +432,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
396
432
|
antennaBlocks[i].style.background = "#FFF";
|
|
397
433
|
antennaBlocks[i].style.boxShadow = "0 0 6px 2px rgba(255,255,255,0.6)";
|
|
398
434
|
} else {
|
|
399
|
-
antennaBlocks[i].style.background =
|
|
435
|
+
antennaBlocks[i].style.background = antenna;
|
|
400
436
|
antennaBlocks[i].style.boxShadow = "none";
|
|
401
437
|
}
|
|
402
438
|
}
|
|
@@ -411,7 +447,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
411
447
|
sparkTimer = null;
|
|
412
448
|
}
|
|
413
449
|
for (var i = 0; i < antennaBlocks.length; i++) {
|
|
414
|
-
antennaBlocks[i].style.background =
|
|
450
|
+
antennaBlocks[i].style.background = antenna;
|
|
415
451
|
antennaBlocks[i].style.boxShadow = "none";
|
|
416
452
|
}
|
|
417
453
|
}
|
|
@@ -456,7 +492,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
456
492
|
if (xhr.status === 200) faviconSvg = xhr.responseText;
|
|
457
493
|
else return;
|
|
458
494
|
}
|
|
459
|
-
var svg = faviconSvg.replace(/fill="#57AB5A"
|
|
495
|
+
var svg = faviconSvg.replace(/fill="#57AB5A"/g, 'fill="' + bgColor + '"');
|
|
460
496
|
faviconLink.href = "data:image/svg+xml," + encodeURIComponent(svg);
|
|
461
497
|
}
|
|
462
498
|
|
|
@@ -482,9 +518,9 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
482
518
|
ioTimer = setTimeout(function () { statusDot.classList.remove("io"); }, 60);
|
|
483
519
|
|
|
484
520
|
// Blink favicon: dim then restore
|
|
485
|
-
updateFavicon("
|
|
521
|
+
updateFavicon(getComputedVar("--sidebar-bg"));
|
|
486
522
|
clearTimeout(faviconIoTimer);
|
|
487
|
-
faviconIoTimer = setTimeout(function () { updateFavicon("
|
|
523
|
+
faviconIoTimer = setTimeout(function () { updateFavicon(getComputedVar("--success")); }, 60);
|
|
488
524
|
}
|
|
489
525
|
|
|
490
526
|
// --- Urgent favicon blink (permission / ask user) ---
|
|
@@ -493,7 +529,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
493
529
|
function startUrgentBlink() {
|
|
494
530
|
if (urgentBlinkTimer) return;
|
|
495
531
|
savedTitle = document.title;
|
|
496
|
-
var colors = ["
|
|
532
|
+
var colors = [getComputedVar("--accent"), getComputedVar("--success"), getComputedVar("--accent"), getComputedVar("--text"), getComputedVar("--accent"), getComputedVar("--success")];
|
|
497
533
|
var tick = 0;
|
|
498
534
|
urgentBlinkTimer = setInterval(function () {
|
|
499
535
|
updateFavicon(colors[tick % colors.length]);
|
|
@@ -505,7 +541,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
505
541
|
if (!urgentBlinkTimer) return;
|
|
506
542
|
clearInterval(urgentBlinkTimer);
|
|
507
543
|
urgentBlinkTimer = null;
|
|
508
|
-
updateFavicon("
|
|
544
|
+
updateFavicon(getComputedVar("--success"));
|
|
509
545
|
if (savedTitle) document.title = savedTitle;
|
|
510
546
|
savedTitle = null;
|
|
511
547
|
}
|
|
@@ -520,12 +556,12 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
520
556
|
setSendBtnMode("send");
|
|
521
557
|
connectOverlay.classList.add("hidden");
|
|
522
558
|
stopVerbCycle();
|
|
523
|
-
updateFavicon("
|
|
559
|
+
updateFavicon(getComputedVar("--success"));
|
|
524
560
|
} else if (status === "processing") {
|
|
525
561
|
statusDot.classList.add("processing");
|
|
526
562
|
processing = true;
|
|
527
563
|
setSendBtnMode("stop");
|
|
528
|
-
updateFavicon("
|
|
564
|
+
updateFavicon(getComputedVar("--success"));
|
|
529
565
|
} else {
|
|
530
566
|
connected = false;
|
|
531
567
|
sendBtn.disabled = true;
|
|
@@ -533,7 +569,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
533
569
|
connectStatusEl.textContent = "Reconnecting...";
|
|
534
570
|
startVerbCycle();
|
|
535
571
|
startPixelAnim();
|
|
536
|
-
updateFavicon("
|
|
572
|
+
updateFavicon(getComputedVar("--error"));
|
|
537
573
|
}
|
|
538
574
|
}
|
|
539
575
|
|
|
@@ -974,6 +1010,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
974
1010
|
div.dataset.turn = ++turnCounter;
|
|
975
1011
|
var bubble = document.createElement("div");
|
|
976
1012
|
bubble.className = "bubble";
|
|
1013
|
+
bubble.dir = "auto";
|
|
977
1014
|
|
|
978
1015
|
if (images && images.length > 0) {
|
|
979
1016
|
var imgRow = document.createElement("div");
|
|
@@ -1024,7 +1061,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1024
1061
|
currentMsgEl = document.createElement("div");
|
|
1025
1062
|
currentMsgEl.className = "msg-assistant";
|
|
1026
1063
|
currentMsgEl.dataset.turn = turnCounter;
|
|
1027
|
-
currentMsgEl.innerHTML = '<div class="md-content"></div>';
|
|
1064
|
+
currentMsgEl.innerHTML = '<div class="md-content" dir="auto"></div>';
|
|
1028
1065
|
addToMessages(currentMsgEl);
|
|
1029
1066
|
currentFullText = "";
|
|
1030
1067
|
}
|
|
@@ -1101,6 +1138,8 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1101
1138
|
if (currentFullText) {
|
|
1102
1139
|
addCopyHandler(currentMsgEl, currentFullText);
|
|
1103
1140
|
}
|
|
1141
|
+
// Assistant text appeared, so break the current tool group
|
|
1142
|
+
closeToolGroup();
|
|
1104
1143
|
}
|
|
1105
1144
|
currentMsgEl = null;
|
|
1106
1145
|
currentFullText = "";
|
|
@@ -1164,8 +1203,13 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1164
1203
|
|
|
1165
1204
|
ws.onopen = function () {
|
|
1166
1205
|
if (connectTimeoutId) { clearTimeout(connectTimeoutId); connectTimeoutId = null; }
|
|
1167
|
-
//
|
|
1168
|
-
if (
|
|
1206
|
+
// Cancel pending "connection lost" notification if reconnected quickly
|
|
1207
|
+
if (disconnectNotifTimer) {
|
|
1208
|
+
clearTimeout(disconnectNotifTimer);
|
|
1209
|
+
disconnectNotifTimer = null;
|
|
1210
|
+
}
|
|
1211
|
+
// Only show "restored" notification if "lost" was actually shown
|
|
1212
|
+
if (wasConnected && disconnectNotifShown && !document.hasFocus() && "serviceWorker" in navigator) {
|
|
1169
1213
|
navigator.serviceWorker.ready.then(function (reg) {
|
|
1170
1214
|
reg.showNotification("Claude Relay", {
|
|
1171
1215
|
body: "Server connection restored",
|
|
@@ -1173,6 +1217,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1173
1217
|
});
|
|
1174
1218
|
}).catch(function () {});
|
|
1175
1219
|
}
|
|
1220
|
+
disconnectNotifShown = false;
|
|
1176
1221
|
wasConnected = true;
|
|
1177
1222
|
setStatus("connected");
|
|
1178
1223
|
reconnectDelay = 1000;
|
|
@@ -1198,14 +1243,20 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1198
1243
|
setStatus("disconnected");
|
|
1199
1244
|
processing = false;
|
|
1200
1245
|
setActivity(null);
|
|
1201
|
-
//
|
|
1202
|
-
if (!
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1246
|
+
// Delay "connection lost" notification by 5s to suppress brief disconnects
|
|
1247
|
+
if (!disconnectNotifTimer) {
|
|
1248
|
+
disconnectNotifTimer = setTimeout(function () {
|
|
1249
|
+
disconnectNotifTimer = null;
|
|
1250
|
+
disconnectNotifShown = true;
|
|
1251
|
+
if (!document.hasFocus() && "serviceWorker" in navigator) {
|
|
1252
|
+
navigator.serviceWorker.ready.then(function (reg) {
|
|
1253
|
+
reg.showNotification("Claude Relay", {
|
|
1254
|
+
body: "Server connection lost",
|
|
1255
|
+
tag: "claude-disconnect",
|
|
1256
|
+
});
|
|
1257
|
+
}).catch(function () {});
|
|
1258
|
+
}
|
|
1259
|
+
}, 5000);
|
|
1209
1260
|
}
|
|
1210
1261
|
scheduleReconnect();
|
|
1211
1262
|
};
|
|
@@ -1242,6 +1293,16 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1242
1293
|
break;
|
|
1243
1294
|
|
|
1244
1295
|
case "history_done":
|
|
1296
|
+
// Render + finalize any incomplete turn from the replayed history
|
|
1297
|
+
if (currentMsgEl && currentFullText) {
|
|
1298
|
+
var replayContentEl = currentMsgEl.querySelector(".md-content");
|
|
1299
|
+
if (replayContentEl) {
|
|
1300
|
+
replayContentEl.innerHTML = renderMarkdown(currentFullText);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
markAllToolsDone();
|
|
1304
|
+
finalizeAssistantBlock();
|
|
1305
|
+
scrollToBottom();
|
|
1245
1306
|
var pendingQuery = getActiveSearchQuery();
|
|
1246
1307
|
if (pendingQuery) {
|
|
1247
1308
|
requestAnimationFrame(function() { buildSearchTimeline(pendingQuery); });
|
|
@@ -1256,6 +1317,9 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1256
1317
|
target = messagesEl.querySelector('[data-uuid="' + nav.assistantUuid + '"]');
|
|
1257
1318
|
}
|
|
1258
1319
|
if (target) {
|
|
1320
|
+
// Auto-expand parent tool group if collapsed
|
|
1321
|
+
var parentGroup = target.closest(".tool-group");
|
|
1322
|
+
if (parentGroup) parentGroup.classList.remove("collapsed");
|
|
1259
1323
|
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1260
1324
|
target.classList.add("message-blink");
|
|
1261
1325
|
setTimeout(function() { target.classList.remove("message-blink"); }, 2000);
|
|
@@ -1267,7 +1331,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1267
1331
|
case "info":
|
|
1268
1332
|
projectName = msg.project || msg.cwd;
|
|
1269
1333
|
if (msg.slug) currentSlug = msg.slug;
|
|
1270
|
-
|
|
1334
|
+
headerTitleEl.textContent = projectName;
|
|
1271
1335
|
updatePageTitle();
|
|
1272
1336
|
if (msg.version) {
|
|
1273
1337
|
var vEl = $("footer-version");
|
|
@@ -1282,7 +1346,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1282
1346
|
var spBanner = $("skip-perms-banner");
|
|
1283
1347
|
if (spBanner) spBanner.classList.remove("hidden");
|
|
1284
1348
|
}
|
|
1285
|
-
|
|
1349
|
+
updateProjectList(msg);
|
|
1286
1350
|
break;
|
|
1287
1351
|
|
|
1288
1352
|
case "update_available":
|
|
@@ -1351,6 +1415,10 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1351
1415
|
handleSearchResults(msg);
|
|
1352
1416
|
break;
|
|
1353
1417
|
|
|
1418
|
+
case "cli_session_list":
|
|
1419
|
+
populateCliSessionList(msg.sessions || []);
|
|
1420
|
+
break;
|
|
1421
|
+
|
|
1354
1422
|
case "session_switched":
|
|
1355
1423
|
// Save draft from outgoing session
|
|
1356
1424
|
if (activeSessionId && inputEl.value) {
|
|
@@ -1453,6 +1521,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1453
1521
|
if (askTool) {
|
|
1454
1522
|
if (askTool.el) askTool.el.style.display = "none";
|
|
1455
1523
|
askTool.done = true;
|
|
1524
|
+
removeToolFromGroup(msg.id);
|
|
1456
1525
|
}
|
|
1457
1526
|
renderAskUserQuestion(msg.id, msg.input);
|
|
1458
1527
|
startUrgentBlink();
|
|
@@ -1528,10 +1597,23 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1528
1597
|
scrollToBottom();
|
|
1529
1598
|
break;
|
|
1530
1599
|
|
|
1600
|
+
case "subagent_activity":
|
|
1601
|
+
updateSubagentActivity(msg.parentToolId, msg.text);
|
|
1602
|
+
break;
|
|
1603
|
+
|
|
1604
|
+
case "subagent_tool":
|
|
1605
|
+
addSubagentToolEntry(msg.parentToolId, msg.toolName, msg.toolId, msg.text);
|
|
1606
|
+
break;
|
|
1607
|
+
|
|
1608
|
+
case "subagent_done":
|
|
1609
|
+
markSubagentDone(msg.parentToolId);
|
|
1610
|
+
break;
|
|
1611
|
+
|
|
1531
1612
|
case "result":
|
|
1532
1613
|
setActivity(null);
|
|
1533
1614
|
stopThinking();
|
|
1534
1615
|
markAllToolsDone();
|
|
1616
|
+
closeToolGroup();
|
|
1535
1617
|
finalizeAssistantBlock();
|
|
1536
1618
|
addTurnMeta(msg.cost, msg.duration);
|
|
1537
1619
|
accumulateUsage(msg.cost, msg.usage);
|
|
@@ -1542,6 +1624,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1542
1624
|
setActivity(null);
|
|
1543
1625
|
stopThinking();
|
|
1544
1626
|
markAllToolsDone();
|
|
1627
|
+
closeToolGroup();
|
|
1545
1628
|
finalizeAssistantBlock();
|
|
1546
1629
|
processing = false;
|
|
1547
1630
|
setStatus("connected");
|
|
@@ -1564,7 +1647,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1564
1647
|
case "error":
|
|
1565
1648
|
setActivity(null);
|
|
1566
1649
|
addSystemMessage(msg.text, true);
|
|
1567
|
-
updateFavicon("
|
|
1650
|
+
updateFavicon(getComputedVar("--error"));
|
|
1568
1651
|
break;
|
|
1569
1652
|
|
|
1570
1653
|
case "rewind_preview_result":
|
|
@@ -1635,6 +1718,22 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1635
1718
|
case "process_stats":
|
|
1636
1719
|
updateStatusPanel(msg);
|
|
1637
1720
|
break;
|
|
1721
|
+
|
|
1722
|
+
case "browse_dir_result":
|
|
1723
|
+
handleBrowseDirResult(msg);
|
|
1724
|
+
break;
|
|
1725
|
+
|
|
1726
|
+
case "add_project_result":
|
|
1727
|
+
handleAddProjectResult(msg);
|
|
1728
|
+
break;
|
|
1729
|
+
|
|
1730
|
+
case "remove_project_result":
|
|
1731
|
+
handleRemoveProjectResult(msg);
|
|
1732
|
+
break;
|
|
1733
|
+
|
|
1734
|
+
case "projects_updated":
|
|
1735
|
+
updateProjectList(msg);
|
|
1736
|
+
break;
|
|
1638
1737
|
}
|
|
1639
1738
|
}
|
|
1640
1739
|
|
|
@@ -1807,6 +1906,231 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1807
1906
|
fileViewerEl: $("file-viewer"),
|
|
1808
1907
|
});
|
|
1809
1908
|
|
|
1909
|
+
// --- Remove project ---
|
|
1910
|
+
function confirmRemoveProject(slug, name) {
|
|
1911
|
+
showConfirm("Remove project \"" + name + "\"?", function () {
|
|
1912
|
+
if (ws && ws.readyState === 1) {
|
|
1913
|
+
ws.send(JSON.stringify({ type: "remove_project", slug: slug }));
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function handleRemoveProjectResult(msg) {
|
|
1919
|
+
if (msg.ok) {
|
|
1920
|
+
showToast("Project removed", "success");
|
|
1921
|
+
// If we removed the current project, navigate to first available
|
|
1922
|
+
if (msg.slug === currentSlug) {
|
|
1923
|
+
window.location.href = "/";
|
|
1924
|
+
}
|
|
1925
|
+
} else {
|
|
1926
|
+
showToast(msg.error || "Failed to remove project", "error");
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// --- Add project modal ---
|
|
1931
|
+
var addProjectModal = document.getElementById("add-project-modal");
|
|
1932
|
+
var addProjectInput = document.getElementById("add-project-input");
|
|
1933
|
+
var addProjectSuggestions = document.getElementById("add-project-suggestions");
|
|
1934
|
+
var addProjectError = document.getElementById("add-project-error");
|
|
1935
|
+
var addProjectOk = document.getElementById("add-project-ok");
|
|
1936
|
+
var addProjectCancel = document.getElementById("add-project-cancel");
|
|
1937
|
+
var addProjectDebounce = null;
|
|
1938
|
+
var addProjectActiveIdx = -1;
|
|
1939
|
+
|
|
1940
|
+
function openAddProjectModal() {
|
|
1941
|
+
addProjectModal.classList.remove("hidden");
|
|
1942
|
+
addProjectInput.value = "/";
|
|
1943
|
+
addProjectError.classList.add("hidden");
|
|
1944
|
+
addProjectError.textContent = "";
|
|
1945
|
+
addProjectSuggestions.classList.add("hidden");
|
|
1946
|
+
addProjectSuggestions.innerHTML = "";
|
|
1947
|
+
addProjectActiveIdx = -1;
|
|
1948
|
+
addProjectOk.disabled = false;
|
|
1949
|
+
setTimeout(function () {
|
|
1950
|
+
addProjectInput.focus();
|
|
1951
|
+
addProjectInput.setSelectionRange(1, 1);
|
|
1952
|
+
}, 50);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
function closeAddProjectModal() {
|
|
1956
|
+
addProjectModal.classList.add("hidden");
|
|
1957
|
+
addProjectInput.value = "";
|
|
1958
|
+
addProjectSuggestions.classList.add("hidden");
|
|
1959
|
+
addProjectSuggestions.innerHTML = "";
|
|
1960
|
+
addProjectError.classList.add("hidden");
|
|
1961
|
+
addProjectActiveIdx = -1;
|
|
1962
|
+
if (addProjectDebounce) { clearTimeout(addProjectDebounce); addProjectDebounce = null; }
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function requestBrowseDir(val) {
|
|
1966
|
+
if (!ws || ws.readyState !== 1) return;
|
|
1967
|
+
ws.send(JSON.stringify({ type: "browse_dir", path: val }));
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function handleBrowseDirResult(msg) {
|
|
1971
|
+
addProjectSuggestions.innerHTML = "";
|
|
1972
|
+
addProjectActiveIdx = -1;
|
|
1973
|
+
if (msg.error) {
|
|
1974
|
+
addProjectSuggestions.classList.add("hidden");
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
var entries = msg.entries || [];
|
|
1978
|
+
if (entries.length === 0) {
|
|
1979
|
+
addProjectSuggestions.classList.add("hidden");
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
for (var si = 0; si < entries.length; si++) {
|
|
1983
|
+
var entry = entries[si];
|
|
1984
|
+
var item = document.createElement("div");
|
|
1985
|
+
item.className = "add-project-suggestion-item";
|
|
1986
|
+
item.dataset.path = entry.path;
|
|
1987
|
+
item.innerHTML = '<i data-lucide="folder"></i><span class="add-project-suggestion-name">' +
|
|
1988
|
+
escapeHtml(entry.name) + '</span>';
|
|
1989
|
+
item.addEventListener("click", function (e) {
|
|
1990
|
+
var p = this.dataset.path + "/";
|
|
1991
|
+
addProjectInput.value = p;
|
|
1992
|
+
addProjectInput.focus();
|
|
1993
|
+
addProjectError.classList.add("hidden");
|
|
1994
|
+
requestBrowseDir(p);
|
|
1995
|
+
});
|
|
1996
|
+
addProjectSuggestions.appendChild(item);
|
|
1997
|
+
}
|
|
1998
|
+
addProjectSuggestions.classList.remove("hidden");
|
|
1999
|
+
refreshIcons();
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
function handleAddProjectResult(msg) {
|
|
2003
|
+
if (msg.ok) {
|
|
2004
|
+
closeAddProjectModal();
|
|
2005
|
+
if (msg.existing) {
|
|
2006
|
+
showToast("Project already registered", "info");
|
|
2007
|
+
} else {
|
|
2008
|
+
showToast("Project added", "success");
|
|
2009
|
+
// Navigate to the new project
|
|
2010
|
+
if (msg.slug) {
|
|
2011
|
+
window.location.href = "/p/" + msg.slug + "/";
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
} else {
|
|
2015
|
+
addProjectError.textContent = msg.error || "Failed to add project";
|
|
2016
|
+
addProjectError.classList.remove("hidden");
|
|
2017
|
+
addProjectOk.disabled = false;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
function setActiveIdx(idx) {
|
|
2022
|
+
var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
|
|
2023
|
+
addProjectActiveIdx = idx;
|
|
2024
|
+
for (var ai = 0; ai < items.length; ai++) {
|
|
2025
|
+
if (ai === idx) {
|
|
2026
|
+
items[ai].classList.add("active");
|
|
2027
|
+
items[ai].scrollIntoView({ block: "nearest" });
|
|
2028
|
+
} else {
|
|
2029
|
+
items[ai].classList.remove("active");
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
addProjectInput.addEventListener("focus", function () {
|
|
2035
|
+
var val = addProjectInput.value;
|
|
2036
|
+
if (val && addProjectSuggestions.children.length === 0) {
|
|
2037
|
+
requestBrowseDir(val);
|
|
2038
|
+
} else if (addProjectSuggestions.children.length > 0) {
|
|
2039
|
+
addProjectSuggestions.classList.remove("hidden");
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
|
|
2043
|
+
addProjectModal.querySelector(".confirm-dialog").addEventListener("click", function (e) {
|
|
2044
|
+
if (e.target === addProjectInput || addProjectInput.contains(e.target)) return;
|
|
2045
|
+
if (e.target === addProjectSuggestions || addProjectSuggestions.contains(e.target)) return;
|
|
2046
|
+
addProjectSuggestions.classList.add("hidden");
|
|
2047
|
+
addProjectActiveIdx = -1;
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
addProjectInput.addEventListener("input", function () {
|
|
2051
|
+
var val = addProjectInput.value;
|
|
2052
|
+
addProjectError.classList.add("hidden");
|
|
2053
|
+
if (addProjectDebounce) clearTimeout(addProjectDebounce);
|
|
2054
|
+
addProjectDebounce = setTimeout(function () {
|
|
2055
|
+
requestBrowseDir(val);
|
|
2056
|
+
}, 200);
|
|
2057
|
+
});
|
|
2058
|
+
|
|
2059
|
+
addProjectInput.addEventListener("keydown", function (e) {
|
|
2060
|
+
var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
|
|
2061
|
+
|
|
2062
|
+
if (e.key === "ArrowDown") {
|
|
2063
|
+
e.preventDefault();
|
|
2064
|
+
if (items.length > 0) {
|
|
2065
|
+
var next = addProjectActiveIdx < items.length - 1 ? addProjectActiveIdx + 1 : 0;
|
|
2066
|
+
setActiveIdx(next);
|
|
2067
|
+
}
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
if (e.key === "ArrowUp") {
|
|
2072
|
+
e.preventDefault();
|
|
2073
|
+
if (items.length > 0) {
|
|
2074
|
+
var prev = addProjectActiveIdx > 0 ? addProjectActiveIdx - 1 : items.length - 1;
|
|
2075
|
+
setActiveIdx(prev);
|
|
2076
|
+
}
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
if (e.key === "Tab") {
|
|
2081
|
+
e.preventDefault();
|
|
2082
|
+
var target = addProjectActiveIdx >= 0 && addProjectActiveIdx < items.length
|
|
2083
|
+
? items[addProjectActiveIdx]
|
|
2084
|
+
: items.length > 0 ? items[0] : null;
|
|
2085
|
+
if (target) {
|
|
2086
|
+
var p = target.dataset.path + "/";
|
|
2087
|
+
addProjectInput.value = p;
|
|
2088
|
+
addProjectError.classList.add("hidden");
|
|
2089
|
+
requestBrowseDir(p);
|
|
2090
|
+
}
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
if (e.key === "Enter") {
|
|
2095
|
+
e.preventDefault();
|
|
2096
|
+
// If a suggestion is highlighted, pick it first
|
|
2097
|
+
if (addProjectActiveIdx >= 0 && addProjectActiveIdx < items.length) {
|
|
2098
|
+
var picked = items[addProjectActiveIdx].dataset.path + "/";
|
|
2099
|
+
addProjectInput.value = picked;
|
|
2100
|
+
addProjectError.classList.add("hidden");
|
|
2101
|
+
requestBrowseDir(picked);
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
// Otherwise submit
|
|
2105
|
+
submitAddProject();
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
if (e.key === "Escape") {
|
|
2110
|
+
e.preventDefault();
|
|
2111
|
+
closeAddProjectModal();
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
function submitAddProject() {
|
|
2117
|
+
var val = addProjectInput.value.replace(/\/+$/, "");
|
|
2118
|
+
if (!val) return;
|
|
2119
|
+
addProjectOk.disabled = true;
|
|
2120
|
+
addProjectError.classList.add("hidden");
|
|
2121
|
+
if (ws && ws.readyState === 1) {
|
|
2122
|
+
ws.send(JSON.stringify({ type: "add_project", path: val }));
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
addProjectOk.addEventListener("click", function () { submitAddProject(); });
|
|
2127
|
+
addProjectCancel.addEventListener("click", function () { closeAddProjectModal(); });
|
|
2128
|
+
|
|
2129
|
+
// Close on backdrop click
|
|
2130
|
+
addProjectModal.querySelector(".confirm-backdrop").addEventListener("click", function () {
|
|
2131
|
+
closeAddProjectModal();
|
|
2132
|
+
});
|
|
2133
|
+
|
|
1810
2134
|
// --- Init ---
|
|
1811
2135
|
lucide.createIcons();
|
|
1812
2136
|
startVerbCycle();
|