@firstpick/pi-package-webui 0.4.1 → 0.4.3
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 +19 -5
- package/WEBUI_TUI_NATIVE_PARITY.json +2 -2
- package/bin/pi-webui.mjs +336 -16
- package/index.ts +16 -1
- package/lib/trust-boundaries.mjs +1 -0
- package/package.json +5 -3
- package/public/app.js +524 -49
- package/public/index.html +17 -4
- package/public/styles.css +176 -55
- package/tests/http-endpoints-harness.test.mjs +57 -0
- package/tests/mobile-static.test.mjs +61 -10
- package/tests/remote-auth-settings-harness.test.mjs +81 -0
- package/tests/session-auth-harness.test.mjs +4 -0
package/public/app.js
CHANGED
|
@@ -85,6 +85,7 @@ const elements = {
|
|
|
85
85
|
optionsCommandPaletteButton: $("#optionsCommandPaletteButton"),
|
|
86
86
|
optionsResumeButton: $("#optionsResumeButton"),
|
|
87
87
|
optionsReloadButton: $("#optionsReloadButton"),
|
|
88
|
+
optionsRemoteButton: $("#optionsRemoteButton"),
|
|
88
89
|
optionsNameButton: $("#optionsNameButton"),
|
|
89
90
|
optionsCloneButton: $("#optionsCloneButton"),
|
|
90
91
|
optionsSettingsButton: $("#optionsSettingsButton"),
|
|
@@ -127,6 +128,8 @@ const elements = {
|
|
|
127
128
|
backgroundClearButton: $("#backgroundClearButton"),
|
|
128
129
|
backgroundStatus: $("#backgroundStatus"),
|
|
129
130
|
networkStatus: $("#networkStatus"),
|
|
131
|
+
remoteAuthToggle: $("#remoteAuthToggle"),
|
|
132
|
+
remoteAuthStatus: $("#remoteAuthStatus"),
|
|
130
133
|
openNetworkButton: $("#openNetworkButton"),
|
|
131
134
|
serverActionSelect: $("#serverActionSelect"),
|
|
132
135
|
runServerActionButton: $("#runServerActionButton"),
|
|
@@ -195,6 +198,7 @@ const elements = {
|
|
|
195
198
|
commandPaletteInput: $("#commandPaletteInput"),
|
|
196
199
|
commandPaletteList: $("#commandPaletteList"),
|
|
197
200
|
commandPaletteHint: $("#commandPaletteHint"),
|
|
201
|
+
commandPaletteCloseButton: $("#commandPaletteCloseButton"),
|
|
198
202
|
editRetryDialog: $("#editRetryDialog"),
|
|
199
203
|
editRetryMessage: $("#editRetryMessage"),
|
|
200
204
|
editRetryText: $("#editRetryText"),
|
|
@@ -314,6 +318,8 @@ let updateStatusRefreshTimer = null;
|
|
|
314
318
|
let updateNotificationHideTimer = null;
|
|
315
319
|
let backendOfflineNoticeShown = false;
|
|
316
320
|
let latestMessages = [];
|
|
321
|
+
let latestMessagesSessionKey = "";
|
|
322
|
+
const tabMessagesCache = new Map();
|
|
317
323
|
let promptHistoryByTab = new Map();
|
|
318
324
|
let promptHistoryNavigation = null;
|
|
319
325
|
let transientMessages = [];
|
|
@@ -338,6 +344,8 @@ let toolOutputGloballyExpanded = false;
|
|
|
338
344
|
let agentDoneNotificationPermissionRequested = false;
|
|
339
345
|
let agentDoneNotificationFallbackNoted = false;
|
|
340
346
|
let agentDoneNotificationKeys = new Set();
|
|
347
|
+
let pendingAgentDoneNotificationTimers = new Map();
|
|
348
|
+
let autoRetryingTabs = new Set();
|
|
341
349
|
let availableModels = [];
|
|
342
350
|
let availableThemes = [];
|
|
343
351
|
let currentThemeName = "catppuccin-mocha";
|
|
@@ -372,7 +380,17 @@ let workspaceDashboardCollapsed = false;
|
|
|
372
380
|
let commandPaletteIndex = 0;
|
|
373
381
|
let commandPaletteItems = [];
|
|
374
382
|
let activeEditRetry = null;
|
|
383
|
+
let activePointerActivation = null;
|
|
384
|
+
let pointerActivationTimeout = null;
|
|
385
|
+
let deferredChatFollowScroll = false;
|
|
386
|
+
const deferredUiRenderCallbacks = new Map();
|
|
375
387
|
let abortLongPressTimer = null;
|
|
388
|
+
let abortLongPressTickTimer = null;
|
|
389
|
+
let abortLongPressResetTimer = null;
|
|
390
|
+
let abortLongPressStartedAt = 0;
|
|
391
|
+
let abortLongPressDeadlineAt = 0;
|
|
392
|
+
let abortLongPressSource = "long-press";
|
|
393
|
+
let abortLongPressReleasePending = false;
|
|
376
394
|
let abortLongPressHandled = false;
|
|
377
395
|
const dialogQueue = [];
|
|
378
396
|
const SIDE_PANEL_STORAGE_KEY = "pi-webui-side-panel-collapsed";
|
|
@@ -410,6 +428,8 @@ const LAST_USER_PROMPT_STORAGE_KEY = "pi-webui-last-user-prompts";
|
|
|
410
428
|
const PROMPT_HISTORY_STORAGE_KEY = "pi-webui-prompt-history";
|
|
411
429
|
const PROMPT_LIST_STORAGE_KEY = "pi-webui-prompt-lists";
|
|
412
430
|
const WORKSPACE_DASHBOARD_STORAGE_KEY = "pi-webui-workspace-dashboard-collapsed";
|
|
431
|
+
const POINTER_ACTIVATION_SELECTOR = "button, a[href], input, select, textarea, summary, [role='button'], [tabindex]:not([tabindex='-1'])";
|
|
432
|
+
const POINTER_ACTIVATION_RENDER_DEFER_MAX_MS = 1200;
|
|
413
433
|
const PROMPT_HISTORY_LIMIT_PER_TAB = 50;
|
|
414
434
|
const ATTACHMENT_MAX_FILES = 12;
|
|
415
435
|
const ATTACHMENT_MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
@@ -442,7 +462,9 @@ const UPDATE_STATUS_INITIAL_DELAY_MS = 1800;
|
|
|
442
462
|
const RUN_INDICATOR_TICK_MS = 1000;
|
|
443
463
|
const RUN_INDICATOR_START_GRACE_MS = 2500;
|
|
444
464
|
const RUN_INDICATOR_STATE_RECHECK_MS = 5000;
|
|
445
|
-
const ABORT_LONG_PRESS_MS =
|
|
465
|
+
const ABORT_LONG_PRESS_MS = 3000;
|
|
466
|
+
const ABORT_LONG_PRESS_TICK_MS = 100;
|
|
467
|
+
const ABORT_LONG_PRESS_RELEASE_GRACE_MS = 350;
|
|
446
468
|
const STREAM_OUTPUT_HIDE_DELAY_MS = 300;
|
|
447
469
|
const STREAM_OUTPUT_TOOLCALL_GUARD_MS = 220;
|
|
448
470
|
const STREAM_OUTPUT_MIN_VISIBLE_MS = 900;
|
|
@@ -452,6 +474,7 @@ const TODO_PROGRESS_LINE_REGEX = /^\s*(?:(?:[-*]|\d+[.)])\s*)?\[(?: |x|X|-)\]\s+
|
|
|
452
474
|
const TODO_PROGRESS_PARTIAL_LINE_REGEX = /^\s*(?:(?:[-*]|\d+[.)])\s*)?\[(?: |x|X|-)?\]?\s*.*$/;
|
|
453
475
|
const CHAT_SCROLL_KEYS = new Set(["ArrowDown", "ArrowUp", "End", "Home", "PageDown", "PageUp", " "]);
|
|
454
476
|
const TAB_ACTIVITY_IDLE_RECONCILE_GRACE_MS = 1200;
|
|
477
|
+
const AGENT_DONE_NOTIFICATION_RETRY_GRACE_MS = 1200;
|
|
455
478
|
const FOREGROUND_RECONCILE_DELAY_MS = 120;
|
|
456
479
|
const TAB_GROUP_STATUS_PRIORITY = ["blocked", "done", "working", "idle"];
|
|
457
480
|
const EXTENSION_UI_BLOCKING_METHODS = new Set(["select", "confirm", "input", "editor"]);
|
|
@@ -482,6 +505,7 @@ const optionalFeatureAvailability = {
|
|
|
482
505
|
tuiSkillsCommand: false,
|
|
483
506
|
todoProgressWidget: false,
|
|
484
507
|
tuiToolsCommand: false,
|
|
508
|
+
remoteWebui: false,
|
|
485
509
|
themeBundle: false,
|
|
486
510
|
};
|
|
487
511
|
const OPTIONAL_FEATURES = [
|
|
@@ -534,6 +558,13 @@ const OPTIONAL_FEATURES = [
|
|
|
534
558
|
capabilityLabel: "RPC /tools from tools extension",
|
|
535
559
|
description: "Terminal-native active-tool manager alongside WebUI-native /tools toggles.",
|
|
536
560
|
},
|
|
561
|
+
{
|
|
562
|
+
id: "remoteWebui",
|
|
563
|
+
label: "Remote WebUI",
|
|
564
|
+
packageName: "@firstpick/pi-package-remote-webui",
|
|
565
|
+
capabilityLabel: "/remote",
|
|
566
|
+
description: "Trusted-LAN QR helper for opening the Web UI from mobile browsers.",
|
|
567
|
+
},
|
|
537
568
|
{
|
|
538
569
|
id: "gitFooterStatus",
|
|
539
570
|
label: "Git footer status",
|
|
@@ -566,6 +597,7 @@ const OPTIONAL_COMMAND_FEATURES = new Map([
|
|
|
566
597
|
["safety-guard", "safetyGuard"],
|
|
567
598
|
["skills", "tuiSkillsCommand"],
|
|
568
599
|
["tools", "tuiToolsCommand"],
|
|
600
|
+
["remote", "remoteWebui"],
|
|
569
601
|
["stats", "statsCommand"],
|
|
570
602
|
["git-footer-refresh", "gitFooterStatus"],
|
|
571
603
|
["todo-progress-status", "todoProgressWidget"],
|
|
@@ -824,6 +856,82 @@ function delay(ms) {
|
|
|
824
856
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
825
857
|
}
|
|
826
858
|
|
|
859
|
+
function activationControlFromEvent(event) {
|
|
860
|
+
const target = event?.target instanceof Element ? event.target : null;
|
|
861
|
+
const control = target?.closest?.(POINTER_ACTIVATION_SELECTOR);
|
|
862
|
+
if (!control || control === document.body || control === document.documentElement) return null;
|
|
863
|
+
if (control.disabled || control.getAttribute("aria-disabled") === "true") return null;
|
|
864
|
+
return control;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function shouldDeferUiRenderForPointerActivation() {
|
|
868
|
+
return Boolean(
|
|
869
|
+
activePointerActivation
|
|
870
|
+
&& performance.now() - activePointerActivation.startedAt <= POINTER_ACTIVATION_RENDER_DEFER_MAX_MS,
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function deferUiRenderDuringPointerActivation(key, callback) {
|
|
875
|
+
if (!shouldDeferUiRenderForPointerActivation()) return false;
|
|
876
|
+
deferredUiRenderCallbacks.set(key, callback);
|
|
877
|
+
return true;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function deferChatFollowScrollDuringPointerActivation({ force = false } = {}) {
|
|
881
|
+
if (force || !shouldDeferUiRenderForPointerActivation()) return false;
|
|
882
|
+
deferredChatFollowScroll = true;
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function flushDeferredUiRenders() {
|
|
887
|
+
const callbacks = [...deferredUiRenderCallbacks.values()];
|
|
888
|
+
deferredUiRenderCallbacks.clear();
|
|
889
|
+
const shouldScroll = deferredChatFollowScroll;
|
|
890
|
+
deferredChatFollowScroll = false;
|
|
891
|
+
|
|
892
|
+
for (const callback of callbacks) {
|
|
893
|
+
try {
|
|
894
|
+
callback();
|
|
895
|
+
} catch (error) {
|
|
896
|
+
console.error("deferred Web UI render failed", error);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (shouldScroll) scrollChatToBottom();
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function beginPointerActivation(event) {
|
|
903
|
+
if (event?.button !== undefined && event.button !== 0) return;
|
|
904
|
+
const control = activationControlFromEvent(event);
|
|
905
|
+
if (!control) return;
|
|
906
|
+
clearTimeout(pointerActivationTimeout);
|
|
907
|
+
const activation = { pointerId: event.pointerId, startedAt: performance.now(), control };
|
|
908
|
+
activePointerActivation = activation;
|
|
909
|
+
pointerActivationTimeout = setTimeout(() => {
|
|
910
|
+
if (activePointerActivation === activation) activePointerActivation = null;
|
|
911
|
+
pointerActivationTimeout = null;
|
|
912
|
+
flushDeferredUiRenders();
|
|
913
|
+
}, POINTER_ACTIVATION_RENDER_DEFER_MAX_MS);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function finishPointerActivation(event) {
|
|
917
|
+
if (!activePointerActivation) return;
|
|
918
|
+
if (event?.pointerId !== undefined && activePointerActivation.pointerId !== event.pointerId) return;
|
|
919
|
+
const activation = activePointerActivation;
|
|
920
|
+
clearTimeout(pointerActivationTimeout);
|
|
921
|
+
pointerActivationTimeout = null;
|
|
922
|
+
setTimeout(() => {
|
|
923
|
+
if (activePointerActivation === activation) activePointerActivation = null;
|
|
924
|
+
flushDeferredUiRenders();
|
|
925
|
+
}, 0);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function cancelPointerActivation() {
|
|
929
|
+
clearTimeout(pointerActivationTimeout);
|
|
930
|
+
pointerActivationTimeout = null;
|
|
931
|
+
activePointerActivation = null;
|
|
932
|
+
flushDeferredUiRenders();
|
|
933
|
+
}
|
|
934
|
+
|
|
827
935
|
function isMobileView() {
|
|
828
936
|
return mobileViewMedia?.matches || false;
|
|
829
937
|
}
|
|
@@ -832,6 +940,36 @@ function isSidePanelOverlayView() {
|
|
|
832
940
|
return sidePanelOverlayMedia?.matches || false;
|
|
833
941
|
}
|
|
834
942
|
|
|
943
|
+
function mobileDropdownViewportHeight() {
|
|
944
|
+
return window.visualViewport?.height || window.innerHeight || document.documentElement.clientHeight || 0;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function mobileDropdownConfigs() {
|
|
948
|
+
return [
|
|
949
|
+
{ menu: elements.publishButton?.parentElement, button: elements.publishButton, panel: elements.publishButton?.parentElement?.querySelector(".composer-publish-menu-panel") },
|
|
950
|
+
{ menu: elements.nativeCommandMenuButton?.parentElement, button: elements.nativeCommandMenuButton, panel: elements.nativeCommandMenuButton?.parentElement?.querySelector(".composer-publish-menu-panel") },
|
|
951
|
+
{ menu: elements.optionsMenuButton?.parentElement, button: elements.optionsMenuButton, panel: elements.optionsMenu },
|
|
952
|
+
{ menu: elements.appRunnerMenu, button: elements.appRunnerMenuButton, panel: elements.appRunnerMenuPanel },
|
|
953
|
+
];
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function updateMobileDropdownScrollBounds() {
|
|
957
|
+
const viewportHeight = mobileDropdownViewportHeight();
|
|
958
|
+
for (const { menu, button, panel } of mobileDropdownConfigs()) {
|
|
959
|
+
if (!panel) continue;
|
|
960
|
+
panel.style.removeProperty("--mobile-dropdown-max-height");
|
|
961
|
+
if (!isMobileView() || !menu?.classList.contains("open") || !viewportHeight) continue;
|
|
962
|
+
const anchorRect = (button || menu).getBoundingClientRect();
|
|
963
|
+
const availableAbove = Math.floor(anchorRect.top - 8);
|
|
964
|
+
const boundedHeight = Math.max(72, Math.min(viewportHeight - 16, availableAbove));
|
|
965
|
+
panel.style.setProperty("--mobile-dropdown-max-height", `${boundedHeight}px`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function scheduleMobileDropdownScrollBoundsUpdate() {
|
|
970
|
+
requestAnimationFrame(updateMobileDropdownScrollBounds);
|
|
971
|
+
}
|
|
972
|
+
|
|
835
973
|
function readStoredSidePanelCollapsed() {
|
|
836
974
|
try {
|
|
837
975
|
const stored = localStorage.getItem(SIDE_PANEL_STORAGE_KEY);
|
|
@@ -1590,6 +1728,7 @@ function setComposerActionsOpen(open) {
|
|
|
1590
1728
|
setOptionsMenuOpen(false);
|
|
1591
1729
|
setBusyPromptBehaviorMenuOpen(false);
|
|
1592
1730
|
}
|
|
1731
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
1593
1732
|
}
|
|
1594
1733
|
|
|
1595
1734
|
function isUserBashActive(tabId = activeTabId) {
|
|
@@ -1641,11 +1780,17 @@ function updateComposerModeButtons() {
|
|
|
1641
1780
|
button.hidden = !runActive;
|
|
1642
1781
|
button.disabled = !runActive;
|
|
1643
1782
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
elements.abortButton.
|
|
1647
|
-
elements.abortButton.
|
|
1648
|
-
|
|
1783
|
+
const abortHoldActive = isAbortLongPressActive();
|
|
1784
|
+
if (!abortAvailable && !abortHoldActive) resetAbortLongPressAffordance();
|
|
1785
|
+
elements.abortButton.hidden = !abortAvailable && !abortHoldActive;
|
|
1786
|
+
elements.abortButton.disabled = (!abortAvailable && !abortHoldActive) || abortRequestInFlight;
|
|
1787
|
+
if (abortHoldActive) {
|
|
1788
|
+
renderAbortLongPressAffordance();
|
|
1789
|
+
} else {
|
|
1790
|
+
elements.abortButton.textContent = abortRequestInFlight ? "Aborting…" : "Abort";
|
|
1791
|
+
elements.abortButton.title = abortAvailable ? abortButtonReadyTitle() : "Abort is available while Pi is running";
|
|
1792
|
+
elements.abortButton.setAttribute("aria-label", elements.abortButton.title);
|
|
1793
|
+
}
|
|
1649
1794
|
renderBusyPromptBehaviorTag();
|
|
1650
1795
|
document.body.classList.toggle("pi-run-active", runActive || abortAvailable);
|
|
1651
1796
|
}
|
|
@@ -1849,6 +1994,7 @@ function updateVisualViewportVars() {
|
|
|
1849
1994
|
syncMobileChatToBottomForInput();
|
|
1850
1995
|
}
|
|
1851
1996
|
updateFooterModelPickerPosition();
|
|
1997
|
+
updateMobileDropdownScrollBounds();
|
|
1852
1998
|
}
|
|
1853
1999
|
|
|
1854
2000
|
function installViewportHandlers() {
|
|
@@ -2267,6 +2413,10 @@ async function api(path, { method = "GET", body, tabId = activeTabId, scoped = t
|
|
|
2267
2413
|
setBackendOffline(false);
|
|
2268
2414
|
const data = await response.json().catch(() => ({}));
|
|
2269
2415
|
if (!response.ok) {
|
|
2416
|
+
if (response.status === 401 && data.remoteAuthRequired) {
|
|
2417
|
+
const returnPath = `${window.location.pathname}${window.location.search || ""}` || "/";
|
|
2418
|
+
window.location.assign(`/remote-auth?return=${encodeURIComponent(returnPath)}`);
|
|
2419
|
+
}
|
|
2270
2420
|
const error = new Error(data.error || data.message || JSON.stringify(data));
|
|
2271
2421
|
error.statusCode = response.status;
|
|
2272
2422
|
error.data = data;
|
|
@@ -3720,11 +3870,17 @@ function syncTabMetadata(nextTabs = []) {
|
|
|
3720
3870
|
if (!liveIds.has(tabId)) {
|
|
3721
3871
|
tabActivities.delete(tabId);
|
|
3722
3872
|
tabSeenCompletionSerials.delete(tabId);
|
|
3873
|
+
autoRetryingTabs.delete(tabId);
|
|
3874
|
+
suppressPendingAgentDoneNotificationsForTab(tabId, { markSeen: false });
|
|
3723
3875
|
actionFeedbackByTab.delete(tabId);
|
|
3724
3876
|
skillUsageByTab.delete(tabId);
|
|
3877
|
+
tabMessagesCache.delete(tabId);
|
|
3725
3878
|
clearGitWorkflowForTab(tabId);
|
|
3726
3879
|
}
|
|
3727
3880
|
}
|
|
3881
|
+
for (const tabId of tabMessagesCache.keys()) {
|
|
3882
|
+
if (!liveIds.has(tabId)) tabMessagesCache.delete(tabId);
|
|
3883
|
+
}
|
|
3728
3884
|
pruneSkillUsageForKnownTabs(liveIds);
|
|
3729
3885
|
}
|
|
3730
3886
|
|
|
@@ -3888,6 +4044,18 @@ function ingestEventTabActivity(event) {
|
|
|
3888
4044
|
if (changed) renderTabs();
|
|
3889
4045
|
}
|
|
3890
4046
|
|
|
4047
|
+
function trackAutoRetryStateFromEvent(event) {
|
|
4048
|
+
const tabId = event?.tabId || activeTabId;
|
|
4049
|
+
if (!tabId) return;
|
|
4050
|
+
if (event.type === "auto_retry_start") {
|
|
4051
|
+
autoRetryingTabs.add(tabId);
|
|
4052
|
+
suppressPendingAgentDoneNotificationsForTab(tabId);
|
|
4053
|
+
markTabWorkingLocally(tabId);
|
|
4054
|
+
} else if (event.type === "auto_retry_end") {
|
|
4055
|
+
autoRetryingTabs.delete(tabId);
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
|
|
3891
4059
|
function rememberActiveTab() {
|
|
3892
4060
|
try {
|
|
3893
4061
|
if (activeTabId) localStorage.setItem(TAB_STORAGE_KEY, activeTabId);
|
|
@@ -3979,6 +4147,7 @@ function resetActiveTabUi() {
|
|
|
3979
4147
|
latestStatsOverlayPayload = null;
|
|
3980
4148
|
latestWorkspace = null;
|
|
3981
4149
|
latestMessages = [];
|
|
4150
|
+
latestMessagesSessionKey = "";
|
|
3982
4151
|
clearRunIndicatorActivity({ render: false });
|
|
3983
4152
|
statusEntries.clear();
|
|
3984
4153
|
widgets.clear();
|
|
@@ -4010,8 +4179,10 @@ function resetActiveTabUi() {
|
|
|
4010
4179
|
renderAppRunnerControls();
|
|
4011
4180
|
renderWidgets();
|
|
4012
4181
|
renderGitWorkflow();
|
|
4013
|
-
|
|
4014
|
-
|
|
4182
|
+
if (!restoreCachedMessagesForActiveTab()) {
|
|
4183
|
+
renderFooter();
|
|
4184
|
+
renderFeedbackTray();
|
|
4185
|
+
}
|
|
4015
4186
|
}
|
|
4016
4187
|
|
|
4017
4188
|
function tabGroupStatusRank(state) {
|
|
@@ -4250,6 +4421,7 @@ function moveNewTabMenuFocus(delta) {
|
|
|
4250
4421
|
}
|
|
4251
4422
|
|
|
4252
4423
|
function renderTabs() {
|
|
4424
|
+
if (deferUiRenderDuringPointerActivation("tabs", renderTabs)) return;
|
|
4253
4425
|
const active = activeTab();
|
|
4254
4426
|
const activeIndicator = active ? tabIndicator(active) : null;
|
|
4255
4427
|
elements.terminalTabsToggleButton.textContent = active ? `${activeIndicator.glyph} ${active.title}${tabs.length > 1 ? ` · ${tabs.length}` : ""}` : "Tabs";
|
|
@@ -4274,7 +4446,7 @@ function renderTabs() {
|
|
|
4274
4446
|
updateDocumentTitle();
|
|
4275
4447
|
renderWorkspaceDashboard();
|
|
4276
4448
|
renderContextMeter();
|
|
4277
|
-
if (elements.commandPaletteDialog?.open) renderCommandPalette();
|
|
4449
|
+
if (elements.commandPaletteDialog?.open) renderCommandPalette({ preserveScroll: true });
|
|
4278
4450
|
syncTabPolling();
|
|
4279
4451
|
}
|
|
4280
4452
|
|
|
@@ -4305,6 +4477,7 @@ async function switchTab(tabId) {
|
|
|
4305
4477
|
footerBranchPickerOpen = false;
|
|
4306
4478
|
footerBranchPickerRequestSerial += 1;
|
|
4307
4479
|
saveActiveDraft();
|
|
4480
|
+
cacheMessagesForTab(activeTabId);
|
|
4308
4481
|
const tabContext = setActiveTabId(tabId, { remember: true });
|
|
4309
4482
|
resetActiveTabUi();
|
|
4310
4483
|
renderTabs();
|
|
@@ -4417,6 +4590,7 @@ async function closeTerminalTabs(tabIds, { label = "selected terminal tabs" } =
|
|
|
4417
4590
|
clearAttachments(id);
|
|
4418
4591
|
clearGitWorkflowForTab(id);
|
|
4419
4592
|
appRunnerDataByTab.delete(id);
|
|
4593
|
+
tabMessagesCache.delete(id);
|
|
4420
4594
|
}
|
|
4421
4595
|
clearOpenTerminalTabGroup(null, { force: true });
|
|
4422
4596
|
|
|
@@ -4683,6 +4857,36 @@ function agentDoneNotificationKey(tabId, activity = {}) {
|
|
|
4683
4857
|
return `${tabId}:${Number.isFinite(serial) && serial > 0 ? serial : "done"}`;
|
|
4684
4858
|
}
|
|
4685
4859
|
|
|
4860
|
+
function isAutoRetryingTab(tabId) {
|
|
4861
|
+
return !!tabId && autoRetryingTabs.has(tabId);
|
|
4862
|
+
}
|
|
4863
|
+
|
|
4864
|
+
function clearPendingAgentDoneNotification(key, { markSeen = false } = {}) {
|
|
4865
|
+
const pending = pendingAgentDoneNotificationTimers.get(key);
|
|
4866
|
+
if (!pending) return false;
|
|
4867
|
+
clearTimeout(pending.timer);
|
|
4868
|
+
pendingAgentDoneNotificationTimers.delete(key);
|
|
4869
|
+
if (markSeen) agentDoneNotificationKeys.add(key);
|
|
4870
|
+
return true;
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
function suppressPendingAgentDoneNotificationsForTab(tabId, { markSeen = true } = {}) {
|
|
4874
|
+
if (!tabId) return;
|
|
4875
|
+
for (const [key, pending] of pendingAgentDoneNotificationTimers) {
|
|
4876
|
+
if (pending.tabId === tabId) clearPendingAgentDoneNotification(key, { markSeen });
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4880
|
+
function queueAgentDoneBrowserNotification({ key, tabId, title, body }) {
|
|
4881
|
+
clearPendingAgentDoneNotification(key);
|
|
4882
|
+
const timer = setTimeout(() => {
|
|
4883
|
+
pendingAgentDoneNotificationTimers.delete(key);
|
|
4884
|
+
if (isAutoRetryingTab(tabId)) return;
|
|
4885
|
+
showAgentDoneBrowserNotification({ tabId, title, body });
|
|
4886
|
+
}, AGENT_DONE_NOTIFICATION_RETRY_GRACE_MS);
|
|
4887
|
+
pendingAgentDoneNotificationTimers.set(key, { tabId, timer });
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4686
4890
|
function notifyAgentDone(tabOrId, { activity = null, tabTitle = "" } = {}) {
|
|
4687
4891
|
if (!agentDoneNotificationsEnabled) return;
|
|
4688
4892
|
const tabId = typeof tabOrId === "string" ? tabOrId : tabOrId?.id || activeTabId;
|
|
@@ -4693,9 +4897,11 @@ function notifyAgentDone(tabOrId, { activity = null, tabTitle = "" } = {}) {
|
|
|
4693
4897
|
const key = agentDoneNotificationKey(tabId, normalizedActivity);
|
|
4694
4898
|
if (agentDoneNotificationKeys.has(key)) return;
|
|
4695
4899
|
agentDoneNotificationKeys.add(key);
|
|
4900
|
+
if (isAutoRetryingTab(tabId)) return;
|
|
4696
4901
|
|
|
4697
4902
|
const displayTitle = tabTitle || tab?.title || "terminal";
|
|
4698
|
-
|
|
4903
|
+
queueAgentDoneBrowserNotification({
|
|
4904
|
+
key,
|
|
4699
4905
|
tabId,
|
|
4700
4906
|
title: "Pi finished work",
|
|
4701
4907
|
body: `${displayTitle} finished its agent run.`,
|
|
@@ -6177,6 +6383,7 @@ async function requestManualCompaction({ triggerButton = null } = {}) {
|
|
|
6177
6383
|
}
|
|
6178
6384
|
|
|
6179
6385
|
function renderContextMeter() {
|
|
6386
|
+
if (deferUiRenderDuringPointerActivation("context-meter", renderContextMeter)) return;
|
|
6180
6387
|
const root = elements.contextMeterBar;
|
|
6181
6388
|
if (!root) return;
|
|
6182
6389
|
const tab = activeTab();
|
|
@@ -6233,6 +6440,7 @@ function dashboardAction(label, handler, className = "") {
|
|
|
6233
6440
|
}
|
|
6234
6441
|
|
|
6235
6442
|
function renderWorkspaceDashboard() {
|
|
6443
|
+
if (deferUiRenderDuringPointerActivation("workspace-dashboard", renderWorkspaceDashboard)) return;
|
|
6236
6444
|
const root = elements.workspaceDashboard;
|
|
6237
6445
|
if (!root) return;
|
|
6238
6446
|
const tab = activeTab();
|
|
@@ -7031,6 +7239,7 @@ async function changeActiveTabCwd() {
|
|
|
7031
7239
|
}
|
|
7032
7240
|
|
|
7033
7241
|
function renderFooter() {
|
|
7242
|
+
if (deferUiRenderDuringPointerActivation("footer", renderFooter)) return;
|
|
7034
7243
|
const gitFooterPayload = parseGitFooterWebuiPayload();
|
|
7035
7244
|
if (gitFooterPayload) {
|
|
7036
7245
|
renderGitFooterPayload(footerPayloadWithLiveModel(gitFooterPayload));
|
|
@@ -7263,6 +7472,7 @@ function initializeCodexUsage() {
|
|
|
7263
7472
|
}
|
|
7264
7473
|
|
|
7265
7474
|
function renderStatus() {
|
|
7475
|
+
if (deferUiRenderDuringPointerActivation("status", renderStatus)) return;
|
|
7266
7476
|
const state = currentState;
|
|
7267
7477
|
updateComposerModeButtons();
|
|
7268
7478
|
const running = state?.isStreaming ? "running" : "idle";
|
|
@@ -8704,7 +8914,22 @@ function handleStatsWebuiStatus(statusText) {
|
|
|
8704
8914
|
if (payload.open || elements.statsOverlayDialog?.open) renderStatsOverlay();
|
|
8705
8915
|
}
|
|
8706
8916
|
|
|
8917
|
+
function remoteWebuiWidgetLines(lines = []) {
|
|
8918
|
+
return (Array.isArray(lines) ? lines : [])
|
|
8919
|
+
.map(stripAnsi)
|
|
8920
|
+
.map((line) => String(line ?? ""))
|
|
8921
|
+
.filter((line, index, array) => line.trim() || (index > 0 && index < array.length - 1));
|
|
8922
|
+
}
|
|
8923
|
+
|
|
8924
|
+
function mirrorRemoteWebuiWidgetToTranscript(widgetKey, lines = [], request = {}) {
|
|
8925
|
+
if (widgetKey !== "pi-remote-webui" || request.replayed) return;
|
|
8926
|
+
const content = remoteWebuiWidgetLines(lines).join("\n").trimEnd();
|
|
8927
|
+
if (!content) return;
|
|
8928
|
+
addTransientMessage({ role: "extension", title: "/remote", content, level: "info", widgetKey });
|
|
8929
|
+
}
|
|
8930
|
+
|
|
8707
8931
|
function renderWidgets() {
|
|
8932
|
+
if (deferUiRenderDuringPointerActivation("widgets", renderWidgets)) return;
|
|
8708
8933
|
elements.widgetArea.replaceChildren();
|
|
8709
8934
|
const releaseOutput = renderReleaseNpmOutputWidget();
|
|
8710
8935
|
if (releaseOutput) elements.widgetArea.append(releaseOutput);
|
|
@@ -8720,8 +8945,9 @@ function renderWidgets() {
|
|
|
8720
8945
|
for (const [key, value] of widgets) {
|
|
8721
8946
|
const widgetFeatureId = optionalFeatureWidgetFeatureId(key);
|
|
8722
8947
|
if (widgetFeatureId && !isOptionalFeatureEnabled(widgetFeatureId)) continue;
|
|
8723
|
-
if (widgetFeatureId && key
|
|
8948
|
+
if (widgetFeatureId && optionalFeatureWidgetHasSpecializedRenderer(key)) continue;
|
|
8724
8949
|
const lines = Array.isArray(value.widgetLines) ? value.widgetLines : [];
|
|
8950
|
+
if (key === "pi-remote-webui") continue;
|
|
8725
8951
|
const specialized = key === "todo-progress" && isOptionalFeatureEnabled("todoProgressWidget") ? renderTodoProgressWidget(key, lines) : null;
|
|
8726
8952
|
if (specialized) {
|
|
8727
8953
|
elements.widgetArea.append(specialized);
|
|
@@ -10324,6 +10550,7 @@ function renderQueueGroup(label, items, tone) {
|
|
|
10324
10550
|
}
|
|
10325
10551
|
|
|
10326
10552
|
function renderQueue(event) {
|
|
10553
|
+
if (deferUiRenderDuringPointerActivation("queue", () => renderQueue(event))) return;
|
|
10327
10554
|
const snapshot = normalizeQueuedMessages(event);
|
|
10328
10555
|
const tabId = event?.tabId || activeTabId;
|
|
10329
10556
|
if (tabId) latestQueuedMessagesByTab.set(tabId, snapshot);
|
|
@@ -11275,6 +11502,7 @@ function renderActionFeedbackControls(bubble, message, messageIndex) {
|
|
|
11275
11502
|
}
|
|
11276
11503
|
|
|
11277
11504
|
function renderFeedbackTray() {
|
|
11505
|
+
if (deferUiRenderDuringPointerActivation("feedback-tray", renderFeedbackTray)) return;
|
|
11278
11506
|
const items = queuedActionFeedback();
|
|
11279
11507
|
const hasItems = items.length > 0;
|
|
11280
11508
|
elements.feedbackTray.hidden = !hasItems;
|
|
@@ -11747,6 +11975,7 @@ function findStickyUserPromptTarget(targets = userPromptTargets()) {
|
|
|
11747
11975
|
}
|
|
11748
11976
|
|
|
11749
11977
|
function updateStickyUserPromptButton() {
|
|
11978
|
+
if (deferUiRenderDuringPointerActivation("sticky-user-prompt", updateStickyUserPromptButton)) return;
|
|
11750
11979
|
const button = elements.stickyUserPromptButton;
|
|
11751
11980
|
if (!button) return;
|
|
11752
11981
|
const targets = userPromptTargets();
|
|
@@ -12896,6 +13125,7 @@ function pruneDisconnectedLiveToolCards() {
|
|
|
12896
13125
|
}
|
|
12897
13126
|
|
|
12898
13127
|
function renderAllMessages({ preserveScroll = false, forceRebuild = false } = {}) {
|
|
13128
|
+
if (deferUiRenderDuringPointerActivation("messages", () => renderAllMessages({ preserveScroll, forceRebuild }))) return;
|
|
12899
13129
|
const shouldFollow = !preserveScroll && (autoFollowChat || isChatNearBottom());
|
|
12900
13130
|
const previousScrollTop = elements.chat.scrollTop;
|
|
12901
13131
|
const transcriptItems = orderedTranscriptItems();
|
|
@@ -13067,6 +13297,7 @@ function scheduleChatFollowScroll() {
|
|
|
13067
13297
|
}
|
|
13068
13298
|
|
|
13069
13299
|
function scrollChatToBottom({ force = false } = {}) {
|
|
13300
|
+
if (deferChatFollowScrollDuringPointerActivation({ force })) return;
|
|
13070
13301
|
if (force) autoFollowChat = true;
|
|
13071
13302
|
if (!autoFollowChat) {
|
|
13072
13303
|
updateJumpToLatestButton();
|
|
@@ -13127,6 +13358,7 @@ function setPublishMenuOpen(open) {
|
|
|
13127
13358
|
elements.publishButton.setAttribute("aria-expanded", publishMenuOpen ? "true" : "false");
|
|
13128
13359
|
elements.publishButton.classList.toggle("menu-open", publishMenuOpen);
|
|
13129
13360
|
elements.publishButton.parentElement?.classList.toggle("open", publishMenuOpen);
|
|
13361
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
13130
13362
|
}
|
|
13131
13363
|
|
|
13132
13364
|
function setNativeCommandMenuOpen(open) {
|
|
@@ -13134,6 +13366,7 @@ function setNativeCommandMenuOpen(open) {
|
|
|
13134
13366
|
elements.nativeCommandMenuButton.setAttribute("aria-expanded", nativeCommandMenuOpen ? "true" : "false");
|
|
13135
13367
|
elements.nativeCommandMenuButton.classList.toggle("menu-open", nativeCommandMenuOpen);
|
|
13136
13368
|
elements.nativeCommandMenuButton.parentElement?.classList.toggle("open", nativeCommandMenuOpen);
|
|
13369
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
13137
13370
|
}
|
|
13138
13371
|
|
|
13139
13372
|
function setAppRunnerMenuOpen(open) {
|
|
@@ -13141,6 +13374,7 @@ function setAppRunnerMenuOpen(open) {
|
|
|
13141
13374
|
elements.appRunnerMenuButton?.setAttribute("aria-expanded", appRunnerMenuOpen ? "true" : "false");
|
|
13142
13375
|
elements.appRunnerMenuButton?.classList.toggle("menu-open", appRunnerMenuOpen);
|
|
13143
13376
|
elements.appRunnerMenuButton?.parentElement?.classList.toggle("open", appRunnerMenuOpen);
|
|
13377
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
13144
13378
|
}
|
|
13145
13379
|
|
|
13146
13380
|
function setOptionsMenuOpen(open) {
|
|
@@ -13148,6 +13382,7 @@ function setOptionsMenuOpen(open) {
|
|
|
13148
13382
|
elements.optionsMenuButton.setAttribute("aria-expanded", optionsMenuOpen ? "true" : "false");
|
|
13149
13383
|
elements.optionsMenuButton.classList.toggle("menu-open", optionsMenuOpen);
|
|
13150
13384
|
elements.optionsMenuButton.parentElement?.classList.toggle("open", optionsMenuOpen);
|
|
13385
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
13151
13386
|
}
|
|
13152
13387
|
|
|
13153
13388
|
function optionalFeatureIdForCommand(name) {
|
|
@@ -13312,6 +13547,7 @@ function updateOptionalFeatureAvailability() {
|
|
|
13312
13547
|
optionalFeatureAvailability.tuiSkillsCommand = hasLoadedRpcCommand("skills");
|
|
13313
13548
|
optionalFeatureAvailability.todoProgressWidget = hasAvailableCommand("todo-progress-status") || optionalFeatureAvailability.todoProgressWidget || widgets.has("todo-progress");
|
|
13314
13549
|
optionalFeatureAvailability.tuiToolsCommand = hasLoadedRpcCommand("tools");
|
|
13550
|
+
optionalFeatureAvailability.remoteWebui = hasAvailableCommand("remote") || optionalFeatureAvailability.remoteWebui || statusEntries.has("pi-remote-webui") || widgets.has("pi-remote-webui");
|
|
13315
13551
|
optionalFeatureAvailability.themeBundle = availableThemes.length > 0;
|
|
13316
13552
|
requestGitFooterWebuiPayload();
|
|
13317
13553
|
renderOptionalFeatureControls();
|
|
@@ -13336,9 +13572,14 @@ function optionalFeatureWidgetFeatureId(key) {
|
|
|
13336
13572
|
if (key.startsWith("release-npm:")) return "releaseNpm";
|
|
13337
13573
|
if (key.startsWith("release-aur:")) return "releaseAur";
|
|
13338
13574
|
if (key === "todo-progress") return "todoProgressWidget";
|
|
13575
|
+
if (key === "pi-remote-webui") return "remoteWebui";
|
|
13339
13576
|
return null;
|
|
13340
13577
|
}
|
|
13341
13578
|
|
|
13579
|
+
function optionalFeatureWidgetHasSpecializedRenderer(key) {
|
|
13580
|
+
return key.startsWith("release-npm:") || key.startsWith("release-aur:");
|
|
13581
|
+
}
|
|
13582
|
+
|
|
13342
13583
|
function renderOptionalFeaturePanel() {
|
|
13343
13584
|
if (!elements.optionalFeaturesBox) return;
|
|
13344
13585
|
elements.optionalFeaturesBox.replaceChildren();
|
|
@@ -13443,6 +13684,16 @@ function renderOptionalFeatureControls() {
|
|
|
13443
13684
|
);
|
|
13444
13685
|
}
|
|
13445
13686
|
|
|
13687
|
+
const hasRemoteWebuiCommand = isOptionalFeatureEnabled("remoteWebui") && hasAvailableCommand("remote");
|
|
13688
|
+
if (elements.optionsRemoteButton) {
|
|
13689
|
+
elements.optionsRemoteButton.hidden = !hasRemoteWebuiCommand;
|
|
13690
|
+
setOptionalControlState(
|
|
13691
|
+
elements.optionsRemoteButton,
|
|
13692
|
+
hasRemoteWebuiCommand,
|
|
13693
|
+
optionalFeatureUnavailableMessage("remoteWebui"),
|
|
13694
|
+
);
|
|
13695
|
+
}
|
|
13696
|
+
|
|
13446
13697
|
renderOptionalFeaturePanel();
|
|
13447
13698
|
}
|
|
13448
13699
|
|
|
@@ -14745,6 +14996,10 @@ async function refreshState(tabContext = activeTabContext()) {
|
|
|
14745
14996
|
if (!isCurrentTabContext(tabContext)) return;
|
|
14746
14997
|
const previousState = currentState;
|
|
14747
14998
|
currentState = response.data || null;
|
|
14999
|
+
if (latestMessages.length) {
|
|
15000
|
+
latestMessagesSessionKey = resolveMessagesSessionKey(tabContext.tabId);
|
|
15001
|
+
cacheMessagesForTab(tabContext.tabId, latestMessages, latestMessagesSessionKey);
|
|
15002
|
+
}
|
|
14748
15003
|
const shouldRefreshGitFooter = gitFooterRelevantStateChanged(previousState, currentState);
|
|
14749
15004
|
syncActiveTabActivityFromState(currentState);
|
|
14750
15005
|
syncRunIndicatorFromState(currentState);
|
|
@@ -14838,7 +15093,22 @@ function renderNetworkStatus() {
|
|
|
14838
15093
|
if (networkUrls.length === 0) list.append(make("div", "network-status-empty", "No LAN address detected."));
|
|
14839
15094
|
}
|
|
14840
15095
|
|
|
14841
|
-
|
|
15096
|
+
const auth = network?.auth || {};
|
|
15097
|
+
const authText = auth.enabled
|
|
15098
|
+
? auth.pin
|
|
15099
|
+
? `Remote PIN auth on · PIN ${auth.pin}`
|
|
15100
|
+
: "Remote PIN auth on"
|
|
15101
|
+
: "Remote PIN auth off";
|
|
15102
|
+
const authDetail = make("div", "network-status-detail", authText);
|
|
15103
|
+
|
|
15104
|
+
elements.networkStatus.replaceChildren(heading, detail, list, authDetail);
|
|
15105
|
+
elements.remoteAuthToggle.checked = !!auth.enabled;
|
|
15106
|
+
elements.remoteAuthToggle.disabled = rebinding;
|
|
15107
|
+
elements.remoteAuthStatus.textContent = auth.enabled
|
|
15108
|
+
? auth.pin
|
|
15109
|
+
? `PIN ${auth.pin}`
|
|
15110
|
+
: "On"
|
|
15111
|
+
: "Off";
|
|
14842
15112
|
elements.openNetworkButton.disabled = rebinding;
|
|
14843
15113
|
elements.openNetworkButton.textContent = opening ? "Opening…" : closing ? "Closing…" : open ? "Close for network" : "Open to network";
|
|
14844
15114
|
}
|
|
@@ -14854,6 +15124,28 @@ async function refreshNetworkStatus() {
|
|
|
14854
15124
|
renderNetworkStatus();
|
|
14855
15125
|
}
|
|
14856
15126
|
|
|
15127
|
+
async function toggleRemoteAuth() {
|
|
15128
|
+
const enable = !latestNetwork?.auth?.enabled;
|
|
15129
|
+
const message = enable
|
|
15130
|
+
? "Enable remote PIN authentication?\n\nA random 4-digit PIN will be required for non-local browser clients. The PIN is shown in Controls."
|
|
15131
|
+
: "Disable remote PIN authentication?\n\nNon-local browser clients will no longer need a PIN while the network listener is open.";
|
|
15132
|
+
if (!confirm(message)) {
|
|
15133
|
+
renderNetworkStatus();
|
|
15134
|
+
return;
|
|
15135
|
+
}
|
|
15136
|
+
|
|
15137
|
+
elements.remoteAuthToggle.disabled = true;
|
|
15138
|
+
try {
|
|
15139
|
+
const response = await api("/api/remote-auth/settings", { method: "POST", body: { enabled: enable }, scoped: false });
|
|
15140
|
+
latestNetwork = response.data?.network || { ...(latestNetwork || {}), auth: response.data?.auth };
|
|
15141
|
+
addEvent(enable ? "remote PIN auth enabled" : "remote PIN auth disabled", enable ? "warn" : "info");
|
|
15142
|
+
} catch (error) {
|
|
15143
|
+
addEvent(error.message || String(error), "error");
|
|
15144
|
+
} finally {
|
|
15145
|
+
renderNetworkStatus();
|
|
15146
|
+
}
|
|
15147
|
+
}
|
|
15148
|
+
|
|
14857
15149
|
async function refreshFooterData(tabContext = activeTabContext()) {
|
|
14858
15150
|
if (!tabContext.tabId) return;
|
|
14859
15151
|
await Promise.allSettled([refreshStats(tabContext), refreshWorkspace(tabContext)]);
|
|
@@ -14861,7 +15153,32 @@ async function refreshFooterData(tabContext = activeTabContext()) {
|
|
|
14861
15153
|
|
|
14862
15154
|
// Session key of the last applied transcript fetch; deltas are only
|
|
14863
15155
|
// attempted while the tab+session is unchanged.
|
|
14864
|
-
|
|
15156
|
+
function resolveMessagesSessionKey(tabId = activeTabId) {
|
|
15157
|
+
if (!tabId) return "";
|
|
15158
|
+
const stateSessionId = tabId === activeTabId ? currentState?.sessionId : null;
|
|
15159
|
+
if (stateSessionId) return `${tabId}|${stateSessionId}`;
|
|
15160
|
+
if (latestMessagesSessionKey.startsWith(`${tabId}|`)) return latestMessagesSessionKey;
|
|
15161
|
+
const cached = tabMessagesCache.get(tabId);
|
|
15162
|
+
if (cached?.sessionKey?.startsWith(`${tabId}|`)) return cached.sessionKey;
|
|
15163
|
+
return `${tabId}|`;
|
|
15164
|
+
}
|
|
15165
|
+
|
|
15166
|
+
function cacheMessagesForTab(tabId = activeTabId, messages = latestMessages, sessionKey = latestMessagesSessionKey) {
|
|
15167
|
+
if (!tabId || !Array.isArray(messages)) return;
|
|
15168
|
+
const stateSessionKey = tabId === activeTabId && currentState?.sessionId ? `${tabId}|${currentState.sessionId}` : "";
|
|
15169
|
+
const resolvedSessionKey = stateSessionKey || (sessionKey?.startsWith(`${tabId}|`) ? sessionKey : resolveMessagesSessionKey(tabId));
|
|
15170
|
+
tabMessagesCache.set(tabId, { messages, sessionKey: resolvedSessionKey });
|
|
15171
|
+
}
|
|
15172
|
+
|
|
15173
|
+
function restoreCachedMessagesForActiveTab() {
|
|
15174
|
+
if (!activeTabId) return false;
|
|
15175
|
+
const cached = tabMessagesCache.get(activeTabId);
|
|
15176
|
+
if (!cached || !Array.isArray(cached.messages)) return false;
|
|
15177
|
+
latestMessages = cached.messages;
|
|
15178
|
+
latestMessagesSessionKey = cached.sessionKey || resolveMessagesSessionKey(activeTabId);
|
|
15179
|
+
renderMessages(latestMessages);
|
|
15180
|
+
return true;
|
|
15181
|
+
}
|
|
14865
15182
|
|
|
14866
15183
|
function messagesLookEqual(a, b) {
|
|
14867
15184
|
return !!a && !!b && a.role === b.role && String(a.timestamp || "") === String(b.timestamp || "")
|
|
@@ -14890,7 +15207,7 @@ function mergeMessagesDelta(previous, data) {
|
|
|
14890
15207
|
async function refreshMessages(tabContext = activeTabContext()) {
|
|
14891
15208
|
if (!tabContext.tabId) return;
|
|
14892
15209
|
const previousMessages = latestMessages;
|
|
14893
|
-
const sessionKey =
|
|
15210
|
+
const sessionKey = resolveMessagesSessionKey(tabContext.tabId);
|
|
14894
15211
|
let nextMessages = null;
|
|
14895
15212
|
if (previousMessages.length > 1 && sessionKey === latestMessagesSessionKey) {
|
|
14896
15213
|
// Delta fetch with a one-message overlap: the last known message is
|
|
@@ -14906,6 +15223,7 @@ async function refreshMessages(tabContext = activeTabContext()) {
|
|
|
14906
15223
|
}
|
|
14907
15224
|
latestMessages = nextMessages;
|
|
14908
15225
|
latestMessagesSessionKey = sessionKey;
|
|
15226
|
+
cacheMessagesForTab(tabContext.tabId, latestMessages, latestMessagesSessionKey);
|
|
14909
15227
|
const preserveLiveStream = liveStreamRenderActive();
|
|
14910
15228
|
if (!preserveLiveStream) resetStreamBubble();
|
|
14911
15229
|
renderMessages(latestMessages);
|
|
@@ -14946,7 +15264,7 @@ async function refreshModels(tabContext = activeTabContext()) {
|
|
|
14946
15264
|
syncModelSelectToState();
|
|
14947
15265
|
renderFooter();
|
|
14948
15266
|
renderFeedbackTray();
|
|
14949
|
-
if (elements.commandPaletteDialog?.open) renderCommandPalette();
|
|
15267
|
+
if (elements.commandPaletteDialog?.open) renderCommandPalette({ preserveScroll: true });
|
|
14950
15268
|
}
|
|
14951
15269
|
|
|
14952
15270
|
function syncModelSelectToState() {
|
|
@@ -15423,7 +15741,7 @@ async function refreshCommands(tabContext = activeTabContext()) {
|
|
|
15423
15741
|
availableCommands = normalizeCommands(response.data?.commands || []);
|
|
15424
15742
|
updateOptionalFeatureAvailability();
|
|
15425
15743
|
renderCommands();
|
|
15426
|
-
if (elements.commandPaletteDialog?.open) renderCommandPalette();
|
|
15744
|
+
if (elements.commandPaletteDialog?.open) renderCommandPalette({ preserveScroll: true });
|
|
15427
15745
|
}
|
|
15428
15746
|
|
|
15429
15747
|
function paletteText(value) {
|
|
@@ -15520,12 +15838,14 @@ function setCommandPaletteIndex(index) {
|
|
|
15520
15838
|
renderCommandPaletteList();
|
|
15521
15839
|
}
|
|
15522
15840
|
|
|
15523
|
-
function renderCommandPaletteList() {
|
|
15841
|
+
function renderCommandPaletteList({ preserveScroll = false } = {}) {
|
|
15524
15842
|
const list = elements.commandPaletteList;
|
|
15525
15843
|
if (!list) return;
|
|
15844
|
+
const scrollTop = preserveScroll ? list.scrollTop : 0;
|
|
15526
15845
|
list.replaceChildren();
|
|
15527
15846
|
if (!commandPaletteItems.length) {
|
|
15528
15847
|
list.append(make("div", "command-palette-empty muted", "No matching actions."));
|
|
15848
|
+
if (preserveScroll) list.scrollTop = scrollTop;
|
|
15529
15849
|
return;
|
|
15530
15850
|
}
|
|
15531
15851
|
commandPaletteItems.forEach((item, index) => {
|
|
@@ -15541,14 +15861,18 @@ function renderCommandPaletteList() {
|
|
|
15541
15861
|
);
|
|
15542
15862
|
list.append(button);
|
|
15543
15863
|
});
|
|
15864
|
+
if (preserveScroll) {
|
|
15865
|
+
list.scrollTop = scrollTop;
|
|
15866
|
+
return;
|
|
15867
|
+
}
|
|
15544
15868
|
const active = list.children[commandPaletteIndex];
|
|
15545
15869
|
active?.scrollIntoView({ block: "nearest" });
|
|
15546
15870
|
}
|
|
15547
15871
|
|
|
15548
|
-
function renderCommandPalette() {
|
|
15872
|
+
function renderCommandPalette({ preserveScroll = false } = {}) {
|
|
15549
15873
|
commandPaletteItems = filteredCommandPaletteItems();
|
|
15550
15874
|
if (commandPaletteIndex >= commandPaletteItems.length) commandPaletteIndex = 0;
|
|
15551
|
-
renderCommandPaletteList();
|
|
15875
|
+
renderCommandPaletteList({ preserveScroll });
|
|
15552
15876
|
}
|
|
15553
15877
|
|
|
15554
15878
|
function openCommandPalette(initialQuery = "") {
|
|
@@ -15636,7 +15960,7 @@ async function openToNetwork() {
|
|
|
15636
15960
|
await closeNetworkAccess();
|
|
15637
15961
|
return;
|
|
15638
15962
|
}
|
|
15639
|
-
if (!confirm(
|
|
15963
|
+
if (!confirm(`Open Pi Web UI to your local network?\n\nRemote PIN auth is ${latestNetwork?.auth?.enabled ? "ON" : "OFF"}. The Web UI can control Pi/tools, so only do this on a trusted LAN.`)) return;
|
|
15640
15964
|
|
|
15641
15965
|
elements.openNetworkButton.disabled = true;
|
|
15642
15966
|
elements.openNetworkButton.textContent = "Opening…";
|
|
@@ -16124,7 +16448,7 @@ function hasQueuedDialogRequest(id) {
|
|
|
16124
16448
|
|
|
16125
16449
|
function removeQueuedDialogRequests(ids = []) {
|
|
16126
16450
|
const idSet = new Set(ids.map((id) => String(id)).filter(Boolean));
|
|
16127
|
-
if (idSet.size === 0) return;
|
|
16451
|
+
if (idSet.size === 0) return false;
|
|
16128
16452
|
for (let i = dialogQueue.length - 1; i >= 0; i -= 1) {
|
|
16129
16453
|
if (idSet.has(String(dialogQueue[i]?.id || ""))) dialogQueue.splice(i, 1);
|
|
16130
16454
|
}
|
|
@@ -16132,7 +16456,9 @@ function removeQueuedDialogRequests(ids = []) {
|
|
|
16132
16456
|
if (elements.dialog.open) elements.dialog.close();
|
|
16133
16457
|
activeDialog = null;
|
|
16134
16458
|
showNextDialog();
|
|
16459
|
+
return true;
|
|
16135
16460
|
}
|
|
16461
|
+
return false;
|
|
16136
16462
|
}
|
|
16137
16463
|
|
|
16138
16464
|
function handleExtensionUiRequest(request) {
|
|
@@ -16158,12 +16484,20 @@ function handleExtensionUiRequest(request) {
|
|
|
16158
16484
|
renderStatus();
|
|
16159
16485
|
return;
|
|
16160
16486
|
}
|
|
16161
|
-
case "setWidget":
|
|
16162
|
-
|
|
16163
|
-
|
|
16487
|
+
case "setWidget": {
|
|
16488
|
+
const widgetKey = request.widgetKey || request.id;
|
|
16489
|
+
if (widgetKey === "pi-remote-webui") {
|
|
16490
|
+
widgets.delete(widgetKey);
|
|
16491
|
+
if (Array.isArray(request.widgetLines)) mirrorRemoteWebuiWidgetToTranscript(widgetKey, request.widgetLines, request);
|
|
16492
|
+
} else if (Array.isArray(request.widgetLines)) {
|
|
16493
|
+
widgets.set(widgetKey, request);
|
|
16494
|
+
} else {
|
|
16495
|
+
widgets.delete(widgetKey);
|
|
16496
|
+
}
|
|
16164
16497
|
updateOptionalFeatureAvailability();
|
|
16165
16498
|
renderWidgets();
|
|
16166
16499
|
return;
|
|
16500
|
+
}
|
|
16167
16501
|
case "setTitle":
|
|
16168
16502
|
if (request.title) document.title = request.title;
|
|
16169
16503
|
return;
|
|
@@ -16196,6 +16530,7 @@ function handleExtensionUiRequest(request) {
|
|
|
16196
16530
|
async function sendDialogResponse(payload) {
|
|
16197
16531
|
const { tabId = activeTabId, ...body } = payload;
|
|
16198
16532
|
const tabContext = activeTabContext(tabId);
|
|
16533
|
+
const responseId = String(body.id || "");
|
|
16199
16534
|
try {
|
|
16200
16535
|
const response = await api("/api/extension-ui-response", { method: "POST", body, tabId });
|
|
16201
16536
|
if (!applyResponseTab(response) && decrementTabPendingBlockerCount(tabId)) renderTabs();
|
|
@@ -16203,6 +16538,7 @@ async function sendDialogResponse(payload) {
|
|
|
16203
16538
|
if (isCurrentTabContext(tabContext)) addEvent(error.message, "error");
|
|
16204
16539
|
} finally {
|
|
16205
16540
|
if (!isCurrentTabContext(tabContext)) return;
|
|
16541
|
+
if (responseId && activeDialog && String(activeDialog.id || "") !== responseId) return;
|
|
16206
16542
|
if (elements.dialog.open) elements.dialog.close();
|
|
16207
16543
|
activeDialog = null;
|
|
16208
16544
|
if (runIndicatorIsActive()) setRunIndicatorActivity("Continuing after your response…");
|
|
@@ -16293,6 +16629,7 @@ function handleInactiveTabEvent(event) {
|
|
|
16293
16629
|
|
|
16294
16630
|
function handleEvent(event) {
|
|
16295
16631
|
ingestEventTabActivity(event);
|
|
16632
|
+
trackAutoRetryStateFromEvent(event);
|
|
16296
16633
|
trackSkillsFromEvent(event);
|
|
16297
16634
|
if (!eventTargetsActiveTab(event)) {
|
|
16298
16635
|
handleInactiveTabEvent(event);
|
|
@@ -16347,6 +16684,14 @@ function handleEvent(event) {
|
|
|
16347
16684
|
removeQueuedDialogRequests(event.ids || []);
|
|
16348
16685
|
addEvent(`cancelled ${event.ids?.length || 0} pending extension UI request(s)`, "warn");
|
|
16349
16686
|
break;
|
|
16687
|
+
case "webui_extension_ui_resolved": {
|
|
16688
|
+
const closedActiveDialog = removeQueuedDialogRequests([event.id]);
|
|
16689
|
+
if (closedActiveDialog) {
|
|
16690
|
+
addEvent("extension UI request resolved");
|
|
16691
|
+
if (runIndicatorIsActive() && !activeDialog) setRunIndicatorActivity("Continuing after extension UI response…");
|
|
16692
|
+
}
|
|
16693
|
+
break;
|
|
16694
|
+
}
|
|
16350
16695
|
case "webui_app_runner_update":
|
|
16351
16696
|
setAppRunnerData(event.tabId || activeTabId, { cwd: event.cwd, activeRun: event.activeRun });
|
|
16352
16697
|
renderAppRunnerControls();
|
|
@@ -16374,6 +16719,11 @@ function handleEvent(event) {
|
|
|
16374
16719
|
renderNetworkStatus();
|
|
16375
16720
|
break;
|
|
16376
16721
|
}
|
|
16722
|
+
case "webui_remote_auth_changed":
|
|
16723
|
+
latestNetwork = { ...(latestNetwork || {}), auth: event.auth || {} };
|
|
16724
|
+
addEvent(`remote PIN auth ${event.auth?.enabled ? "enabled" : "disabled"}`, event.auth?.enabled ? "warn" : "info");
|
|
16725
|
+
renderNetworkStatus();
|
|
16726
|
+
break;
|
|
16377
16727
|
case "pi_process_exit":
|
|
16378
16728
|
addEvent(`pi rpc exited (${event.code ?? event.signal ?? "unknown"})`, "error");
|
|
16379
16729
|
clearRunIndicatorActivity();
|
|
@@ -16846,6 +17196,7 @@ elements.nativeToolsButton.addEventListener("click", () => runNativeCommandMenu(
|
|
|
16846
17196
|
elements.optionsCommandPaletteButton.addEventListener("click", () => openCommandPalette());
|
|
16847
17197
|
elements.optionsResumeButton.addEventListener("click", () => runNativeCommandMenu("/resume"));
|
|
16848
17198
|
elements.optionsReloadButton.addEventListener("click", () => runNativeCommandMenu("/reload"));
|
|
17199
|
+
elements.optionsRemoteButton.addEventListener("click", () => runNativeCommandMenu("/remote"));
|
|
16849
17200
|
elements.optionsNameButton.addEventListener("click", () => runNativeCommandMenu("/name"));
|
|
16850
17201
|
elements.optionsCloneButton.addEventListener("click", () => runNativeCommandMenu("/clone"));
|
|
16851
17202
|
elements.optionsSettingsButton.addEventListener("click", () => runNativeCommandMenu("/settings"));
|
|
@@ -16906,6 +17257,7 @@ elements.commandPaletteDialog?.addEventListener("cancel", (event) => {
|
|
|
16906
17257
|
event.preventDefault();
|
|
16907
17258
|
closeCommandPalette();
|
|
16908
17259
|
});
|
|
17260
|
+
elements.commandPaletteCloseButton?.addEventListener("click", closeCommandPalette);
|
|
16909
17261
|
elements.commandPaletteInput?.addEventListener("input", () => {
|
|
16910
17262
|
commandPaletteIndex = 0;
|
|
16911
17263
|
renderCommandPalette();
|
|
@@ -16938,11 +17290,104 @@ elements.editRetryCancelButton?.addEventListener("click", closeEditRetryDialog);
|
|
|
16938
17290
|
elements.editRetryForkButton?.addEventListener("click", () => submitEditRetry({ send: false }));
|
|
16939
17291
|
elements.editRetrySendButton?.addEventListener("click", () => submitEditRetry({ send: true }));
|
|
16940
17292
|
|
|
16941
|
-
function
|
|
17293
|
+
function abortButtonHoldSeconds() {
|
|
17294
|
+
return String(Math.round(ABORT_LONG_PRESS_MS / 1000));
|
|
17295
|
+
}
|
|
17296
|
+
|
|
17297
|
+
function abortButtonReadyTitle() {
|
|
17298
|
+
return `Hold Esc or the Abort button for ${abortButtonHoldSeconds()} seconds to abort the active Pi run`;
|
|
17299
|
+
}
|
|
17300
|
+
|
|
17301
|
+
function clearAbortLongPressResetTimer() {
|
|
17302
|
+
clearTimeout(abortLongPressResetTimer);
|
|
17303
|
+
abortLongPressResetTimer = null;
|
|
17304
|
+
}
|
|
17305
|
+
|
|
17306
|
+
function clearAbortLongPressCompletionTimers() {
|
|
16942
17307
|
clearTimeout(abortLongPressTimer);
|
|
17308
|
+
clearInterval(abortLongPressTickTimer);
|
|
16943
17309
|
abortLongPressTimer = null;
|
|
17310
|
+
abortLongPressTickTimer = null;
|
|
17311
|
+
}
|
|
17312
|
+
|
|
17313
|
+
function isAbortLongPressActive() {
|
|
17314
|
+
return abortLongPressStartedAt > 0;
|
|
17315
|
+
}
|
|
17316
|
+
|
|
17317
|
+
function abortLongPressRemainingMs() {
|
|
17318
|
+
if (!abortLongPressStartedAt || !abortLongPressDeadlineAt) return ABORT_LONG_PRESS_MS;
|
|
17319
|
+
return Math.max(0, abortLongPressDeadlineAt - performance.now());
|
|
17320
|
+
}
|
|
17321
|
+
|
|
17322
|
+
function formatAbortLongPressRemaining(ms) {
|
|
17323
|
+
return (Math.ceil(Math.max(0, ms) / 100) / 10).toFixed(1);
|
|
17324
|
+
}
|
|
17325
|
+
|
|
17326
|
+
function abortLongPressLabel() {
|
|
17327
|
+
const remaining = formatAbortLongPressRemaining(abortLongPressRemainingMs());
|
|
17328
|
+
return abortLongPressSource === "escape" ? `Hold Esc ${remaining}s` : `Hold ${remaining}s`;
|
|
17329
|
+
}
|
|
17330
|
+
|
|
17331
|
+
function renderAbortLongPressAffordance() {
|
|
17332
|
+
const label = abortLongPressLabel();
|
|
17333
|
+
elements.abortButton.textContent = label;
|
|
17334
|
+
elements.abortButton.title = `${label} more to abort the active Pi run`;
|
|
17335
|
+
elements.abortButton.setAttribute("aria-label", elements.abortButton.title);
|
|
17336
|
+
}
|
|
17337
|
+
|
|
17338
|
+
function completeAbortLongPress() {
|
|
17339
|
+
if (!isAbortLongPressActive()) return;
|
|
17340
|
+
if (abortLongPressReleasePending) return;
|
|
17341
|
+
const source = abortLongPressSource;
|
|
17342
|
+
clearAbortLongPressResetTimer();
|
|
17343
|
+
clearAbortLongPressCompletionTimers();
|
|
17344
|
+
abortLongPressHandled = true;
|
|
17345
|
+
if (isAbortAvailable()) abortActiveRun({ source });
|
|
17346
|
+
else {
|
|
17347
|
+
resetAbortLongPressAffordance();
|
|
17348
|
+
updateComposerModeButtons();
|
|
17349
|
+
}
|
|
17350
|
+
}
|
|
17351
|
+
|
|
17352
|
+
function tickAbortLongPressAffordance() {
|
|
17353
|
+
if (!isAbortLongPressActive()) return;
|
|
17354
|
+
renderAbortLongPressAffordance();
|
|
17355
|
+
if (abortLongPressRemainingMs() <= 0) completeAbortLongPress();
|
|
17356
|
+
}
|
|
17357
|
+
|
|
17358
|
+
function resumeAbortLongPressAffordance() {
|
|
17359
|
+
if (!isAbortLongPressActive()) return;
|
|
17360
|
+
clearAbortLongPressResetTimer();
|
|
17361
|
+
abortLongPressReleasePending = false;
|
|
17362
|
+
tickAbortLongPressAffordance();
|
|
17363
|
+
}
|
|
17364
|
+
|
|
17365
|
+
function scheduleAbortLongPressReleaseReset() {
|
|
17366
|
+
if (!isAbortLongPressActive()) return;
|
|
17367
|
+
abortLongPressReleasePending = true;
|
|
17368
|
+
clearAbortLongPressResetTimer();
|
|
17369
|
+
abortLongPressResetTimer = setTimeout(() => {
|
|
17370
|
+
abortLongPressResetTimer = null;
|
|
17371
|
+
if (!abortLongPressReleasePending) return;
|
|
17372
|
+
resetAbortLongPressAffordance();
|
|
17373
|
+
updateComposerModeButtons();
|
|
17374
|
+
}, ABORT_LONG_PRESS_RELEASE_GRACE_MS);
|
|
17375
|
+
}
|
|
17376
|
+
|
|
17377
|
+
function resetAbortLongPressAffordance() {
|
|
17378
|
+
clearAbortLongPressResetTimer();
|
|
17379
|
+
clearAbortLongPressCompletionTimers();
|
|
17380
|
+
abortLongPressStartedAt = 0;
|
|
17381
|
+
abortLongPressDeadlineAt = 0;
|
|
17382
|
+
abortLongPressSource = "long-press";
|
|
17383
|
+
abortLongPressReleasePending = false;
|
|
16944
17384
|
elements.abortButton.classList.remove("long-pressing");
|
|
16945
|
-
|
|
17385
|
+
elements.abortButton.style.removeProperty("--abort-long-press-duration");
|
|
17386
|
+
if (!abortRequestInFlight) {
|
|
17387
|
+
elements.abortButton.textContent = "Abort";
|
|
17388
|
+
elements.abortButton.title = isAbortAvailable() ? abortButtonReadyTitle() : "Abort is available while Pi is running";
|
|
17389
|
+
elements.abortButton.setAttribute("aria-label", elements.abortButton.title);
|
|
17390
|
+
}
|
|
16946
17391
|
}
|
|
16947
17392
|
|
|
16948
17393
|
async function abortActiveRun({ source = "button" } = {}) {
|
|
@@ -16978,31 +17423,41 @@ async function abortActiveRun({ source = "button" } = {}) {
|
|
|
16978
17423
|
}
|
|
16979
17424
|
}
|
|
16980
17425
|
|
|
16981
|
-
function startAbortLongPress(event) {
|
|
16982
|
-
if (!isAbortAvailable() || abortRequestInFlight) return;
|
|
16983
|
-
if (event
|
|
17426
|
+
function startAbortLongPress(event, { source = "long-press" } = {}) {
|
|
17427
|
+
if (!isAbortAvailable() || abortRequestInFlight) return false;
|
|
17428
|
+
if (source !== "escape" && event?.button !== undefined && event.button !== 0) return false;
|
|
17429
|
+
if (isAbortLongPressActive()) {
|
|
17430
|
+
resumeAbortLongPressAffordance();
|
|
17431
|
+
return true;
|
|
17432
|
+
}
|
|
16984
17433
|
resetAbortLongPressAffordance();
|
|
16985
17434
|
abortLongPressHandled = false;
|
|
17435
|
+
abortLongPressReleasePending = false;
|
|
17436
|
+
abortLongPressSource = source;
|
|
17437
|
+
abortLongPressStartedAt = performance.now();
|
|
17438
|
+
abortLongPressDeadlineAt = abortLongPressStartedAt + ABORT_LONG_PRESS_MS;
|
|
17439
|
+
elements.abortButton.style.setProperty("--abort-long-press-duration", `${ABORT_LONG_PRESS_MS}ms`);
|
|
16986
17440
|
elements.abortButton.classList.add("long-pressing");
|
|
16987
|
-
|
|
16988
|
-
|
|
16989
|
-
|
|
16990
|
-
|
|
16991
|
-
abortActiveRun({ source: "long-press" });
|
|
16992
|
-
}, ABORT_LONG_PRESS_MS);
|
|
17441
|
+
renderAbortLongPressAffordance();
|
|
17442
|
+
abortLongPressTickTimer = setInterval(tickAbortLongPressAffordance, ABORT_LONG_PRESS_TICK_MS);
|
|
17443
|
+
abortLongPressTimer = setTimeout(tickAbortLongPressAffordance, ABORT_LONG_PRESS_MS + 10);
|
|
17444
|
+
return true;
|
|
16993
17445
|
}
|
|
16994
17446
|
|
|
16995
17447
|
elements.abortButton.addEventListener("pointerdown", startAbortLongPress);
|
|
16996
17448
|
for (const eventName of ["pointerup", "pointerleave", "pointercancel", "blur"]) {
|
|
16997
17449
|
elements.abortButton.addEventListener(eventName, resetAbortLongPressAffordance);
|
|
16998
17450
|
}
|
|
17451
|
+
elements.abortButton.addEventListener("keydown", (event) => {
|
|
17452
|
+
if (event.key !== " " && event.key !== "Enter") return;
|
|
17453
|
+
if (startAbortLongPress(event)) event.preventDefault();
|
|
17454
|
+
});
|
|
17455
|
+
elements.abortButton.addEventListener("keyup", (event) => {
|
|
17456
|
+
if (event.key === " " || event.key === "Enter") resetAbortLongPressAffordance();
|
|
17457
|
+
});
|
|
16999
17458
|
elements.abortButton.addEventListener("click", (event) => {
|
|
17000
|
-
|
|
17001
|
-
|
|
17002
|
-
abortLongPressHandled = false;
|
|
17003
|
-
return;
|
|
17004
|
-
}
|
|
17005
|
-
abortActiveRun({ source: "button" });
|
|
17459
|
+
event.preventDefault();
|
|
17460
|
+
if (abortLongPressHandled) abortLongPressHandled = false;
|
|
17006
17461
|
});
|
|
17007
17462
|
elements.newSessionButton.addEventListener("click", async () => {
|
|
17008
17463
|
setComposerActionsOpen(false);
|
|
@@ -17071,6 +17526,7 @@ if (elements.backgroundChooseButton && elements.backgroundInput) {
|
|
|
17071
17526
|
if (elements.backgroundClearButton) {
|
|
17072
17527
|
elements.backgroundClearButton.addEventListener("click", () => clearCustomBackground().catch((error) => addEvent(error.message || String(error), "error")));
|
|
17073
17528
|
}
|
|
17529
|
+
elements.remoteAuthToggle.addEventListener("change", () => toggleRemoteAuth().catch((error) => addEvent(error.message || String(error), "error")));
|
|
17074
17530
|
elements.openNetworkButton.addEventListener("click", openToNetwork);
|
|
17075
17531
|
elements.serverActionSelect.addEventListener("change", updateServerActionButton);
|
|
17076
17532
|
elements.runServerActionButton.addEventListener("click", () => runSelectedServerAction().catch((error) => addEvent(error.message || String(error), "error")));
|
|
@@ -17117,6 +17573,10 @@ elements.chat.addEventListener("scroll", () => {
|
|
|
17117
17573
|
markTabOutputSeen();
|
|
17118
17574
|
updateStickyUserPromptButton();
|
|
17119
17575
|
}, { passive: true });
|
|
17576
|
+
document.addEventListener("pointerdown", beginPointerActivation, { capture: true, passive: true });
|
|
17577
|
+
document.addEventListener("pointerup", finishPointerActivation, { capture: true, passive: true });
|
|
17578
|
+
document.addEventListener("pointercancel", cancelPointerActivation, { capture: true, passive: true });
|
|
17579
|
+
window.addEventListener("blur", cancelPointerActivation, { passive: true });
|
|
17120
17580
|
document.addEventListener("pointerdown", (event) => {
|
|
17121
17581
|
if (openTerminalTabGroupKey && !event.target?.closest?.(".terminal-tab-group")) {
|
|
17122
17582
|
clearOpenTerminalTabGroup(openTerminalTabGroupKey);
|
|
@@ -17337,12 +17797,14 @@ window.addEventListener("keydown", (event) => {
|
|
|
17337
17797
|
window.addEventListener("keydown", handleNativeAppShortcut, { capture: true });
|
|
17338
17798
|
document.addEventListener("visibilitychange", () => {
|
|
17339
17799
|
if (document.visibilityState === "visible") scheduleForegroundReconcile("visibility resume", 0);
|
|
17800
|
+
else resetAbortLongPressAffordance();
|
|
17340
17801
|
});
|
|
17341
17802
|
window.addEventListener("pageshow", () => scheduleForegroundReconcile("page show", 0));
|
|
17342
17803
|
window.addEventListener("focus", () => scheduleForegroundReconcile("window focus"));
|
|
17343
17804
|
window.addEventListener("online", () => scheduleForegroundReconcile("network online", 0));
|
|
17344
17805
|
window.addEventListener("keydown", (event) => {
|
|
17345
17806
|
if (event.key !== "Escape") return;
|
|
17807
|
+
if (event.defaultPrevented) return;
|
|
17346
17808
|
if (elements.dialog?.open || elements.pathPickerDialog?.open || elements.gitChangesDialog?.open || elements.commandPaletteDialog?.open || elements.editRetryDialog?.open) return;
|
|
17347
17809
|
if (publishMenuOpen) {
|
|
17348
17810
|
setPublishMenuOpen(false);
|
|
@@ -17388,6 +17850,20 @@ window.addEventListener("keydown", (event) => {
|
|
|
17388
17850
|
hideCommandSuggestions();
|
|
17389
17851
|
return;
|
|
17390
17852
|
}
|
|
17853
|
+
if (isSidePanelOverlayView() && !document.body.classList.contains("side-panel-collapsed")) {
|
|
17854
|
+
setSidePanelCollapsed(true);
|
|
17855
|
+
return;
|
|
17856
|
+
}
|
|
17857
|
+
if (isAbortAvailable()) {
|
|
17858
|
+
event.preventDefault();
|
|
17859
|
+
if (abortLongPressSource === "escape" && isAbortLongPressActive()) resumeAbortLongPressAffordance();
|
|
17860
|
+
else if (!event.repeat) startAbortLongPress(event, { source: "escape" });
|
|
17861
|
+
return;
|
|
17862
|
+
}
|
|
17863
|
+
if (event.repeat) {
|
|
17864
|
+
event.preventDefault();
|
|
17865
|
+
return;
|
|
17866
|
+
}
|
|
17391
17867
|
if (document.activeElement === elements.promptInput && !elements.promptInput.value.trim() && doubleEscapeAction !== "none") {
|
|
17392
17868
|
const now = Date.now();
|
|
17393
17869
|
if (now - lastEmptyPromptEscapeTime < 500) {
|
|
@@ -17398,14 +17874,13 @@ window.addEventListener("keydown", (event) => {
|
|
|
17398
17874
|
}
|
|
17399
17875
|
lastEmptyPromptEscapeTime = now;
|
|
17400
17876
|
}
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
}
|
|
17877
|
+
});
|
|
17878
|
+
window.addEventListener("keyup", (event) => {
|
|
17879
|
+
if (event.key === "Escape" && abortLongPressSource === "escape") scheduleAbortLongPressReleaseReset();
|
|
17880
|
+
}, { capture: true });
|
|
17881
|
+
window.addEventListener("blur", () => {
|
|
17882
|
+
if (abortLongPressSource === "escape") scheduleAbortLongPressReleaseReset();
|
|
17883
|
+
else resetAbortLongPressAffordance();
|
|
17409
17884
|
});
|
|
17410
17885
|
|
|
17411
17886
|
elements.gitChangesRefreshButton?.addEventListener("click", refreshGitChangesDialog);
|