claude-relay 2.2.4 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/bin/cli.js +144 -6
- package/lib/config.js +34 -2
- package/lib/daemon.js +54 -2
- package/lib/pages.js +22 -1
- package/lib/project.js +312 -26
- package/lib/public/app.js +339 -18
- package/lib/public/css/base.css +5 -0
- package/lib/public/css/diff.css +128 -0
- package/lib/public/css/filebrowser.css +571 -0
- package/lib/public/css/input.css +3 -0
- package/lib/public/css/menus.css +89 -5
- package/lib/public/css/messages.css +89 -50
- package/lib/public/css/overlays.css +40 -0
- package/lib/public/index.html +102 -19
- package/lib/public/modules/diff.js +398 -0
- package/lib/public/modules/filebrowser.js +1023 -11
- package/lib/public/modules/input.js +96 -2
- package/lib/public/modules/notifications.js +29 -3
- package/lib/public/modules/qrcode.js +11 -2
- package/lib/public/modules/rewind.js +51 -2
- package/lib/public/modules/terminal.js +73 -0
- package/lib/public/modules/tools.js +45 -104
- package/lib/public/modules/utils.js +10 -2
- package/lib/public/style.css +1 -0
- package/lib/public/sw.js +21 -7
- package/lib/push.js +5 -1
- package/lib/sdk-bridge.js +38 -5
- package/lib/server.js +41 -7
- package/lib/sessions.js +14 -5
- package/package.json +1 -1
package/lib/public/app.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { copyToClipboard, escapeHtml } from './modules/utils.js';
|
|
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
4
|
import { initSidebar, renderSessionList, handleSearchResults, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline } 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
|
-
import { initInput, clearPendingImages, handleInputSync, autoResize } from './modules/input.js';
|
|
7
|
+
import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands } from './modules/input.js';
|
|
8
8
|
import { initQrCode } from './modules/qrcode.js';
|
|
9
|
-
import { initFileBrowser, loadRootDirectory, handleFsList, handleFsRead, refreshIfOpen, handleFileChanged, closeFileViewer } from './modules/filebrowser.js';
|
|
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
11
|
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 } from './modules/tools.js';
|
|
12
12
|
|
|
@@ -137,7 +137,10 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
document.addEventListener("keydown", function (e) {
|
|
140
|
-
if (e.key === "Escape")
|
|
140
|
+
if (e.key === "Escape") {
|
|
141
|
+
closeProjectDropdown();
|
|
142
|
+
closeImageModal();
|
|
143
|
+
}
|
|
141
144
|
});
|
|
142
145
|
|
|
143
146
|
if (projectHintDismiss) {
|
|
@@ -156,6 +159,22 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
156
159
|
});
|
|
157
160
|
$("mermaid-modal").querySelector(".confirm-backdrop").addEventListener("click", closeMermaidModal);
|
|
158
161
|
$("mermaid-modal").querySelector(".mermaid-modal-btn[title='Close']").addEventListener("click", closeMermaidModal);
|
|
162
|
+
$("image-modal").querySelector(".confirm-backdrop").addEventListener("click", closeImageModal);
|
|
163
|
+
$("image-modal").querySelector(".image-modal-close").addEventListener("click", closeImageModal);
|
|
164
|
+
|
|
165
|
+
function showImageModal(src) {
|
|
166
|
+
var modal = $("image-modal");
|
|
167
|
+
var img = $("image-modal-img");
|
|
168
|
+
if (!modal || !img) return;
|
|
169
|
+
img.src = src;
|
|
170
|
+
modal.classList.remove("hidden");
|
|
171
|
+
refreshIcons(modal);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function closeImageModal() {
|
|
175
|
+
var modal = $("image-modal");
|
|
176
|
+
if (modal) modal.classList.add("hidden");
|
|
177
|
+
}
|
|
159
178
|
|
|
160
179
|
// --- State ---
|
|
161
180
|
var ws = null;
|
|
@@ -166,6 +185,8 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
166
185
|
// isComposing -> modules/input.js
|
|
167
186
|
var reconnectTimer = null;
|
|
168
187
|
var reconnectDelay = 1000;
|
|
188
|
+
var disconnectNotifTimer = null;
|
|
189
|
+
var disconnectNotifShown = false;
|
|
169
190
|
var activityEl = null;
|
|
170
191
|
var currentMsgEl = null;
|
|
171
192
|
var currentFullText = "";
|
|
@@ -657,6 +678,229 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
657
678
|
});
|
|
658
679
|
}
|
|
659
680
|
|
|
681
|
+
// --- Status panel ---
|
|
682
|
+
var statusPanel = $("status-panel");
|
|
683
|
+
var statusPanelClose = $("status-panel-close");
|
|
684
|
+
var statusPidEl = $("status-pid");
|
|
685
|
+
var statusUptimeEl = $("status-uptime");
|
|
686
|
+
var statusRssEl = $("status-rss");
|
|
687
|
+
var statusHeapUsedEl = $("status-heap-used");
|
|
688
|
+
var statusHeapTotalEl = $("status-heap-total");
|
|
689
|
+
var statusExternalEl = $("status-external");
|
|
690
|
+
var statusSessionsEl = $("status-sessions");
|
|
691
|
+
var statusProcessingEl = $("status-processing");
|
|
692
|
+
var statusClientsEl = $("status-clients");
|
|
693
|
+
var statusTerminalsEl = $("status-terminals");
|
|
694
|
+
var statusRefreshTimer = null;
|
|
695
|
+
|
|
696
|
+
function formatBytes(n) {
|
|
697
|
+
if (n >= 1073741824) return (n / 1073741824).toFixed(1) + " GB";
|
|
698
|
+
if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB";
|
|
699
|
+
if (n >= 1024) return (n / 1024).toFixed(1) + " KB";
|
|
700
|
+
return n + " B";
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function formatUptime(seconds) {
|
|
704
|
+
var d = Math.floor(seconds / 86400);
|
|
705
|
+
var h = Math.floor((seconds % 86400) / 3600);
|
|
706
|
+
var m = Math.floor((seconds % 3600) / 60);
|
|
707
|
+
var s = Math.floor(seconds % 60);
|
|
708
|
+
if (d > 0) return d + "d " + h + "h " + m + "m";
|
|
709
|
+
if (h > 0) return h + "h " + m + "m " + s + "s";
|
|
710
|
+
return m + "m " + s + "s";
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function updateStatusPanel(data) {
|
|
714
|
+
if (!statusPidEl) return;
|
|
715
|
+
statusPidEl.textContent = String(data.pid);
|
|
716
|
+
statusUptimeEl.textContent = formatUptime(data.uptime);
|
|
717
|
+
statusRssEl.textContent = formatBytes(data.memory.rss);
|
|
718
|
+
statusHeapUsedEl.textContent = formatBytes(data.memory.heapUsed);
|
|
719
|
+
statusHeapTotalEl.textContent = formatBytes(data.memory.heapTotal);
|
|
720
|
+
statusExternalEl.textContent = formatBytes(data.memory.external);
|
|
721
|
+
statusSessionsEl.textContent = String(data.sessions);
|
|
722
|
+
statusProcessingEl.textContent = String(data.processing);
|
|
723
|
+
statusClientsEl.textContent = String(data.clients);
|
|
724
|
+
statusTerminalsEl.textContent = String(data.terminals);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function requestProcessStats() {
|
|
728
|
+
if (ws && ws.readyState === 1) {
|
|
729
|
+
ws.send(JSON.stringify({ type: "process_stats" }));
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function toggleStatusPanel() {
|
|
734
|
+
if (!statusPanel) return;
|
|
735
|
+
var opening = statusPanel.classList.contains("hidden");
|
|
736
|
+
statusPanel.classList.toggle("hidden");
|
|
737
|
+
if (opening) {
|
|
738
|
+
requestProcessStats();
|
|
739
|
+
statusRefreshTimer = setInterval(requestProcessStats, 5000);
|
|
740
|
+
} else {
|
|
741
|
+
if (statusRefreshTimer) {
|
|
742
|
+
clearInterval(statusRefreshTimer);
|
|
743
|
+
statusRefreshTimer = null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
refreshIcons();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (statusPanelClose) {
|
|
750
|
+
statusPanelClose.addEventListener("click", function () {
|
|
751
|
+
statusPanel.classList.add("hidden");
|
|
752
|
+
if (statusRefreshTimer) {
|
|
753
|
+
clearInterval(statusRefreshTimer);
|
|
754
|
+
statusRefreshTimer = null;
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// --- Context panel ---
|
|
760
|
+
var contextPanel = $("context-panel");
|
|
761
|
+
var contextPanelClose = $("context-panel-close");
|
|
762
|
+
var contextPanelMinimize = $("context-panel-minimize");
|
|
763
|
+
var contextBarFill = $("context-bar-fill");
|
|
764
|
+
var contextBarPct = $("context-bar-pct");
|
|
765
|
+
var contextUsedEl = $("context-used");
|
|
766
|
+
var contextWindowEl = $("context-window");
|
|
767
|
+
var contextMaxOutputEl = $("context-max-output");
|
|
768
|
+
var contextInputEl = $("context-input");
|
|
769
|
+
var contextOutputEl = $("context-output");
|
|
770
|
+
var contextCacheReadEl = $("context-cache-read");
|
|
771
|
+
var contextCacheWriteEl = $("context-cache-write");
|
|
772
|
+
var contextModelEl = $("context-model");
|
|
773
|
+
var contextCostEl = $("context-cost");
|
|
774
|
+
var contextTurnsEl = $("context-turns");
|
|
775
|
+
var contextMini = $("context-mini");
|
|
776
|
+
var contextMiniFill = $("context-mini-fill");
|
|
777
|
+
var contextMiniLabel = $("context-mini-label");
|
|
778
|
+
var contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
779
|
+
|
|
780
|
+
function contextPctClass(pct) {
|
|
781
|
+
return pct >= 85 ? " danger" : pct >= 60 ? " warn" : "";
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function updateContextPanel() {
|
|
785
|
+
if (!contextUsedEl) return;
|
|
786
|
+
// Context window usage = input tokens (includes cache read/write) + output tokens
|
|
787
|
+
var used = contextData.input + contextData.output;
|
|
788
|
+
var win = contextData.contextWindow;
|
|
789
|
+
var pct = win > 0 ? Math.min(100, (used / win) * 100) : 0;
|
|
790
|
+
var cls = contextPctClass(pct);
|
|
791
|
+
// Panel bar
|
|
792
|
+
contextBarFill.style.width = pct.toFixed(1) + "%";
|
|
793
|
+
contextBarFill.className = "context-bar-fill" + cls;
|
|
794
|
+
contextBarPct.textContent = pct.toFixed(0) + "%";
|
|
795
|
+
// Mini bar
|
|
796
|
+
if (contextMiniFill) {
|
|
797
|
+
contextMiniFill.style.width = pct.toFixed(1) + "%";
|
|
798
|
+
contextMiniFill.className = "context-mini-fill" + cls;
|
|
799
|
+
}
|
|
800
|
+
if (contextMiniLabel) {
|
|
801
|
+
contextMiniLabel.textContent = (win > 0 ? formatTokens(used) + "/" + formatTokens(win) : "0%");
|
|
802
|
+
}
|
|
803
|
+
contextUsedEl.textContent = formatTokens(used);
|
|
804
|
+
contextWindowEl.textContent = win > 0 ? formatTokens(win) : "-";
|
|
805
|
+
contextMaxOutputEl.textContent = contextData.maxOutputTokens > 0 ? formatTokens(contextData.maxOutputTokens) : "-";
|
|
806
|
+
contextInputEl.textContent = formatTokens(contextData.input);
|
|
807
|
+
contextOutputEl.textContent = formatTokens(contextData.output);
|
|
808
|
+
contextCacheReadEl.textContent = formatTokens(contextData.cacheRead);
|
|
809
|
+
contextCacheWriteEl.textContent = formatTokens(contextData.cacheWrite);
|
|
810
|
+
contextModelEl.textContent = contextData.model;
|
|
811
|
+
contextCostEl.textContent = "$" + contextData.cost.toFixed(4);
|
|
812
|
+
contextTurnsEl.textContent = String(contextData.turns);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function accumulateContext(cost, usage, modelUsage) {
|
|
816
|
+
if (cost != null) contextData.cost += cost;
|
|
817
|
+
// Use latest turn values (not cumulative) since each turn's input_tokens
|
|
818
|
+
// already includes the full conversation context up to that point
|
|
819
|
+
if (usage) {
|
|
820
|
+
contextData.input = usage.input_tokens || usage.inputTokens || 0;
|
|
821
|
+
contextData.output = usage.output_tokens || usage.outputTokens || 0;
|
|
822
|
+
contextData.cacheRead = usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0;
|
|
823
|
+
contextData.cacheWrite = usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
|
|
824
|
+
}
|
|
825
|
+
contextData.turns++;
|
|
826
|
+
if (modelUsage) {
|
|
827
|
+
var models = Object.keys(modelUsage);
|
|
828
|
+
if (models.length > 0) {
|
|
829
|
+
var m = models[0];
|
|
830
|
+
var mu = modelUsage[m];
|
|
831
|
+
contextData.model = m;
|
|
832
|
+
if (mu.contextWindow) contextData.contextWindow = mu.contextWindow;
|
|
833
|
+
if (mu.maxOutputTokens) contextData.maxOutputTokens = mu.maxOutputTokens;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
updateContextPanel();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// contextView: "off" | "mini" | "panel"
|
|
840
|
+
function getContextView() {
|
|
841
|
+
try { return localStorage.getItem("claude-relay-context-view") || "off"; } catch (e) { return "off"; }
|
|
842
|
+
}
|
|
843
|
+
function setContextView(v) {
|
|
844
|
+
try { localStorage.setItem("claude-relay-context-view", v); } catch (e) {}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function applyContextView(view) {
|
|
848
|
+
if (contextPanel) contextPanel.classList.toggle("hidden", view !== "panel");
|
|
849
|
+
if (contextMini) contextMini.classList.toggle("hidden", view !== "mini");
|
|
850
|
+
if (view === "panel") refreshIcons();
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function resetContextData() {
|
|
854
|
+
contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
855
|
+
updateContextPanel();
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function resetContext() {
|
|
859
|
+
resetContextData();
|
|
860
|
+
// Keep view state, just reset data
|
|
861
|
+
applyContextView(getContextView());
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function minimizeContext() {
|
|
865
|
+
setContextView("mini");
|
|
866
|
+
applyContextView("mini");
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function expandContext() {
|
|
870
|
+
setContextView("panel");
|
|
871
|
+
applyContextView("panel");
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function toggleContextPanel() {
|
|
875
|
+
if (!contextPanel) return;
|
|
876
|
+
var view = getContextView();
|
|
877
|
+
if (view === "panel") {
|
|
878
|
+
setContextView("mini");
|
|
879
|
+
applyContextView("mini");
|
|
880
|
+
} else {
|
|
881
|
+
setContextView("panel");
|
|
882
|
+
applyContextView("panel");
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (contextPanelClose) {
|
|
887
|
+
contextPanelClose.addEventListener("click", function () {
|
|
888
|
+
setContextView("off");
|
|
889
|
+
applyContextView("off");
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (contextPanelMinimize) {
|
|
894
|
+
contextPanelMinimize.addEventListener("click", minimizeContext);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Restore context view on load
|
|
898
|
+
applyContextView(getContextView());
|
|
899
|
+
|
|
900
|
+
if (contextMini) {
|
|
901
|
+
contextMini.addEventListener("click", expandContext);
|
|
902
|
+
}
|
|
903
|
+
|
|
660
904
|
function addToMessages(el) {
|
|
661
905
|
if (prependAnchor) messagesEl.insertBefore(el, prependAnchor);
|
|
662
906
|
else messagesEl.appendChild(el);
|
|
@@ -732,6 +976,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
732
976
|
div.dataset.turn = ++turnCounter;
|
|
733
977
|
var bubble = document.createElement("div");
|
|
734
978
|
bubble.className = "bubble";
|
|
979
|
+
bubble.dir = "auto";
|
|
735
980
|
|
|
736
981
|
if (images && images.length > 0) {
|
|
737
982
|
var imgRow = document.createElement("div");
|
|
@@ -740,6 +985,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
740
985
|
var img = document.createElement("img");
|
|
741
986
|
img.src = "data:" + images[i].mediaType + ";base64," + images[i].data;
|
|
742
987
|
img.className = "bubble-img";
|
|
988
|
+
img.addEventListener("click", function () { showImageModal(this.src); });
|
|
743
989
|
imgRow.appendChild(img);
|
|
744
990
|
}
|
|
745
991
|
bubble.appendChild(imgRow);
|
|
@@ -781,7 +1027,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
781
1027
|
currentMsgEl = document.createElement("div");
|
|
782
1028
|
currentMsgEl.className = "msg-assistant";
|
|
783
1029
|
currentMsgEl.dataset.turn = turnCounter;
|
|
784
|
-
currentMsgEl.innerHTML = '<div class="md-content"></div>';
|
|
1030
|
+
currentMsgEl.innerHTML = '<div class="md-content" dir="auto"></div>';
|
|
785
1031
|
addToMessages(currentMsgEl);
|
|
786
1032
|
currentFullText = "";
|
|
787
1033
|
}
|
|
@@ -894,6 +1140,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
894
1140
|
setStatus("connected");
|
|
895
1141
|
enableMainInput();
|
|
896
1142
|
resetUsage();
|
|
1143
|
+
resetContext();
|
|
897
1144
|
}
|
|
898
1145
|
|
|
899
1146
|
// --- WebSocket ---
|
|
@@ -920,8 +1167,13 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
920
1167
|
|
|
921
1168
|
ws.onopen = function () {
|
|
922
1169
|
if (connectTimeoutId) { clearTimeout(connectTimeoutId); connectTimeoutId = null; }
|
|
923
|
-
//
|
|
924
|
-
if (
|
|
1170
|
+
// Cancel pending "connection lost" notification if reconnected quickly
|
|
1171
|
+
if (disconnectNotifTimer) {
|
|
1172
|
+
clearTimeout(disconnectNotifTimer);
|
|
1173
|
+
disconnectNotifTimer = null;
|
|
1174
|
+
}
|
|
1175
|
+
// Only show "restored" notification if "lost" was actually shown
|
|
1176
|
+
if (wasConnected && disconnectNotifShown && !document.hasFocus() && "serviceWorker" in navigator) {
|
|
925
1177
|
navigator.serviceWorker.ready.then(function (reg) {
|
|
926
1178
|
reg.showNotification("Claude Relay", {
|
|
927
1179
|
body: "Server connection restored",
|
|
@@ -929,6 +1181,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
929
1181
|
});
|
|
930
1182
|
}).catch(function () {});
|
|
931
1183
|
}
|
|
1184
|
+
disconnectNotifShown = false;
|
|
932
1185
|
wasConnected = true;
|
|
933
1186
|
setStatus("connected");
|
|
934
1187
|
reconnectDelay = 1000;
|
|
@@ -954,14 +1207,20 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
954
1207
|
setStatus("disconnected");
|
|
955
1208
|
processing = false;
|
|
956
1209
|
setActivity(null);
|
|
957
|
-
//
|
|
958
|
-
if (!
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1210
|
+
// Delay "connection lost" notification by 5s to suppress brief disconnects
|
|
1211
|
+
if (!disconnectNotifTimer) {
|
|
1212
|
+
disconnectNotifTimer = setTimeout(function () {
|
|
1213
|
+
disconnectNotifTimer = null;
|
|
1214
|
+
disconnectNotifShown = true;
|
|
1215
|
+
if (!document.hasFocus() && "serviceWorker" in navigator) {
|
|
1216
|
+
navigator.serviceWorker.ready.then(function (reg) {
|
|
1217
|
+
reg.showNotification("Claude Relay", {
|
|
1218
|
+
body: "Server connection lost",
|
|
1219
|
+
tag: "claude-disconnect",
|
|
1220
|
+
});
|
|
1221
|
+
}).catch(function () {});
|
|
1222
|
+
}
|
|
1223
|
+
}, 5000);
|
|
965
1224
|
}
|
|
966
1225
|
scheduleReconnect();
|
|
967
1226
|
};
|
|
@@ -1002,6 +1261,22 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1002
1261
|
if (pendingQuery) {
|
|
1003
1262
|
requestAnimationFrame(function() { buildSearchTimeline(pendingQuery); });
|
|
1004
1263
|
}
|
|
1264
|
+
// Scroll to tool element if navigating from file edit history
|
|
1265
|
+
var nav = getPendingNavigate();
|
|
1266
|
+
if (nav && (nav.toolId || nav.assistantUuid)) {
|
|
1267
|
+
requestAnimationFrame(function() {
|
|
1268
|
+
// Prefer scrolling to the exact tool element
|
|
1269
|
+
var target = nav.toolId ? messagesEl.querySelector('[data-tool-id="' + nav.toolId + '"]') : null;
|
|
1270
|
+
if (!target && nav.assistantUuid) {
|
|
1271
|
+
target = messagesEl.querySelector('[data-uuid="' + nav.assistantUuid + '"]');
|
|
1272
|
+
}
|
|
1273
|
+
if (target) {
|
|
1274
|
+
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1275
|
+
target.classList.add("message-blink");
|
|
1276
|
+
setTimeout(function() { target.classList.remove("message-blink"); }, 2000);
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1005
1280
|
break;
|
|
1006
1281
|
|
|
1007
1282
|
case "info":
|
|
@@ -1017,6 +1292,11 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1017
1292
|
var debugWrap = $("debug-menu-wrap");
|
|
1018
1293
|
if (debugWrap) debugWrap.classList.remove("hidden");
|
|
1019
1294
|
}
|
|
1295
|
+
if (msg.lanHost) window.__lanHost = msg.lanHost;
|
|
1296
|
+
if (msg.dangerouslySkipPermissions) {
|
|
1297
|
+
var spBanner = $("skip-perms-banner");
|
|
1298
|
+
if (spBanner) spBanner.classList.remove("hidden");
|
|
1299
|
+
}
|
|
1020
1300
|
updateProjectSwitcher(msg);
|
|
1021
1301
|
break;
|
|
1022
1302
|
|
|
@@ -1045,7 +1325,10 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1045
1325
|
break;
|
|
1046
1326
|
|
|
1047
1327
|
case "slash_commands":
|
|
1048
|
-
|
|
1328
|
+
var reserved = new Set(builtinCommands.map(function (c) { return c.name; }));
|
|
1329
|
+
slashCommands = (msg.commands || []).filter(function (name) {
|
|
1330
|
+
return !reserved.has(name);
|
|
1331
|
+
}).map(function (name) {
|
|
1049
1332
|
return { name: name, desc: "Skill" };
|
|
1050
1333
|
});
|
|
1051
1334
|
break;
|
|
@@ -1067,6 +1350,10 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1067
1350
|
}
|
|
1068
1351
|
break;
|
|
1069
1352
|
|
|
1353
|
+
case "toast":
|
|
1354
|
+
showToast(msg.message, msg.level, msg.detail);
|
|
1355
|
+
break;
|
|
1356
|
+
|
|
1070
1357
|
case "input_sync":
|
|
1071
1358
|
handleInputSync(msg.text);
|
|
1072
1359
|
break;
|
|
@@ -1093,6 +1380,9 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1093
1380
|
var draft = sessionDrafts[activeSessionId] || "";
|
|
1094
1381
|
inputEl.value = draft;
|
|
1095
1382
|
autoResize();
|
|
1383
|
+
if (!("ontouchstart" in window)) {
|
|
1384
|
+
inputEl.focus();
|
|
1385
|
+
}
|
|
1096
1386
|
break;
|
|
1097
1387
|
|
|
1098
1388
|
case "session_id":
|
|
@@ -1260,6 +1550,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1260
1550
|
finalizeAssistantBlock();
|
|
1261
1551
|
addTurnMeta(msg.cost, msg.duration);
|
|
1262
1552
|
accumulateUsage(msg.cost, msg.usage);
|
|
1553
|
+
accumulateContext(msg.cost, msg.usage, msg.modelUsage);
|
|
1263
1554
|
break;
|
|
1264
1555
|
|
|
1265
1556
|
case "done":
|
|
@@ -1272,7 +1563,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1272
1563
|
enableMainInput();
|
|
1273
1564
|
resetToolState();
|
|
1274
1565
|
if (document.hidden) {
|
|
1275
|
-
if (isNotifAlertEnabled()) showDoneNotification();
|
|
1566
|
+
if (isNotifAlertEnabled() && !window._pushSubscription) showDoneNotification();
|
|
1276
1567
|
if (isNotifSoundEnabled()) playDoneSound();
|
|
1277
1568
|
}
|
|
1278
1569
|
break;
|
|
@@ -1297,7 +1588,10 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1297
1588
|
|
|
1298
1589
|
case "rewind_complete":
|
|
1299
1590
|
setRewindMode(false);
|
|
1300
|
-
|
|
1591
|
+
var rewindText = "Rewound to earlier point. Files have been restored.";
|
|
1592
|
+
if (msg.mode === "chat") rewindText = "Conversation rewound to earlier point.";
|
|
1593
|
+
else if (msg.mode === "files") rewindText = "Files restored to earlier point.";
|
|
1594
|
+
addSystemMessage(rewindText, false);
|
|
1301
1595
|
break;
|
|
1302
1596
|
|
|
1303
1597
|
case "rewind_error":
|
|
@@ -1317,6 +1611,22 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1317
1611
|
handleFileChanged(msg);
|
|
1318
1612
|
break;
|
|
1319
1613
|
|
|
1614
|
+
case "fs_dir_changed":
|
|
1615
|
+
handleDirChanged(msg);
|
|
1616
|
+
break;
|
|
1617
|
+
|
|
1618
|
+
case "fs_file_history_result":
|
|
1619
|
+
handleFileHistory(msg);
|
|
1620
|
+
break;
|
|
1621
|
+
|
|
1622
|
+
case "fs_git_diff_result":
|
|
1623
|
+
handleGitDiff(msg);
|
|
1624
|
+
break;
|
|
1625
|
+
|
|
1626
|
+
case "fs_file_at_result":
|
|
1627
|
+
handleFileAt(msg);
|
|
1628
|
+
break;
|
|
1629
|
+
|
|
1320
1630
|
case "term_list":
|
|
1321
1631
|
handleTermList(msg);
|
|
1322
1632
|
break;
|
|
@@ -1336,6 +1646,10 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1336
1646
|
case "term_closed":
|
|
1337
1647
|
handleTermClosed(msg);
|
|
1338
1648
|
break;
|
|
1649
|
+
|
|
1650
|
+
case "process_stats":
|
|
1651
|
+
updateStatusPanel(msg);
|
|
1652
|
+
break;
|
|
1339
1653
|
}
|
|
1340
1654
|
}
|
|
1341
1655
|
|
|
@@ -1467,6 +1781,10 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1467
1781
|
addUserMessage: addUserMessage,
|
|
1468
1782
|
addSystemMessage: addSystemMessage,
|
|
1469
1783
|
toggleUsagePanel: toggleUsagePanel,
|
|
1784
|
+
toggleStatusPanel: toggleStatusPanel,
|
|
1785
|
+
toggleContextPanel: toggleContextPanel,
|
|
1786
|
+
resetContextData: resetContextData,
|
|
1787
|
+
showImageModal: showImageModal,
|
|
1470
1788
|
});
|
|
1471
1789
|
|
|
1472
1790
|
// --- Notifications module (viewport, banners, notifications, debug, service worker) ---
|
|
@@ -1479,6 +1797,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1479
1797
|
scrollToBottom: scrollToBottom,
|
|
1480
1798
|
basePath: basePath,
|
|
1481
1799
|
toggleUsagePanel: toggleUsagePanel,
|
|
1800
|
+
toggleStatusPanel: toggleStatusPanel,
|
|
1482
1801
|
});
|
|
1483
1802
|
|
|
1484
1803
|
// --- QR code ---
|
|
@@ -1488,6 +1807,8 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1488
1807
|
initFileBrowser({
|
|
1489
1808
|
get ws() { return ws; },
|
|
1490
1809
|
get connected() { return connected; },
|
|
1810
|
+
get activeSessionId() { return activeSessionId; },
|
|
1811
|
+
messagesEl: messagesEl,
|
|
1491
1812
|
fileTreeEl: $("file-tree"),
|
|
1492
1813
|
fileViewerEl: $("file-viewer"),
|
|
1493
1814
|
});
|
package/lib/public/css/base.css
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Diff Views (unified + split)
|
|
3
|
+
========================================================================== */
|
|
4
|
+
|
|
5
|
+
.diff-unified,
|
|
6
|
+
.diff-split-view {
|
|
7
|
+
overflow: auto;
|
|
8
|
+
font-family: "SF Mono", Menlo, Monaco, monospace;
|
|
9
|
+
font-size: 12px;
|
|
10
|
+
line-height: 1.5;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.diff-table {
|
|
14
|
+
border-collapse: collapse;
|
|
15
|
+
width: 100%;
|
|
16
|
+
table-layout: fixed;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* --- Line numbers --- */
|
|
20
|
+
.diff-ln {
|
|
21
|
+
width: 40px;
|
|
22
|
+
min-width: 40px;
|
|
23
|
+
padding: 0 8px 0 4px;
|
|
24
|
+
text-align: right;
|
|
25
|
+
color: var(--text-dimmer);
|
|
26
|
+
user-select: none;
|
|
27
|
+
vertical-align: top;
|
|
28
|
+
white-space: nowrap;
|
|
29
|
+
opacity: 0.6;
|
|
30
|
+
border-right: 1px solid var(--border-subtle);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* --- Marker column (unified only) --- */
|
|
34
|
+
.diff-marker {
|
|
35
|
+
width: 16px;
|
|
36
|
+
min-width: 16px;
|
|
37
|
+
padding: 0 2px;
|
|
38
|
+
text-align: center;
|
|
39
|
+
color: var(--text-dimmer);
|
|
40
|
+
user-select: none;
|
|
41
|
+
vertical-align: top;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* --- Code cells --- */
|
|
45
|
+
.diff-code {
|
|
46
|
+
padding: 0 12px;
|
|
47
|
+
white-space: pre;
|
|
48
|
+
vertical-align: top;
|
|
49
|
+
tab-size: 2;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* --- Unified diff row colors --- */
|
|
53
|
+
.diff-row-remove {
|
|
54
|
+
background: rgba(229, 83, 75, 0.12);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.diff-row-remove .diff-marker {
|
|
58
|
+
color: var(--diff-remove, #E5534B);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.diff-row-add {
|
|
62
|
+
background: rgba(87, 171, 90, 0.12);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.diff-row-add .diff-marker {
|
|
66
|
+
color: var(--diff-add, #57AB5A);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* --- Hunk header row (patch diffs) --- */
|
|
70
|
+
.diff-row-hunk {
|
|
71
|
+
background: rgba(128, 128, 128, 0.05);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.diff-row-hunk .diff-hunk-text {
|
|
75
|
+
color: var(--text-dimmer);
|
|
76
|
+
font-style: italic;
|
|
77
|
+
padding: 2px 12px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* --- Split diff specifics --- */
|
|
81
|
+
.diff-table-split .diff-code-old,
|
|
82
|
+
.diff-table-split .diff-code-new {
|
|
83
|
+
max-width: 0;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Split: divider between left and right */
|
|
88
|
+
.diff-table-split .diff-code-old {
|
|
89
|
+
border-right: 1px solid var(--border-subtle);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Split: change rows */
|
|
93
|
+
.diff-row-change .diff-code-old,
|
|
94
|
+
.diff-row-remove .diff-code-old {
|
|
95
|
+
background: rgba(229, 83, 75, 0.12);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.diff-row-change .diff-code-new,
|
|
99
|
+
.diff-row-add .diff-code-new {
|
|
100
|
+
background: rgba(87, 171, 90, 0.12);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Empty cells in split view */
|
|
104
|
+
.diff-row-remove .diff-code-new,
|
|
105
|
+
.diff-row-add .diff-code-old {
|
|
106
|
+
background: rgba(128, 128, 128, 0.03);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* --- Compact mode for inline previews --- */
|
|
110
|
+
.diff-compact .diff-table {
|
|
111
|
+
font-size: 11px;
|
|
112
|
+
line-height: 1.4;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.diff-compact .diff-ln {
|
|
116
|
+
width: 28px;
|
|
117
|
+
min-width: 28px;
|
|
118
|
+
padding: 0 4px 0 2px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.diff-compact .diff-code {
|
|
122
|
+
padding: 0 6px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.diff-compact .diff-marker {
|
|
126
|
+
width: 12px;
|
|
127
|
+
min-width: 12px;
|
|
128
|
+
}
|