claude-relay 2.1.3 → 2.2.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 +31 -6
- package/bin/cli.js +7 -3
- package/lib/project.js +130 -39
- package/lib/public/app.js +381 -10
- package/lib/public/css/base.css +7 -0
- package/lib/public/css/filebrowser.css +149 -2
- package/lib/public/css/input.css +83 -10
- package/lib/public/css/menus.css +281 -1
- package/lib/public/css/messages.css +191 -0
- package/lib/public/css/sidebar.css +93 -1
- package/lib/public/index.html +90 -9
- package/lib/public/modules/filebrowser.js +33 -2
- package/lib/public/modules/input.js +98 -1
- package/lib/public/modules/notifications.js +19 -1
- package/lib/public/modules/qrcode.js +7 -1
- package/lib/public/modules/sidebar.js +233 -3
- package/lib/public/modules/terminal.js +484 -74
- package/lib/public/modules/tools.js +346 -2
- package/lib/public/sw.js +2 -5
- package/lib/push.js +16 -0
- package/lib/sdk-bridge.js +56 -6
- package/lib/sessions.js +34 -0
- package/lib/terminal-manager.js +187 -0
- package/lib/terminal.js +3 -3
- package/lib/usage.js +90 -0
- package/package.json +1 -1
package/lib/public/app.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { 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, updatePageTitle } from './modules/sidebar.js';
|
|
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 } from './modules/input.js';
|
|
7
|
+
import { initInput, clearPendingImages, handleInputSync, autoResize } from './modules/input.js';
|
|
8
8
|
import { initQrCode } from './modules/qrcode.js';
|
|
9
|
-
import { initFileBrowser, loadRootDirectory, handleFsList, handleFsRead, refreshIfOpen } from './modules/filebrowser.js';
|
|
10
|
-
import { initTerminal, openTerminal, handleTermOutput, handleTermExited,
|
|
11
|
-
import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, 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';
|
|
9
|
+
import { initFileBrowser, loadRootDirectory, handleFsList, handleFsRead, refreshIfOpen, handleFileChanged, closeFileViewer } from './modules/filebrowser.js';
|
|
10
|
+
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed } from './modules/terminal.js';
|
|
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
|
|
|
13
13
|
// --- Base path for multi-project routing ---
|
|
14
14
|
var slugMatch = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
|
|
@@ -172,6 +172,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
172
172
|
// tools, currentThinking -> modules/tools.js
|
|
173
173
|
var highlightTimer = null;
|
|
174
174
|
var activeSessionId = null;
|
|
175
|
+
var sessionDrafts = {};
|
|
175
176
|
var slashCommands = [];
|
|
176
177
|
// slashActiveIdx, slashFiltered, pendingImages, pendingPastes -> modules/input.js
|
|
177
178
|
// pendingPermissions -> modules/tools.js
|
|
@@ -189,6 +190,10 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
189
190
|
var loadingMore = false;
|
|
190
191
|
var historySentinelObserver = null;
|
|
191
192
|
|
|
193
|
+
// --- Scroll lock ---
|
|
194
|
+
var isUserScrolledUp = false;
|
|
195
|
+
var scrollThreshold = 150;
|
|
196
|
+
|
|
192
197
|
// builtinCommands -> modules/input.js
|
|
193
198
|
|
|
194
199
|
// --- Confirm modal ---
|
|
@@ -247,6 +252,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
247
252
|
get ws() { return ws; },
|
|
248
253
|
get connected() { return connected; },
|
|
249
254
|
get projectName() { return projectName; },
|
|
255
|
+
messagesEl: messagesEl,
|
|
250
256
|
sessionListEl: sessionListEl,
|
|
251
257
|
sidebar: sidebar,
|
|
252
258
|
sidebarOverlay: sidebarOverlay,
|
|
@@ -496,6 +502,8 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
496
502
|
connectOverlay.classList.add("hidden");
|
|
497
503
|
stopVerbCycle();
|
|
498
504
|
updateFavicon("#57AB5A");
|
|
505
|
+
if (usageFab) usageFab.classList.remove("hidden");
|
|
506
|
+
if (usageHeaderBtn) usageHeaderBtn.classList.remove("hidden");
|
|
499
507
|
} else if (status === "processing") {
|
|
500
508
|
statusDot.classList.add("processing");
|
|
501
509
|
processing = true;
|
|
@@ -533,13 +541,307 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
533
541
|
}
|
|
534
542
|
}
|
|
535
543
|
|
|
544
|
+
// --- Model selector ---
|
|
545
|
+
var modelMenuWrap = $("model-menu-wrap");
|
|
546
|
+
var modelBtn = $("model-btn");
|
|
547
|
+
var modelLabel = $("model-label");
|
|
548
|
+
var modelMenu = $("model-menu");
|
|
549
|
+
|
|
550
|
+
function modelDisplayName(value, models) {
|
|
551
|
+
if (!value) return "";
|
|
552
|
+
// Look up displayName from models list
|
|
553
|
+
if (models) {
|
|
554
|
+
for (var i = 0; i < models.length; i++) {
|
|
555
|
+
if (models[i].value === value && models[i].displayName) return models[i].displayName;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return value;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
var currentModels = [];
|
|
562
|
+
|
|
563
|
+
function updateModelSelector(current, models) {
|
|
564
|
+
if (!modelMenuWrap || !modelBtn || !modelMenu) return;
|
|
565
|
+
currentModels = models;
|
|
566
|
+
modelLabel.textContent = modelDisplayName(current, models);
|
|
567
|
+
modelMenuWrap.classList.remove("hidden");
|
|
568
|
+
|
|
569
|
+
modelMenu.innerHTML = "";
|
|
570
|
+
var list = models.length > 0 ? models : (current ? [{ value: current, displayName: current }] : []);
|
|
571
|
+
for (var i = 0; i < list.length; i++) {
|
|
572
|
+
var item = list[i];
|
|
573
|
+
var value = item.value || "";
|
|
574
|
+
var label = item.displayName || value;
|
|
575
|
+
var btn = document.createElement("button");
|
|
576
|
+
btn.className = "model-menu-item";
|
|
577
|
+
if (value === current) btn.classList.add("active");
|
|
578
|
+
btn.dataset.model = value;
|
|
579
|
+
btn.textContent = label;
|
|
580
|
+
btn.addEventListener("click", function () {
|
|
581
|
+
var model = this.dataset.model;
|
|
582
|
+
if (ws && ws.readyState === 1) {
|
|
583
|
+
ws.send(JSON.stringify({ type: "set_model", model: model }));
|
|
584
|
+
}
|
|
585
|
+
modelMenu.classList.add("hidden");
|
|
586
|
+
modelBtn.classList.remove("active");
|
|
587
|
+
});
|
|
588
|
+
modelMenu.appendChild(btn);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
modelBtn.addEventListener("click", function (e) {
|
|
593
|
+
e.stopPropagation();
|
|
594
|
+
var open = modelMenu.classList.toggle("hidden");
|
|
595
|
+
modelBtn.classList.toggle("active", !open);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
document.addEventListener("click", function (e) {
|
|
599
|
+
if (!modelMenu.contains(e.target) && e.target !== modelBtn) {
|
|
600
|
+
modelMenu.classList.add("hidden");
|
|
601
|
+
modelBtn.classList.remove("active");
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// --- Usage panel ---
|
|
606
|
+
var usagePanel = $("usage-panel");
|
|
607
|
+
var usagePanelClose = $("usage-panel-close");
|
|
608
|
+
var usageFab = $("usage-fab");
|
|
609
|
+
var usageHeaderBtn = $("usage-header-btn");
|
|
610
|
+
var usageCostEl = $("usage-cost");
|
|
611
|
+
var usageInputEl = $("usage-input");
|
|
612
|
+
var usageOutputEl = $("usage-output");
|
|
613
|
+
var usageCacheReadEl = $("usage-cache-read");
|
|
614
|
+
var usageCacheWriteEl = $("usage-cache-write");
|
|
615
|
+
var usageTurnsEl = $("usage-turns");
|
|
616
|
+
var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
617
|
+
|
|
618
|
+
// Rate limit bar elements
|
|
619
|
+
var usageLoading = $("usage-loading");
|
|
620
|
+
var usageError = $("usage-error");
|
|
621
|
+
var usageBars = $("usage-bars");
|
|
622
|
+
var usageRateLimitFetched = false;
|
|
623
|
+
|
|
624
|
+
function formatTokens(n) {
|
|
625
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + "M";
|
|
626
|
+
if (n >= 1000) return (n / 1000).toFixed(1) + "K";
|
|
627
|
+
return String(n);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function formatTimeUntil(isoStr) {
|
|
631
|
+
if (!isoStr) return "";
|
|
632
|
+
var d = new Date(isoStr);
|
|
633
|
+
var now = Date.now();
|
|
634
|
+
var diff = d.getTime() - now;
|
|
635
|
+
if (diff <= 0) return "Resets soon";
|
|
636
|
+
var hours = Math.floor(diff / 3600000);
|
|
637
|
+
var minutes = Math.floor((diff % 3600000) / 60000);
|
|
638
|
+
var relative;
|
|
639
|
+
if (hours > 24) {
|
|
640
|
+
var days = Math.floor(hours / 24);
|
|
641
|
+
relative = days + "d " + (hours % 24) + "h";
|
|
642
|
+
} else if (hours > 0) {
|
|
643
|
+
relative = hours + "h " + minutes + "m";
|
|
644
|
+
} else {
|
|
645
|
+
relative = minutes + "m";
|
|
646
|
+
}
|
|
647
|
+
// Absolute time: "Mon 2/17 15:00" or "15:00" if today
|
|
648
|
+
var nowDate = new Date(now);
|
|
649
|
+
var month = d.getMonth() + 1;
|
|
650
|
+
var day = d.getDate();
|
|
651
|
+
var hh = String(d.getHours()).padStart(2, "0");
|
|
652
|
+
var mm = String(d.getMinutes()).padStart(2, "0");
|
|
653
|
+
var sameDay = d.getFullYear() === nowDate.getFullYear() && d.getMonth() === nowDate.getMonth() && d.getDate() === nowDate.getDate();
|
|
654
|
+
var abs = sameDay ? hh + ":" + mm : month + "/" + day + " " + hh + ":" + mm;
|
|
655
|
+
return "Resets in " + relative + " (" + abs + ")";
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function updateRateLimitBar(prefix, utilization, resetsAt) {
|
|
659
|
+
var pctEl = $("usage-pct-" + prefix);
|
|
660
|
+
var fillEl = $("usage-fill-" + prefix);
|
|
661
|
+
var resetEl = $("usage-reset-" + prefix);
|
|
662
|
+
var groupEl = $("usage-bar-" + prefix);
|
|
663
|
+
if (!pctEl || !fillEl || !resetEl) return;
|
|
664
|
+
|
|
665
|
+
if (utilization == null) {
|
|
666
|
+
if (groupEl) groupEl.classList.add("hidden");
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (groupEl) groupEl.classList.remove("hidden");
|
|
670
|
+
|
|
671
|
+
var pct = Math.max(0, Math.min(100, Math.round(utilization)));
|
|
672
|
+
pctEl.textContent = pct + "%";
|
|
673
|
+
fillEl.style.width = pct + "%";
|
|
674
|
+
fillEl.className = "usage-bar-fill";
|
|
675
|
+
if (prefix === "extra") fillEl.classList.add("usage-bar-fill-extra");
|
|
676
|
+
if (pct >= 90) fillEl.classList.add("critical");
|
|
677
|
+
else if (pct >= 70) fillEl.classList.add("warn");
|
|
678
|
+
|
|
679
|
+
resetEl.textContent = formatTimeUntil(resetsAt);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function handleUsageData(msg) {
|
|
683
|
+
if (!usageLoading || !usageError || !usageBars) return;
|
|
684
|
+
usageLoading.style.display = "none";
|
|
685
|
+
|
|
686
|
+
if (msg.error) {
|
|
687
|
+
usageError.textContent = msg.error;
|
|
688
|
+
usageError.classList.remove("hidden");
|
|
689
|
+
usageBars.classList.add("hidden");
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
usageError.classList.add("hidden");
|
|
694
|
+
usageBars.classList.remove("hidden");
|
|
695
|
+
var data = msg.data || {};
|
|
696
|
+
|
|
697
|
+
// Session (five_hour)
|
|
698
|
+
var session = data.five_hour || {};
|
|
699
|
+
updateRateLimitBar("session", session.utilization, session.resets_at);
|
|
700
|
+
|
|
701
|
+
// Weekly all models (seven_day)
|
|
702
|
+
var weekly = data.seven_day || {};
|
|
703
|
+
updateRateLimitBar("weekly", weekly.utilization, weekly.resets_at);
|
|
704
|
+
|
|
705
|
+
// Weekly Sonnet only (seven_day_sonnet)
|
|
706
|
+
var sonnet = data.seven_day_sonnet || {};
|
|
707
|
+
updateRateLimitBar("sonnet", sonnet.utilization, sonnet.resets_at);
|
|
708
|
+
|
|
709
|
+
// Extra usage
|
|
710
|
+
var extra = data.extra_usage || {};
|
|
711
|
+
var extraGroup = $("usage-bar-extra");
|
|
712
|
+
if (extra.is_enabled) {
|
|
713
|
+
if (extraGroup) extraGroup.classList.remove("hidden");
|
|
714
|
+
// Compute reset time as first of next month
|
|
715
|
+
var now = new Date();
|
|
716
|
+
var nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
717
|
+
updateRateLimitBar("extra", extra.utilization, nextMonth.toISOString());
|
|
718
|
+
var extraResetEl = $("usage-reset-extra");
|
|
719
|
+
if (extraResetEl && extra.monthly_limit != null) {
|
|
720
|
+
var usedDollars = (extra.used_credits / 100).toFixed(2);
|
|
721
|
+
var limitDollars = (extra.monthly_limit / 100).toFixed(2);
|
|
722
|
+
extraResetEl.textContent = "$" + usedDollars + " / $" + limitDollars;
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
if (extraGroup) extraGroup.classList.add("hidden");
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
usageRateLimitFetched = true;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function requestUsageData() {
|
|
732
|
+
if (!ws || ws.readyState !== 1) return;
|
|
733
|
+
if (usageLoading) usageLoading.style.display = "";
|
|
734
|
+
if (usageError) usageError.classList.add("hidden");
|
|
735
|
+
ws.send(JSON.stringify({ type: "get_usage" }));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function updateUsagePanel() {
|
|
739
|
+
if (!usageCostEl) return;
|
|
740
|
+
usageCostEl.textContent = "$" + sessionUsage.cost.toFixed(4);
|
|
741
|
+
usageInputEl.textContent = formatTokens(sessionUsage.input);
|
|
742
|
+
usageOutputEl.textContent = formatTokens(sessionUsage.output);
|
|
743
|
+
usageCacheReadEl.textContent = formatTokens(sessionUsage.cacheRead);
|
|
744
|
+
usageCacheWriteEl.textContent = formatTokens(sessionUsage.cacheWrite);
|
|
745
|
+
usageTurnsEl.textContent = String(sessionUsage.turns);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function accumulateUsage(cost, usage) {
|
|
749
|
+
if (cost != null) sessionUsage.cost += cost;
|
|
750
|
+
if (usage) {
|
|
751
|
+
sessionUsage.input += usage.input_tokens || usage.inputTokens || 0;
|
|
752
|
+
sessionUsage.output += usage.output_tokens || usage.outputTokens || 0;
|
|
753
|
+
sessionUsage.cacheRead += usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0;
|
|
754
|
+
sessionUsage.cacheWrite += usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
|
|
755
|
+
}
|
|
756
|
+
sessionUsage.turns++;
|
|
757
|
+
updateUsagePanel();
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function resetUsage() {
|
|
761
|
+
sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
762
|
+
updateUsagePanel();
|
|
763
|
+
if (usagePanel) usagePanel.classList.add("hidden");
|
|
764
|
+
if (usageFab) usageFab.classList.remove("active");
|
|
765
|
+
if (usageHeaderBtn) usageHeaderBtn.classList.remove("active");
|
|
766
|
+
usageRateLimitFetched = false;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function toggleUsagePanel() {
|
|
770
|
+
if (!usagePanel) return;
|
|
771
|
+
var isHidden = usagePanel.classList.toggle("hidden");
|
|
772
|
+
if (usageFab) usageFab.classList.toggle("active", !isHidden);
|
|
773
|
+
if (usageHeaderBtn) usageHeaderBtn.classList.toggle("active", !isHidden);
|
|
774
|
+
// Fetch rate limit data when opening
|
|
775
|
+
if (!isHidden) {
|
|
776
|
+
requestUsageData();
|
|
777
|
+
}
|
|
778
|
+
refreshIcons();
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (usagePanelClose) {
|
|
782
|
+
usagePanelClose.addEventListener("click", function () {
|
|
783
|
+
usagePanel.classList.add("hidden");
|
|
784
|
+
if (usageFab) usageFab.classList.remove("active");
|
|
785
|
+
if (usageHeaderBtn) usageHeaderBtn.classList.remove("active");
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (usageFab) {
|
|
790
|
+
usageFab.addEventListener("click", function () {
|
|
791
|
+
toggleUsagePanel();
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (usageHeaderBtn) {
|
|
796
|
+
usageHeaderBtn.addEventListener("click", function () {
|
|
797
|
+
toggleUsagePanel();
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
536
801
|
function addToMessages(el) {
|
|
537
802
|
if (prependAnchor) messagesEl.insertBefore(el, prependAnchor);
|
|
538
803
|
else messagesEl.appendChild(el);
|
|
539
804
|
}
|
|
540
805
|
|
|
806
|
+
var newMsgBtn = $("new-msg-btn");
|
|
807
|
+
var newMsgBtnDefault = "\u2193 Latest";
|
|
808
|
+
var newMsgBtnActivity = "\u2193 New activity";
|
|
809
|
+
|
|
810
|
+
messagesEl.addEventListener("scroll", function () {
|
|
811
|
+
var distFromBottom = messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight;
|
|
812
|
+
isUserScrolledUp = distFromBottom > scrollThreshold;
|
|
813
|
+
if (isUserScrolledUp) {
|
|
814
|
+
if (newMsgBtn.classList.contains("hidden")) {
|
|
815
|
+
newMsgBtn.textContent = newMsgBtnDefault;
|
|
816
|
+
}
|
|
817
|
+
newMsgBtn.classList.remove("hidden");
|
|
818
|
+
} else {
|
|
819
|
+
newMsgBtn.classList.add("hidden");
|
|
820
|
+
newMsgBtn.textContent = newMsgBtnDefault;
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
newMsgBtn.addEventListener("click", function () {
|
|
825
|
+
forceScrollToBottom();
|
|
826
|
+
});
|
|
827
|
+
|
|
541
828
|
function scrollToBottom() {
|
|
542
829
|
if (prependAnchor) return;
|
|
830
|
+
if (isUserScrolledUp) {
|
|
831
|
+
newMsgBtn.textContent = newMsgBtnActivity;
|
|
832
|
+
newMsgBtn.classList.remove("hidden");
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
requestAnimationFrame(function () {
|
|
836
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function forceScrollToBottom() {
|
|
841
|
+
if (prependAnchor) return;
|
|
842
|
+
isUserScrolledUp = false;
|
|
843
|
+
newMsgBtn.classList.add("hidden");
|
|
844
|
+
newMsgBtn.textContent = newMsgBtnDefault;
|
|
543
845
|
requestAnimationFrame(function () {
|
|
544
846
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
545
847
|
});
|
|
@@ -612,7 +914,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
612
914
|
|
|
613
915
|
div.appendChild(bubble);
|
|
614
916
|
addToMessages(div);
|
|
615
|
-
|
|
917
|
+
forceScrollToBottom();
|
|
616
918
|
}
|
|
617
919
|
|
|
618
920
|
function ensureAssistantBlock() {
|
|
@@ -725,10 +1027,14 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
725
1027
|
historyTotal = 0;
|
|
726
1028
|
prependAnchor = null;
|
|
727
1029
|
loadingMore = false;
|
|
1030
|
+
isUserScrolledUp = false;
|
|
1031
|
+
newMsgBtn.classList.add("hidden");
|
|
728
1032
|
setRewindMode(false);
|
|
1033
|
+
removeSearchTimeline();
|
|
729
1034
|
setActivity(null);
|
|
730
1035
|
setStatus("connected");
|
|
731
1036
|
enableMainInput();
|
|
1037
|
+
resetUsage();
|
|
732
1038
|
}
|
|
733
1039
|
|
|
734
1040
|
// --- WebSocket ---
|
|
@@ -769,6 +1075,9 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
769
1075
|
reconnectDelay = 1000;
|
|
770
1076
|
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
771
1077
|
|
|
1078
|
+
// Reset terminal xterm instances (server will send fresh term_list)
|
|
1079
|
+
resetTerminals();
|
|
1080
|
+
|
|
772
1081
|
// Re-send push subscription on reconnect
|
|
773
1082
|
if (window._pushSubscription) {
|
|
774
1083
|
try {
|
|
@@ -829,6 +1138,13 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
829
1138
|
prependOlderHistory(msg.items, msg.meta);
|
|
830
1139
|
break;
|
|
831
1140
|
|
|
1141
|
+
case "history_done":
|
|
1142
|
+
var pendingQuery = getActiveSearchQuery();
|
|
1143
|
+
if (pendingQuery) {
|
|
1144
|
+
requestAnimationFrame(function() { buildSearchTimeline(pendingQuery); });
|
|
1145
|
+
}
|
|
1146
|
+
break;
|
|
1147
|
+
|
|
832
1148
|
case "info":
|
|
833
1149
|
projectName = msg.project || msg.cwd;
|
|
834
1150
|
if (msg.slug) currentSlug = msg.slug;
|
|
@@ -875,6 +1191,14 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
875
1191
|
});
|
|
876
1192
|
break;
|
|
877
1193
|
|
|
1194
|
+
case "model_info":
|
|
1195
|
+
updateModelSelector(msg.model, msg.models || []);
|
|
1196
|
+
break;
|
|
1197
|
+
|
|
1198
|
+
case "usage_data":
|
|
1199
|
+
handleUsageData(msg);
|
|
1200
|
+
break;
|
|
1201
|
+
|
|
878
1202
|
case "client_count":
|
|
879
1203
|
var countEl = document.getElementById("client-count");
|
|
880
1204
|
if (countEl) {
|
|
@@ -896,10 +1220,24 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
896
1220
|
renderSessionList(msg.sessions || []);
|
|
897
1221
|
break;
|
|
898
1222
|
|
|
1223
|
+
case "search_results":
|
|
1224
|
+
handleSearchResults(msg);
|
|
1225
|
+
break;
|
|
1226
|
+
|
|
899
1227
|
case "session_switched":
|
|
1228
|
+
// Save draft from outgoing session
|
|
1229
|
+
if (activeSessionId && inputEl.value) {
|
|
1230
|
+
sessionDrafts[activeSessionId] = inputEl.value;
|
|
1231
|
+
} else if (activeSessionId) {
|
|
1232
|
+
delete sessionDrafts[activeSessionId];
|
|
1233
|
+
}
|
|
900
1234
|
activeSessionId = msg.id;
|
|
901
1235
|
cliSessionId = msg.cliSessionId || null;
|
|
902
1236
|
resetClientState();
|
|
1237
|
+
// Restore draft for incoming session
|
|
1238
|
+
var draft = sessionDrafts[activeSessionId] || "";
|
|
1239
|
+
inputEl.value = draft;
|
|
1240
|
+
autoResize();
|
|
903
1241
|
break;
|
|
904
1242
|
|
|
905
1243
|
case "session_id":
|
|
@@ -932,6 +1270,14 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
932
1270
|
}
|
|
933
1271
|
break;
|
|
934
1272
|
|
|
1273
|
+
case "compacting":
|
|
1274
|
+
if (msg.active) {
|
|
1275
|
+
setActivity("Compacting conversation...");
|
|
1276
|
+
} else {
|
|
1277
|
+
setActivity(randomThinkingVerb() + "...");
|
|
1278
|
+
}
|
|
1279
|
+
break;
|
|
1280
|
+
|
|
935
1281
|
case "thinking_start":
|
|
936
1282
|
startThinking();
|
|
937
1283
|
break;
|
|
@@ -998,11 +1344,13 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
998
1344
|
}
|
|
999
1345
|
break;
|
|
1000
1346
|
|
|
1001
|
-
case "tool_result":
|
|
1002
|
-
if (msg.content != null) {
|
|
1347
|
+
case "tool_result": {
|
|
1003
1348
|
var tr = getTools()[msg.id];
|
|
1004
1349
|
if (tr && tr.hidden) break; // skip hidden plan tools
|
|
1005
|
-
updateToolResult(
|
|
1350
|
+
// Always call updateToolResult for Edit (to show diff from input), or when content exists
|
|
1351
|
+
if (msg.content != null || (tr && tr.name === "Edit" && tr.input && tr.input.old_string)) {
|
|
1352
|
+
updateToolResult(msg.id, msg.content || "", msg.is_error || false);
|
|
1353
|
+
}
|
|
1006
1354
|
// Refresh file browser if an Edit/Write tool modified the open file
|
|
1007
1355
|
if (!msg.is_error && tr && (tr.name === "Edit" || tr.name === "Write") && tr.input && tr.input.file_path) {
|
|
1008
1356
|
refreshIfOpen(tr.input.file_path);
|
|
@@ -1010,6 +1358,11 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1010
1358
|
}
|
|
1011
1359
|
break;
|
|
1012
1360
|
|
|
1361
|
+
case "ask_user_answered":
|
|
1362
|
+
markAskUserAnswered(msg.toolId);
|
|
1363
|
+
stopUrgentBlink();
|
|
1364
|
+
break;
|
|
1365
|
+
|
|
1013
1366
|
case "permission_request":
|
|
1014
1367
|
renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason);
|
|
1015
1368
|
startUrgentBlink();
|
|
@@ -1051,6 +1404,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1051
1404
|
markAllToolsDone();
|
|
1052
1405
|
finalizeAssistantBlock();
|
|
1053
1406
|
addTurnMeta(msg.cost, msg.duration);
|
|
1407
|
+
accumulateUsage(msg.cost, msg.usage);
|
|
1054
1408
|
break;
|
|
1055
1409
|
|
|
1056
1410
|
case "done":
|
|
@@ -1104,12 +1458,28 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1104
1458
|
handleFsRead(msg);
|
|
1105
1459
|
break;
|
|
1106
1460
|
|
|
1461
|
+
case "fs_file_changed":
|
|
1462
|
+
handleFileChanged(msg);
|
|
1463
|
+
break;
|
|
1464
|
+
|
|
1465
|
+
case "term_list":
|
|
1466
|
+
handleTermList(msg);
|
|
1467
|
+
break;
|
|
1468
|
+
|
|
1469
|
+
case "term_created":
|
|
1470
|
+
handleTermCreated(msg);
|
|
1471
|
+
break;
|
|
1472
|
+
|
|
1107
1473
|
case "term_output":
|
|
1108
1474
|
handleTermOutput(msg);
|
|
1109
1475
|
break;
|
|
1110
1476
|
|
|
1111
1477
|
case "term_exited":
|
|
1112
|
-
handleTermExited();
|
|
1478
|
+
handleTermExited(msg);
|
|
1479
|
+
break;
|
|
1480
|
+
|
|
1481
|
+
case "term_closed":
|
|
1482
|
+
handleTermClosed(msg);
|
|
1113
1483
|
break;
|
|
1114
1484
|
}
|
|
1115
1485
|
}
|
|
@@ -1241,6 +1611,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1241
1611
|
messageUuidMap: function() { return messageUuidMap; },
|
|
1242
1612
|
addUserMessage: addUserMessage,
|
|
1243
1613
|
addSystemMessage: addSystemMessage,
|
|
1614
|
+
toggleUsagePanel: toggleUsagePanel,
|
|
1244
1615
|
});
|
|
1245
1616
|
|
|
1246
1617
|
// --- Notifications module (viewport, banners, notifications, debug, service worker) ---
|
package/lib/public/css/base.css
CHANGED
|
@@ -8,8 +8,15 @@
|
|
|
8
8
|
padding: 0;
|
|
9
9
|
box-sizing: border-box;
|
|
10
10
|
-webkit-tap-highlight-color: transparent;
|
|
11
|
+
scrollbar-width: thin;
|
|
12
|
+
scrollbar-color: rgba(255,255,255,0.15) transparent;
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
16
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
17
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 3px; }
|
|
18
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.25); }
|
|
19
|
+
|
|
13
20
|
:root {
|
|
14
21
|
--bg: #2F2E2B;
|
|
15
22
|
--bg-alt: #35332F;
|