@firstpick/pi-package-webui 0.2.6 → 0.2.8
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/WEBUI_TUI_NATIVE_PARITY.json +3 -3
- package/bin/pi-webui.mjs +219 -2
- package/package.json +1 -1
- package/public/app.js +639 -79
- package/public/index.html +43 -2
- package/public/styles.css +223 -7
- package/tests/mobile-static.test.mjs +38 -4
- package/tests/native-parity.test.mjs +3 -0
package/public/app.js
CHANGED
|
@@ -47,6 +47,16 @@ const elements = {
|
|
|
47
47
|
nativeCommandMenu: $("#nativeCommandMenu"),
|
|
48
48
|
nativeSkillsButton: $("#nativeSkillsButton"),
|
|
49
49
|
nativeToolsButton: $("#nativeToolsButton"),
|
|
50
|
+
optionsMenuButton: $("#optionsMenuButton"),
|
|
51
|
+
optionsMenu: $("#optionsMenu"),
|
|
52
|
+
optionsResumeButton: $("#optionsResumeButton"),
|
|
53
|
+
optionsReloadButton: $("#optionsReloadButton"),
|
|
54
|
+
optionsNameButton: $("#optionsNameButton"),
|
|
55
|
+
optionsCloneButton: $("#optionsCloneButton"),
|
|
56
|
+
optionsSettingsButton: $("#optionsSettingsButton"),
|
|
57
|
+
optionsExportButton: $("#optionsExportButton"),
|
|
58
|
+
optionsForkButton: $("#optionsForkButton"),
|
|
59
|
+
optionsTreeButton: $("#optionsTreeButton"),
|
|
50
60
|
gitWorkflowPanel: $("#gitWorkflowPanel"),
|
|
51
61
|
gitWorkflowTitle: $("#gitWorkflowTitle"),
|
|
52
62
|
gitWorkflowHint: $("#gitWorkflowHint"),
|
|
@@ -81,6 +91,7 @@ const elements = {
|
|
|
81
91
|
sidePanel: $("#sidePanel"),
|
|
82
92
|
stateDetails: $("#stateDetails"),
|
|
83
93
|
queueBox: $("#queueBox"),
|
|
94
|
+
commandSearchInput: $("#commandSearchInput"),
|
|
84
95
|
commandsBox: $("#commandsBox"),
|
|
85
96
|
eventLog: $("#eventLog"),
|
|
86
97
|
dialog: $("#extensionDialog"),
|
|
@@ -148,6 +159,7 @@ let pathFastPicksLoadPromise = null;
|
|
|
148
159
|
let mobileTabsExpanded = false;
|
|
149
160
|
let openTerminalTabGroupKey = null;
|
|
150
161
|
let nativeCommandMenuOpen = false;
|
|
162
|
+
let optionsMenuOpen = false;
|
|
151
163
|
let availableCommands = [];
|
|
152
164
|
let rawAvailableCommands = [];
|
|
153
165
|
let commandSuggestions = [];
|
|
@@ -185,6 +197,11 @@ let blockedTabNotificationPermissionRequested = false;
|
|
|
185
197
|
let blockedTabNotificationFallbackNoted = false;
|
|
186
198
|
let agentDoneNotificationsEnabled = false;
|
|
187
199
|
let thinkingOutputVisible = true;
|
|
200
|
+
let webuiSettings = {};
|
|
201
|
+
let autocompleteMaxVisible = 12;
|
|
202
|
+
let doubleEscapeAction = "none";
|
|
203
|
+
let treeFilterMode = "default";
|
|
204
|
+
let lastEmptyPromptEscapeTime = 0;
|
|
188
205
|
let toolOutputGloballyExpanded = false;
|
|
189
206
|
let agentDoneNotificationPermissionRequested = false;
|
|
190
207
|
let agentDoneNotificationFallbackNoted = false;
|
|
@@ -205,6 +222,7 @@ let lastChatProgrammaticScrollAt = 0;
|
|
|
205
222
|
let chatUserScrollIntentUntil = 0;
|
|
206
223
|
let mobileFooterExpanded = false;
|
|
207
224
|
let footerModelPickerOpen = false;
|
|
225
|
+
let footerThinkingPickerOpen = false;
|
|
208
226
|
let publishMenuOpen = false;
|
|
209
227
|
let maxVisualViewportHeight = 0;
|
|
210
228
|
let abortRequestInFlight = false;
|
|
@@ -374,7 +392,25 @@ const OPTIONAL_COMMAND_FEATURES = new Map([
|
|
|
374
392
|
["todo-progress-status", "todoProgressWidget"],
|
|
375
393
|
]);
|
|
376
394
|
const HIDDEN_COMMAND_NAMES = new Set(["webui-tree-navigate", "webui-helper"]);
|
|
377
|
-
const NATIVE_SELECTOR_COMMANDS = new Set(["model", "settings", "theme", "fork", "clone", "resume", "tree", "login", "logout", "scoped-models", "tools", "skills"]);
|
|
395
|
+
const NATIVE_SELECTOR_COMMANDS = new Set(["model", "settings", "theme", "fork", "clone", "name", "resume", "tree", "login", "logout", "scoped-models", "tools", "skills"]);
|
|
396
|
+
const SETTINGS_THINKING_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
397
|
+
const SETTINGS_TRANSPORT_OPTIONS = ["sse", "websocket", "websocket-cached", "auto"];
|
|
398
|
+
const SETTINGS_HTTP_IDLE_TIMEOUT_OPTIONS = [
|
|
399
|
+
{ value: "30000", label: "30 sec" },
|
|
400
|
+
{ value: "60000", label: "1 min" },
|
|
401
|
+
{ value: "120000", label: "2 min" },
|
|
402
|
+
{ value: "300000", label: "5 min" },
|
|
403
|
+
{ value: "0", label: "disabled" },
|
|
404
|
+
];
|
|
405
|
+
const SETTINGS_DOUBLE_ESCAPE_OPTIONS = [
|
|
406
|
+
{ value: "tree", label: "open /tree" },
|
|
407
|
+
{ value: "fork", label: "open /fork" },
|
|
408
|
+
{ value: "none", label: "do nothing" },
|
|
409
|
+
];
|
|
410
|
+
const SETTINGS_TREE_FILTER_OPTIONS = ["default", "no-tools", "user-only", "labeled-only", "all"];
|
|
411
|
+
const SETTINGS_IMAGE_WIDTH_OPTIONS = ["60", "80", "120"];
|
|
412
|
+
const SETTINGS_EDITOR_PADDING_OPTIONS = ["0", "1", "2", "3"];
|
|
413
|
+
const SETTINGS_AUTOCOMPLETE_OPTIONS = ["3", "5", "7", "10", "15", "20"];
|
|
378
414
|
const optionalFeatureInstallInProgress = new Set();
|
|
379
415
|
const gitFooterPayloadRefreshInFlightByTab = new Set();
|
|
380
416
|
|
|
@@ -696,11 +732,37 @@ function restoreThinkingVisibilitySetting() {
|
|
|
696
732
|
renderThinkingVisibilityToggle();
|
|
697
733
|
}
|
|
698
734
|
|
|
735
|
+
function clampAutocompleteMaxVisible(value) {
|
|
736
|
+
const number = Number(value);
|
|
737
|
+
if (!Number.isFinite(number)) return 12;
|
|
738
|
+
return Math.max(3, Math.min(20, Math.floor(number)));
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function applyNativeSettingsForBrowser(settings = {}, { syncThinkingVisibility = false } = {}) {
|
|
742
|
+
if (!settings || typeof settings !== "object") return;
|
|
743
|
+
webuiSettings = { ...webuiSettings, ...settings, warnings: { ...(webuiSettings.warnings || {}), ...(settings.warnings || {}) } };
|
|
744
|
+
if (settings.autocompleteMaxVisible !== undefined) autocompleteMaxVisible = clampAutocompleteMaxVisible(settings.autocompleteMaxVisible);
|
|
745
|
+
if (SETTINGS_DOUBLE_ESCAPE_OPTIONS.some((option) => option.value === settings.doubleEscapeAction)) doubleEscapeAction = settings.doubleEscapeAction;
|
|
746
|
+
if (SETTINGS_TREE_FILTER_OPTIONS.includes(settings.treeFilterMode)) treeFilterMode = settings.treeFilterMode;
|
|
747
|
+
if (syncThinkingVisibility && typeof settings.hideThinkingBlock === "boolean") setThinkingOutputVisible(!settings.hideThinkingBlock);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function refreshNativeSettings(tabContext = activeTabContext()) {
|
|
751
|
+
if (!tabContext.tabId) return;
|
|
752
|
+
const response = await api("/api/settings", { tabId: tabContext.tabId });
|
|
753
|
+
if (!isCurrentTabContext(tabContext)) return;
|
|
754
|
+
applyNativeSettingsForBrowser(response.data?.settings || {});
|
|
755
|
+
}
|
|
756
|
+
|
|
699
757
|
function setComposerActionsOpen(open) {
|
|
700
758
|
const shouldOpen = open && isMobileView();
|
|
701
759
|
document.body.classList.toggle("composer-actions-open", shouldOpen);
|
|
702
760
|
elements.composerActionsButton.setAttribute("aria-expanded", shouldOpen ? "true" : "false");
|
|
703
|
-
if (!shouldOpen)
|
|
761
|
+
if (!shouldOpen) {
|
|
762
|
+
setPublishMenuOpen(false);
|
|
763
|
+
setNativeCommandMenuOpen(false);
|
|
764
|
+
setOptionsMenuOpen(false);
|
|
765
|
+
}
|
|
704
766
|
}
|
|
705
767
|
|
|
706
768
|
function isUserBashActive(tabId = activeTabId) {
|
|
@@ -760,23 +822,63 @@ function updateComposerModeButtons() {
|
|
|
760
822
|
document.body.classList.toggle("pi-run-active", runActive || abortAvailable);
|
|
761
823
|
}
|
|
762
824
|
|
|
825
|
+
function isFooterPickerOpen() {
|
|
826
|
+
return footerModelPickerOpen || footerThinkingPickerOpen;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function footerActivePickerTarget() {
|
|
830
|
+
if (footerThinkingPickerOpen) return elements.statusBar.querySelector(".footer-thinking.footer-meta-action");
|
|
831
|
+
if (footerModelPickerOpen) return elements.statusBar.querySelector(".footer-model.footer-meta-action, .footer-tui-model");
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function clearFooterPickerPosition() {
|
|
836
|
+
document.documentElement.style.removeProperty("--footer-model-picker-bottom");
|
|
837
|
+
document.documentElement.style.removeProperty("--footer-model-picker-left");
|
|
838
|
+
document.documentElement.style.removeProperty("--footer-model-picker-right");
|
|
839
|
+
}
|
|
840
|
+
|
|
763
841
|
function updateFooterModelPickerPosition() {
|
|
764
|
-
if (!
|
|
765
|
-
|
|
842
|
+
if (!isFooterPickerOpen()) {
|
|
843
|
+
clearFooterPickerPosition();
|
|
766
844
|
return;
|
|
767
845
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
846
|
+
if (isMobileView()) {
|
|
847
|
+
document.documentElement.style.removeProperty("--footer-model-picker-left");
|
|
848
|
+
document.documentElement.style.removeProperty("--footer-model-picker-right");
|
|
849
|
+
const viewportHeight = window.innerHeight || window.visualViewport?.height || document.documentElement.clientHeight;
|
|
850
|
+
const statusTop = elements.statusBar.getBoundingClientRect().top;
|
|
851
|
+
const bottom = Math.max(8, Math.round(viewportHeight - statusTop + 6));
|
|
852
|
+
document.documentElement.style.setProperty("--footer-model-picker-bottom", `${bottom}px`);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
document.documentElement.style.removeProperty("--footer-model-picker-bottom");
|
|
856
|
+
const picker = elements.statusBar.querySelector(".footer-model-picker");
|
|
857
|
+
const target = footerActivePickerTarget();
|
|
858
|
+
if (!picker || !target) {
|
|
859
|
+
document.documentElement.style.removeProperty("--footer-model-picker-left");
|
|
860
|
+
document.documentElement.style.removeProperty("--footer-model-picker-right");
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
864
|
+
const statusRect = elements.statusBar.getBoundingClientRect();
|
|
865
|
+
const targetRect = target.getBoundingClientRect();
|
|
866
|
+
const pickerWidth = picker.offsetWidth || Math.min(544, viewportWidth - 16);
|
|
867
|
+
const minLeft = 8 - statusRect.left;
|
|
868
|
+
const maxLeft = Math.max(minLeft, viewportWidth - pickerWidth - 8 - statusRect.left);
|
|
869
|
+
const targetCenterLeft = targetRect.left - statusRect.left + (targetRect.width / 2) - (pickerWidth / 2);
|
|
870
|
+
const left = Math.min(maxLeft, Math.max(minLeft, targetCenterLeft));
|
|
871
|
+
document.documentElement.style.setProperty("--footer-model-picker-left", `${Math.round(left)}px`);
|
|
872
|
+
document.documentElement.style.setProperty("--footer-model-picker-right", "auto");
|
|
772
873
|
}
|
|
773
874
|
|
|
774
875
|
function setMobileFooterExpanded(expanded) {
|
|
775
876
|
mobileFooterExpanded = expanded && isMobileView();
|
|
776
|
-
if (mobileFooterExpanded &&
|
|
877
|
+
if (mobileFooterExpanded && isFooterPickerOpen()) {
|
|
777
878
|
footerModelPickerOpen = false;
|
|
879
|
+
footerThinkingPickerOpen = false;
|
|
778
880
|
document.body.classList.remove("footer-model-picker-open");
|
|
779
|
-
elements.statusBar.
|
|
881
|
+
elements.statusBar.querySelectorAll(".footer-model-picker").forEach((node) => node.remove());
|
|
780
882
|
}
|
|
781
883
|
document.body.classList.toggle("footer-details-expanded", mobileFooterExpanded);
|
|
782
884
|
const button = elements.statusBar.querySelector(".footer-details-toggle");
|
|
@@ -2792,6 +2894,7 @@ async function switchTab(tabId) {
|
|
|
2792
2894
|
clearOpenTerminalTabGroup(null, { force: true });
|
|
2793
2895
|
setMobileTabsExpanded(false);
|
|
2794
2896
|
footerModelPickerOpen = false;
|
|
2897
|
+
footerThinkingPickerOpen = false;
|
|
2795
2898
|
saveActiveDraft();
|
|
2796
2899
|
const tabContext = setActiveTabId(tabId, { remember: true });
|
|
2797
2900
|
resetActiveTabUi();
|
|
@@ -3340,6 +3443,16 @@ function shortModelLabel(model) {
|
|
|
3340
3443
|
return `(${model.provider}) ${model.id}`;
|
|
3341
3444
|
}
|
|
3342
3445
|
|
|
3446
|
+
function footerThinkingLevelLabel(level) {
|
|
3447
|
+
return String(level || "unknown").trim() || "unknown";
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
function footerThinkingDisplay(state = currentState) {
|
|
3451
|
+
const current = footerThinkingLevelLabel(state?.thinkingLevel);
|
|
3452
|
+
const pending = state?.pendingThinkingLevel ? footerThinkingLevelLabel(state.pendingThinkingLevel) : "";
|
|
3453
|
+
return pending && pending !== current ? `${current} → ${pending} next` : current;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3343
3456
|
function footerModelLine(model = currentState?.model, thinkingLevel = currentState?.thinkingLevel) {
|
|
3344
3457
|
const label = shortModelLabel(model);
|
|
3345
3458
|
if (!model?.reasoning) return label;
|
|
@@ -3347,6 +3460,39 @@ function footerModelLine(model = currentState?.model, thinkingLevel = currentSta
|
|
|
3347
3460
|
return `${label} • ${thinking}`;
|
|
3348
3461
|
}
|
|
3349
3462
|
|
|
3463
|
+
function normalizeSelectedModel(model) {
|
|
3464
|
+
const provider = String(model?.provider || "").trim();
|
|
3465
|
+
const id = String(model?.id || model?.modelId || "").trim();
|
|
3466
|
+
if (!provider || !id) return null;
|
|
3467
|
+
const known = [...availableModels, ...footerScopedModels].find((item) => item?.provider === provider && item?.id === id);
|
|
3468
|
+
return { ...(known || {}), ...model, provider, id };
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
function applyOptimisticModelSelection(model, tabContext = activeTabContext()) {
|
|
3472
|
+
const nextModel = normalizeSelectedModel(model);
|
|
3473
|
+
if (!nextModel || !currentState || !isCurrentTabContext(tabContext)) return nextModel;
|
|
3474
|
+
const changed = modelStateKey(currentState.model) !== modelStateKey(nextModel);
|
|
3475
|
+
currentState = { ...currentState, model: nextModel };
|
|
3476
|
+
renderStatus();
|
|
3477
|
+
if (changed) requestGitFooterWebuiPayload(tabContext, { force: true });
|
|
3478
|
+
return nextModel;
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
function applyOptimisticThinkingSelection(data, tabContext = activeTabContext()) {
|
|
3482
|
+
const level = String(data?.level || data?.requestedLevel || "").trim();
|
|
3483
|
+
if (!level || !currentState || !isCurrentTabContext(tabContext)) return level;
|
|
3484
|
+
if (data?.pending) currentState = { ...currentState, pendingThinkingLevel: level };
|
|
3485
|
+
else currentState = { ...currentState, thinkingLevel: level, pendingThinkingLevel: undefined };
|
|
3486
|
+
renderStatus();
|
|
3487
|
+
if (!data?.pending) requestGitFooterWebuiPayload(tabContext, { force: true });
|
|
3488
|
+
return level;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
function footerThinkingLevels() {
|
|
3492
|
+
const levels = Array.from(elements.thinkingSelect?.options || []).map((option) => option.value).filter(Boolean);
|
|
3493
|
+
return levels.length ? levels : ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3350
3496
|
function formatFooterTokenCount(value) {
|
|
3351
3497
|
const n = Math.max(0, Number(value) || 0);
|
|
3352
3498
|
if (n < 1000) return `${Math.round(n)}`;
|
|
@@ -3478,6 +3624,7 @@ const FOOTER_META_CLASS_BY_KEY = new Map([
|
|
|
3478
3624
|
["git-extra", "footer-git-extra"],
|
|
3479
3625
|
["context", "footer-context"],
|
|
3480
3626
|
["model", "footer-model"],
|
|
3627
|
+
["thinking", "footer-thinking"],
|
|
3481
3628
|
]);
|
|
3482
3629
|
|
|
3483
3630
|
function cleanFooterPayloadText(value, fallback = "") {
|
|
@@ -3573,6 +3720,24 @@ function parseGitFooterWebuiPayload() {
|
|
|
3573
3720
|
return parseGitFooterWebuiPayloadRaw(readCachedGitFooterWebuiPayloadRaw());
|
|
3574
3721
|
}
|
|
3575
3722
|
|
|
3723
|
+
function footerPayloadWithLiveModel(payload) {
|
|
3724
|
+
if (!payload || !currentState?.model) return payload;
|
|
3725
|
+
const model = shortModelLabel(currentState.model);
|
|
3726
|
+
const effort = footerThinkingDisplay();
|
|
3727
|
+
const hasThinkingChip = [...payload.main, ...payload.meta].some((chip) => chip?.key === "thinking");
|
|
3728
|
+
const effortChip = (chip) => ({ ...chip, key: "thinking", label: "effort", value: effort, title: `effort: ${effort}`, tone: "mauve" });
|
|
3729
|
+
const splitChip = (chip) => {
|
|
3730
|
+
if (chip?.key === "thinking") return [effortChip(chip)];
|
|
3731
|
+
if (chip?.key !== "model") return [chip];
|
|
3732
|
+
const modelChip = { ...chip, value: model, title: `model: ${model}` };
|
|
3733
|
+
return hasThinkingChip ? [modelChip] : [modelChip, effortChip(chip)];
|
|
3734
|
+
};
|
|
3735
|
+
return {
|
|
3736
|
+
main: payload.main.flatMap(splitChip),
|
|
3737
|
+
meta: payload.meta.flatMap(splitChip),
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3576
3741
|
function footerMetaClassForPayload(chip) {
|
|
3577
3742
|
const base = FOOTER_META_CLASS_BY_KEY.get(chip.key) || "footer-extension-meta";
|
|
3578
3743
|
const toneClass = chip.tone ? ` tone-${chip.tone}` : "";
|
|
@@ -3623,6 +3788,9 @@ function renderGitFooterPayloadMeta(chip, tab) {
|
|
|
3623
3788
|
} else if (chip.key === "model") {
|
|
3624
3789
|
options.onClick = () => setFooterModelPickerOpen(!footerModelPickerOpen);
|
|
3625
3790
|
options.title = chip.title || `Change scoped model: ${chip.value}`;
|
|
3791
|
+
} else if (chip.key === "thinking") {
|
|
3792
|
+
options.onClick = () => setFooterThinkingPickerOpen(!footerThinkingPickerOpen);
|
|
3793
|
+
options.title = chip.title || `Change thinking effort: ${chip.value}`;
|
|
3626
3794
|
}
|
|
3627
3795
|
const node = footerMeta(chip.label, chip.value, footerMetaClassForPayload(chip), options);
|
|
3628
3796
|
return chip.contextUsage ? applyFooterContextUsage(node, chip.contextUsage) : node;
|
|
@@ -3633,7 +3801,7 @@ function renderGitFooterPayload(payload) {
|
|
|
3633
3801
|
elements.statusBar.replaceChildren();
|
|
3634
3802
|
elements.statusBar.classList.remove("statusbar-tui-footer");
|
|
3635
3803
|
elements.statusBar.classList.add("statusbar-git-footer");
|
|
3636
|
-
document.body.classList.toggle("footer-model-picker-open",
|
|
3804
|
+
document.body.classList.toggle("footer-model-picker-open", isFooterPickerOpen());
|
|
3637
3805
|
|
|
3638
3806
|
const row1 = make("div", "footer-line footer-line-main");
|
|
3639
3807
|
row1.append(...payload.main.map(renderGitFooterPayloadMetric));
|
|
@@ -3648,6 +3816,7 @@ function renderGitFooterPayload(payload) {
|
|
|
3648
3816
|
|
|
3649
3817
|
elements.statusBar.append(row1, row2);
|
|
3650
3818
|
if (footerModelPickerOpen) elements.statusBar.append(renderFooterModelPicker());
|
|
3819
|
+
if (footerThinkingPickerOpen) elements.statusBar.append(renderFooterThinkingPicker());
|
|
3651
3820
|
setMobileFooterExpanded(mobileFooterExpanded);
|
|
3652
3821
|
updateFooterModelPickerPosition();
|
|
3653
3822
|
}
|
|
@@ -3672,7 +3841,7 @@ function renderMinimalFooter() {
|
|
|
3672
3841
|
elements.statusBar.replaceChildren();
|
|
3673
3842
|
elements.statusBar.classList.remove("statusbar-git-footer");
|
|
3674
3843
|
elements.statusBar.classList.add("statusbar-tui-footer");
|
|
3675
|
-
document.body.classList.toggle("footer-model-picker-open",
|
|
3844
|
+
document.body.classList.toggle("footer-model-picker-open", isFooterPickerOpen());
|
|
3676
3845
|
elements.statusBar.append(renderTuiFooterLine({
|
|
3677
3846
|
cwd: workspaceLabel,
|
|
3678
3847
|
cwdTitle: tab ? `Change cwd for ${tab.title}: ${workspaceLabel}` : undefined,
|
|
@@ -3681,19 +3850,35 @@ function renderMinimalFooter() {
|
|
|
3681
3850
|
model: modelLine,
|
|
3682
3851
|
}));
|
|
3683
3852
|
if (footerModelPickerOpen) elements.statusBar.append(renderFooterModelPicker());
|
|
3853
|
+
if (footerThinkingPickerOpen) elements.statusBar.append(renderFooterThinkingPicker());
|
|
3684
3854
|
setMobileFooterExpanded(false);
|
|
3685
3855
|
updateFooterModelPickerPosition();
|
|
3686
3856
|
}
|
|
3687
3857
|
|
|
3688
3858
|
function setFooterModelPickerOpen(open) {
|
|
3689
3859
|
footerModelPickerOpen = !!open;
|
|
3860
|
+
if (footerModelPickerOpen) footerThinkingPickerOpen = false;
|
|
3690
3861
|
if (footerModelPickerOpen && isMobileView()) {
|
|
3691
3862
|
mobileFooterExpanded = false;
|
|
3692
3863
|
document.body.classList.remove("footer-details-expanded");
|
|
3693
3864
|
setComposerActionsOpen(false);
|
|
3694
3865
|
setMobileTabsExpanded(false);
|
|
3695
3866
|
}
|
|
3696
|
-
document.body.classList.toggle("footer-model-picker-open",
|
|
3867
|
+
document.body.classList.toggle("footer-model-picker-open", isFooterPickerOpen());
|
|
3868
|
+
renderFooter();
|
|
3869
|
+
updateFooterModelPickerPosition();
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
function setFooterThinkingPickerOpen(open) {
|
|
3873
|
+
footerThinkingPickerOpen = !!open;
|
|
3874
|
+
if (footerThinkingPickerOpen) footerModelPickerOpen = false;
|
|
3875
|
+
if (footerThinkingPickerOpen && isMobileView()) {
|
|
3876
|
+
mobileFooterExpanded = false;
|
|
3877
|
+
document.body.classList.remove("footer-details-expanded");
|
|
3878
|
+
setComposerActionsOpen(false);
|
|
3879
|
+
setMobileTabsExpanded(false);
|
|
3880
|
+
}
|
|
3881
|
+
document.body.classList.toggle("footer-model-picker-open", isFooterPickerOpen());
|
|
3697
3882
|
renderFooter();
|
|
3698
3883
|
updateFooterModelPickerPosition();
|
|
3699
3884
|
}
|
|
@@ -3703,15 +3888,16 @@ async function applyFooterModel(model) {
|
|
|
3703
3888
|
const tabContext = activeTabContext();
|
|
3704
3889
|
try {
|
|
3705
3890
|
footerModelPickerOpen = false;
|
|
3706
|
-
await api("/api/model", { method: "POST", body: { provider: model.provider, modelId: model.id }, tabId: tabContext.tabId });
|
|
3891
|
+
const response = await api("/api/model", { method: "POST", body: { provider: model.provider, modelId: model.id }, tabId: tabContext.tabId });
|
|
3707
3892
|
if (!isCurrentTabContext(tabContext)) return;
|
|
3893
|
+
applyOptimisticModelSelection(response.data || model, tabContext);
|
|
3708
3894
|
await refreshState(tabContext);
|
|
3709
3895
|
await refreshModels(tabContext);
|
|
3710
3896
|
} catch (error) {
|
|
3711
3897
|
if (isCurrentTabContext(tabContext)) addEvent(error.message, "error");
|
|
3712
3898
|
} finally {
|
|
3713
3899
|
if (isCurrentTabContext(tabContext)) {
|
|
3714
|
-
document.body.classList.toggle("footer-model-picker-open",
|
|
3900
|
+
document.body.classList.toggle("footer-model-picker-open", isFooterPickerOpen());
|
|
3715
3901
|
renderFooter();
|
|
3716
3902
|
}
|
|
3717
3903
|
}
|
|
@@ -3750,6 +3936,54 @@ function renderFooterModelPicker() {
|
|
|
3750
3936
|
return picker;
|
|
3751
3937
|
}
|
|
3752
3938
|
|
|
3939
|
+
async function applyFooterThinking(level) {
|
|
3940
|
+
const nextLevel = String(level || "").trim();
|
|
3941
|
+
if (!nextLevel) return;
|
|
3942
|
+
const tabContext = activeTabContext();
|
|
3943
|
+
try {
|
|
3944
|
+
footerThinkingPickerOpen = false;
|
|
3945
|
+
const response = await api("/api/thinking", { method: "POST", body: { level: nextLevel }, tabId: tabContext.tabId });
|
|
3946
|
+
if (!isCurrentTabContext(tabContext)) return;
|
|
3947
|
+
applyOptimisticThinkingSelection(response.data || { level: nextLevel }, tabContext);
|
|
3948
|
+
if (response.data?.pending) {
|
|
3949
|
+
addEvent(response.data.message || `Thinking effort ${response.data.level || nextLevel} will apply to the next prompt.`, "info");
|
|
3950
|
+
} else if (response.data?.level) {
|
|
3951
|
+
const requested = response.data.requestedLevel;
|
|
3952
|
+
const effective = response.data.level;
|
|
3953
|
+
addEvent(requested && requested !== effective ? `Thinking effort set to ${effective} (requested ${requested}).` : `Thinking effort set to ${effective}.`, "info");
|
|
3954
|
+
}
|
|
3955
|
+
await refreshState(tabContext);
|
|
3956
|
+
} catch (error) {
|
|
3957
|
+
if (isCurrentTabContext(tabContext)) addEvent(error.message, "error");
|
|
3958
|
+
} finally {
|
|
3959
|
+
if (isCurrentTabContext(tabContext)) {
|
|
3960
|
+
document.body.classList.toggle("footer-model-picker-open", isFooterPickerOpen());
|
|
3961
|
+
renderFooter();
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
function renderFooterThinkingPicker() {
|
|
3967
|
+
const picker = make("div", "footer-model-picker footer-thinking-picker");
|
|
3968
|
+
picker.setAttribute("role", "listbox");
|
|
3969
|
+
picker.setAttribute("aria-label", "Thinking effort");
|
|
3970
|
+
picker.append(make("div", "footer-model-picker-title", "Thinking effort"));
|
|
3971
|
+
picker.append(make("div", "footer-model-picker-source", "Applies to this Pi tab."));
|
|
3972
|
+
const current = currentState?.pendingThinkingLevel || currentState?.thinkingLevel || "off";
|
|
3973
|
+
for (const level of footerThinkingLevels()) {
|
|
3974
|
+
const selected = current === level;
|
|
3975
|
+
const button = make("button", `footer-model-option${selected ? " active" : ""}`);
|
|
3976
|
+
button.type = "button";
|
|
3977
|
+
button.setAttribute("role", "option");
|
|
3978
|
+
button.setAttribute("aria-selected", selected ? "true" : "false");
|
|
3979
|
+
button.title = `Set thinking effort to ${level}`;
|
|
3980
|
+
button.append(make("span", "footer-model-option-main", footerThinkingLevelLabel(level)));
|
|
3981
|
+
button.addEventListener("click", () => applyFooterThinking(level));
|
|
3982
|
+
picker.append(button);
|
|
3983
|
+
}
|
|
3984
|
+
return picker;
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3753
3987
|
function pathPickerButton(label, title, onClick, className = "") {
|
|
3754
3988
|
const button = make("button", className, label);
|
|
3755
3989
|
button.type = "button";
|
|
@@ -4017,7 +4251,7 @@ async function changeActiveTabCwd() {
|
|
|
4017
4251
|
function renderFooter() {
|
|
4018
4252
|
const gitFooterPayload = parseGitFooterWebuiPayload();
|
|
4019
4253
|
if (gitFooterPayload) {
|
|
4020
|
-
renderGitFooterPayload(gitFooterPayload);
|
|
4254
|
+
renderGitFooterPayload(footerPayloadWithLiveModel(gitFooterPayload));
|
|
4021
4255
|
return;
|
|
4022
4256
|
}
|
|
4023
4257
|
renderMinimalFooter();
|
|
@@ -7221,6 +7455,13 @@ function setNativeCommandMenuOpen(open) {
|
|
|
7221
7455
|
elements.nativeCommandMenuButton.parentElement?.classList.toggle("open", nativeCommandMenuOpen);
|
|
7222
7456
|
}
|
|
7223
7457
|
|
|
7458
|
+
function setOptionsMenuOpen(open) {
|
|
7459
|
+
optionsMenuOpen = !!open;
|
|
7460
|
+
elements.optionsMenuButton.setAttribute("aria-expanded", optionsMenuOpen ? "true" : "false");
|
|
7461
|
+
elements.optionsMenuButton.classList.toggle("menu-open", optionsMenuOpen);
|
|
7462
|
+
elements.optionsMenuButton.parentElement?.classList.toggle("open", optionsMenuOpen);
|
|
7463
|
+
}
|
|
7464
|
+
|
|
7224
7465
|
function optionalFeatureIdForCommand(name) {
|
|
7225
7466
|
if (OPTIONAL_COMMAND_FEATURES.has(name)) return OPTIONAL_COMMAND_FEATURES.get(name);
|
|
7226
7467
|
if (name === "release-toggle" || name === "release-abort" || name === "release-npm-logs") return "releaseNpm";
|
|
@@ -7288,10 +7529,10 @@ function resetOptionalFeatureAvailability() {
|
|
|
7288
7529
|
renderOptionalFeatureControls();
|
|
7289
7530
|
}
|
|
7290
7531
|
|
|
7291
|
-
function requestGitFooterWebuiPayload(tabContext = activeTabContext()) {
|
|
7532
|
+
function requestGitFooterWebuiPayload(tabContext = activeTabContext(), { force = false } = {}) {
|
|
7292
7533
|
if (!tabContext.tabId || isOptionalFeatureDisabled("gitFooterStatus")) return;
|
|
7293
7534
|
if (currentState?.isStreaming || currentState?.isCompacting) return;
|
|
7294
|
-
if (!hasAvailableCommand("git-footer-refresh") || statusEntries.has(GIT_FOOTER_WEBUI_STATUS_KEY)) return;
|
|
7535
|
+
if (!hasAvailableCommand("git-footer-refresh") || (!force && statusEntries.has(GIT_FOOTER_WEBUI_STATUS_KEY))) return;
|
|
7295
7536
|
if (gitFooterPayloadRefreshInFlightByTab.has(tabContext.tabId)) return;
|
|
7296
7537
|
|
|
7297
7538
|
gitFooterPayloadRefreshInFlightByTab.add(tabContext.tabId);
|
|
@@ -7377,9 +7618,11 @@ function renderOptionalFeaturePanel() {
|
|
|
7377
7618
|
}
|
|
7378
7619
|
|
|
7379
7620
|
function renderOptionalFeatureControls() {
|
|
7621
|
+
const hasGitWorkflow = isOptionalFeatureEnabled("gitWorkflow");
|
|
7622
|
+
elements.gitWorkflowButton.hidden = !hasGitWorkflow;
|
|
7380
7623
|
setOptionalControlState(
|
|
7381
7624
|
elements.gitWorkflowButton,
|
|
7382
|
-
|
|
7625
|
+
hasGitWorkflow,
|
|
7383
7626
|
optionalFeatureUnavailableMessage("gitWorkflow"),
|
|
7384
7627
|
);
|
|
7385
7628
|
|
|
@@ -7388,6 +7631,7 @@ function renderOptionalFeatureControls() {
|
|
|
7388
7631
|
const hasPublishWorkflow = isOptionalFeatureEnabled("releaseNpm") || isOptionalFeatureEnabled("releaseAur");
|
|
7389
7632
|
const publishContainer = elements.publishButton.parentElement;
|
|
7390
7633
|
if (publishContainer) publishContainer.hidden = !hasPublishWorkflow;
|
|
7634
|
+
elements.publishButton.hidden = !hasPublishWorkflow;
|
|
7391
7635
|
setOptionalControlState(
|
|
7392
7636
|
elements.publishButton,
|
|
7393
7637
|
hasPublishWorkflow,
|
|
@@ -7400,6 +7644,7 @@ function renderOptionalFeatureControls() {
|
|
|
7400
7644
|
elements.nativeToolsButton.hidden = !isOptionalFeatureEnabled("tuiToolsCommand");
|
|
7401
7645
|
const nativeCommandMenuContainer = elements.nativeCommandMenuButton.parentElement;
|
|
7402
7646
|
if (nativeCommandMenuContainer) nativeCommandMenuContainer.hidden = !hasNativeCommandMenu;
|
|
7647
|
+
elements.nativeCommandMenuButton.hidden = !hasNativeCommandMenu;
|
|
7403
7648
|
setOptionalControlState(
|
|
7404
7649
|
elements.nativeCommandMenuButton,
|
|
7405
7650
|
hasNativeCommandMenu,
|
|
@@ -7457,6 +7702,7 @@ async function installOptionalFeature(featureId) {
|
|
|
7457
7702
|
function runPublishWorkflow(command) {
|
|
7458
7703
|
setComposerActionsOpen(false);
|
|
7459
7704
|
setPublishMenuOpen(false);
|
|
7705
|
+
setOptionsMenuOpen(false);
|
|
7460
7706
|
const commandName = String(command || "").replace(/^\//, "").split(/\s+/)[0];
|
|
7461
7707
|
const featureId = OPTIONAL_COMMAND_FEATURES.get(commandName);
|
|
7462
7708
|
if ((featureId && !isOptionalFeatureEnabled(featureId)) || !hasAvailableCommand(commandName)) {
|
|
@@ -7474,6 +7720,7 @@ async function runNativeCommandMenu(command) {
|
|
|
7474
7720
|
setComposerActionsOpen(false);
|
|
7475
7721
|
setPublishMenuOpen(false);
|
|
7476
7722
|
setNativeCommandMenuOpen(false);
|
|
7723
|
+
setOptionsMenuOpen(false);
|
|
7477
7724
|
const commandName = String(command || "").replace(/^\//, "").split(/\s+/)[0].toLowerCase();
|
|
7478
7725
|
const featureId = optionalFeatureIdForCommand(commandName);
|
|
7479
7726
|
if ((featureId && !isOptionalFeatureEnabled(featureId)) || !hasAvailableCommand(commandName)) {
|
|
@@ -7484,7 +7731,8 @@ async function runNativeCommandMenu(command) {
|
|
|
7484
7731
|
});
|
|
7485
7732
|
return;
|
|
7486
7733
|
}
|
|
7487
|
-
await handleNativeSlashSelectorCommand(command);
|
|
7734
|
+
if (await handleNativeSlashSelectorCommand(command)) return;
|
|
7735
|
+
await sendPrompt("prompt", command);
|
|
7488
7736
|
}
|
|
7489
7737
|
|
|
7490
7738
|
function slashCommandName(message) {
|
|
@@ -7619,11 +7867,13 @@ async function openNativeModelSelector() {
|
|
|
7619
7867
|
activeId,
|
|
7620
7868
|
onSelect: async (item) => {
|
|
7621
7869
|
setNativeCommandError("");
|
|
7870
|
+
const tabContext = activeTabContext(nativeCommandTabId || activeTabId);
|
|
7622
7871
|
try {
|
|
7623
|
-
await nativeCommandApi("/api/model", { method: "POST", body: { provider: item.model.provider, modelId: item.model.id } });
|
|
7872
|
+
const response = await nativeCommandApi("/api/model", { method: "POST", body: { provider: item.model.provider, modelId: item.model.id } });
|
|
7873
|
+
applyOptimisticModelSelection(response.data || item.model, tabContext);
|
|
7624
7874
|
addTransientMessage({ role: "native", title: "/model", content: `Model set to ${item.label}.`, level: "info" });
|
|
7625
7875
|
closeNativeCommandDialog();
|
|
7626
|
-
await refreshState();
|
|
7876
|
+
await refreshState(tabContext);
|
|
7627
7877
|
} catch (error) {
|
|
7628
7878
|
setNativeCommandError(error.message || String(error));
|
|
7629
7879
|
}
|
|
@@ -7672,81 +7922,224 @@ function openNativeThemeSelector() {
|
|
|
7672
7922
|
});
|
|
7673
7923
|
}
|
|
7674
7924
|
|
|
7675
|
-
function
|
|
7925
|
+
function nativeSettingsBadge(label, tone = "") {
|
|
7926
|
+
return make("span", `native-settings-badge${tone ? ` native-settings-badge-${tone}` : ""}`, label);
|
|
7927
|
+
}
|
|
7928
|
+
|
|
7929
|
+
function normalizedSettingsBadge(badge) {
|
|
7930
|
+
if (!badge) return null;
|
|
7931
|
+
if (typeof badge === "string") return { label: badge, tone: "" };
|
|
7932
|
+
return badge;
|
|
7933
|
+
}
|
|
7934
|
+
|
|
7935
|
+
function nativeSettingsLabelRow(label, badge) {
|
|
7936
|
+
const row = make("span", "native-settings-label-row");
|
|
7937
|
+
row.append(make("span", "native-settings-label", label));
|
|
7938
|
+
const normalized = normalizedSettingsBadge(badge);
|
|
7939
|
+
if (normalized?.label) row.append(nativeSettingsBadge(normalized.label, normalized.tone));
|
|
7940
|
+
return row;
|
|
7941
|
+
}
|
|
7942
|
+
|
|
7943
|
+
function normalizedSettingOptions(options) {
|
|
7944
|
+
return options.map((option) => {
|
|
7945
|
+
if (typeof option === "string" || typeof option === "number") return { value: String(option), label: String(option) };
|
|
7946
|
+
return { value: String(option.value), label: option.label || String(option.value) };
|
|
7947
|
+
});
|
|
7948
|
+
}
|
|
7949
|
+
|
|
7950
|
+
function nativeSettingSelect(label, value, options, hint, badge) {
|
|
7676
7951
|
const field = make("label", "native-settings-field");
|
|
7677
|
-
field.append(
|
|
7952
|
+
field.append(nativeSettingsLabelRow(label, badge));
|
|
7678
7953
|
const select = make("select");
|
|
7679
|
-
for (const option of options) {
|
|
7680
|
-
const element = make("option", undefined, option.label
|
|
7954
|
+
for (const option of normalizedSettingOptions(options)) {
|
|
7955
|
+
const element = make("option", undefined, option.label);
|
|
7681
7956
|
element.value = option.value;
|
|
7682
7957
|
select.append(element);
|
|
7683
7958
|
}
|
|
7684
|
-
select.value = value;
|
|
7959
|
+
select.value = String(value);
|
|
7685
7960
|
field.append(select);
|
|
7961
|
+
if (hint) field.append(make("span", "native-settings-hint", hint));
|
|
7686
7962
|
return { field, select };
|
|
7687
7963
|
}
|
|
7688
7964
|
|
|
7689
|
-
function nativeSettingToggle(label, checked, hint) {
|
|
7965
|
+
function nativeSettingToggle(label, checked, hint, badge) {
|
|
7690
7966
|
const field = make("label", "native-settings-toggle");
|
|
7691
7967
|
const input = make("input");
|
|
7692
7968
|
input.type = "checkbox";
|
|
7693
7969
|
input.checked = !!checked;
|
|
7694
7970
|
const text = make("span");
|
|
7695
|
-
text.append(
|
|
7971
|
+
text.append(nativeSettingsLabelRow(label, badge));
|
|
7696
7972
|
if (hint) text.append(make("span", "native-settings-hint", hint));
|
|
7697
7973
|
field.append(input, text);
|
|
7698
7974
|
return { field, input };
|
|
7699
7975
|
}
|
|
7700
7976
|
|
|
7701
|
-
function
|
|
7702
|
-
|
|
7703
|
-
|
|
7977
|
+
function nativeSettingsSection(title, description, controls, { open = true, badge } = {}) {
|
|
7978
|
+
const section = make("details", "native-settings-section");
|
|
7979
|
+
section.open = !!open;
|
|
7980
|
+
const summary = make("summary", "native-settings-section-summary");
|
|
7981
|
+
const titleRow = make("span", "native-settings-section-title");
|
|
7982
|
+
titleRow.append(make("strong", undefined, title));
|
|
7983
|
+
const normalized = normalizedSettingsBadge(badge);
|
|
7984
|
+
if (normalized?.label) titleRow.append(nativeSettingsBadge(normalized.label, normalized.tone));
|
|
7985
|
+
summary.append(titleRow);
|
|
7986
|
+
if (description) summary.append(make("span", "native-settings-section-description", description));
|
|
7987
|
+
const grid = make("div", "native-settings-grid");
|
|
7988
|
+
grid.append(...controls.map((control) => control.field || control));
|
|
7989
|
+
section.append(summary, grid);
|
|
7990
|
+
return section;
|
|
7991
|
+
}
|
|
7992
|
+
|
|
7993
|
+
function nativeSettingsNote(title, text) {
|
|
7994
|
+
const note = make("div", "native-settings-note");
|
|
7995
|
+
note.append(make("strong", undefined, title));
|
|
7996
|
+
note.append(make("span", undefined, text));
|
|
7997
|
+
return note;
|
|
7998
|
+
}
|
|
7999
|
+
|
|
8000
|
+
function currentHttpIdleTimeoutValue(settings) {
|
|
8001
|
+
return String(settings.httpIdleTimeoutMs ?? "300000");
|
|
8002
|
+
}
|
|
8003
|
+
|
|
8004
|
+
function httpIdleTimeoutOptions(settings) {
|
|
8005
|
+
const current = currentHttpIdleTimeoutValue(settings);
|
|
8006
|
+
if (SETTINGS_HTTP_IDLE_TIMEOUT_OPTIONS.some((option) => option.value === current)) return SETTINGS_HTTP_IDLE_TIMEOUT_OPTIONS;
|
|
8007
|
+
const label = Number(current) === 0 ? "disabled (current)" : `${Number(current) / 1000} sec (current)`;
|
|
8008
|
+
return [{ value: current, label }, ...SETTINGS_HTTP_IDLE_TIMEOUT_OPTIONS];
|
|
8009
|
+
}
|
|
8010
|
+
|
|
8011
|
+
function collectNativeSettingsPayload(controls) {
|
|
8012
|
+
return {
|
|
8013
|
+
transport: controls.transport.select.value,
|
|
8014
|
+
httpIdleTimeoutMs: Number(controls.httpIdleTimeout.select.value),
|
|
8015
|
+
autoResizeImages: controls.autoResizeImages.input.checked,
|
|
8016
|
+
blockImages: controls.blockImages.input.checked,
|
|
8017
|
+
enableSkillCommands: controls.skillCommands.input.checked,
|
|
8018
|
+
hideThinkingBlock: !controls.thinkingOutput.input.checked,
|
|
8019
|
+
showImages: controls.showImages.input.checked,
|
|
8020
|
+
imageWidthCells: Number(controls.imageWidth.select.value),
|
|
8021
|
+
collapseChangelog: controls.collapseChangelog.input.checked,
|
|
8022
|
+
quietStartup: controls.quietStartup.input.checked,
|
|
8023
|
+
enableInstallTelemetry: controls.installTelemetry.input.checked,
|
|
8024
|
+
doubleEscapeAction: controls.doubleEscape.select.value,
|
|
8025
|
+
treeFilterMode: controls.treeFilter.select.value,
|
|
8026
|
+
showHardwareCursor: controls.hardwareCursor.input.checked,
|
|
8027
|
+
editorPaddingX: Number(controls.editorPadding.select.value),
|
|
8028
|
+
autocompleteMaxVisible: Number(controls.autocompleteMax.select.value),
|
|
8029
|
+
clearOnShrink: controls.clearOnShrink.input.checked,
|
|
8030
|
+
showTerminalProgress: controls.terminalProgress.input.checked,
|
|
8031
|
+
warnings: { anthropicExtraUsage: controls.anthropicWarning.input.checked },
|
|
8032
|
+
};
|
|
8033
|
+
}
|
|
8034
|
+
|
|
8035
|
+
function nativeSettingsChangedMessage(response, reloadRequested) {
|
|
8036
|
+
const changed = response.data?.changed || [];
|
|
8037
|
+
const reloadRecommended = response.data?.reloadRecommended || [];
|
|
8038
|
+
if (response.data?.reloaded) return `Settings updated and tab reloaded (${changed.length || 0} changed).`;
|
|
8039
|
+
if (reloadRecommended.length) return `Settings updated. Reload tab to apply: ${reloadRecommended.join(", ")}.`;
|
|
8040
|
+
if (reloadRequested) return `Settings updated. No reload-needed changes were detected.`;
|
|
8041
|
+
return `Settings updated${changed.length ? ` (${changed.length} changed)` : ""}.`;
|
|
8042
|
+
}
|
|
8043
|
+
|
|
8044
|
+
async function openNativeSettingsDialog() {
|
|
8045
|
+
openNativeCommandDialog({ title: "/settings", message: "Pi settings for this Web UI tab. Badges show whether changes apply now, in the browser, or after reloading the tab." });
|
|
8046
|
+
renderNativeLoading("Loading settings…");
|
|
8047
|
+
let settingsData;
|
|
8048
|
+
try {
|
|
8049
|
+
const response = await nativeCommandApi("/api/settings");
|
|
8050
|
+
settingsData = response.data || {};
|
|
8051
|
+
} catch (error) {
|
|
8052
|
+
setNativeCommandError(error.message || String(error));
|
|
8053
|
+
elements.nativeCommandBody.replaceChildren();
|
|
8054
|
+
return;
|
|
8055
|
+
}
|
|
8056
|
+
|
|
7704
8057
|
const state = currentState || {};
|
|
7705
|
-
const
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
{
|
|
7720
|
-
{
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
8058
|
+
const settings = settingsData.settings || {};
|
|
8059
|
+
applyNativeSettingsForBrowser(settings);
|
|
8060
|
+
|
|
8061
|
+
const controls = {
|
|
8062
|
+
thinking: nativeSettingSelect("Thinking level", state.thinkingLevel || "off", SETTINGS_THINKING_OPTIONS, "Reasoning depth for thinking-capable models.", { label: "now", tone: "now" }),
|
|
8063
|
+
autoCompact: nativeSettingToggle("Auto-compact", state.autoCompactionEnabled !== false, "Let Pi compact when context is nearly full.", { label: "now", tone: "now" }),
|
|
8064
|
+
steering: nativeSettingSelect("Steering mode", state.steeringMode || "one-at-a-time", [
|
|
8065
|
+
{ value: "one-at-a-time", label: "one at a time" },
|
|
8066
|
+
{ value: "all", label: "all queued" },
|
|
8067
|
+
], "How Enter messages are delivered while the agent is streaming.", { label: "now", tone: "now" }),
|
|
8068
|
+
followUp: nativeSettingSelect("Follow-up mode", state.followUpMode || "one-at-a-time", [
|
|
8069
|
+
{ value: "one-at-a-time", label: "one at a time" },
|
|
8070
|
+
{ value: "all", label: "all queued" },
|
|
8071
|
+
], "How queued follow-ups are delivered after the current response.", { label: "now", tone: "now" }),
|
|
8072
|
+
transport: nativeSettingSelect("Transport", settings.transport || "auto", SETTINGS_TRANSPORT_OPTIONS, "Preferred provider transport when multiple transports are supported.", { label: "reload", tone: "reload" }),
|
|
8073
|
+
httpIdleTimeout: nativeSettingSelect("HTTP idle timeout", currentHttpIdleTimeoutValue(settings), httpIdleTimeoutOptions(settings), "Maximum idle gap while waiting for provider HTTP data.", { label: "reload", tone: "reload" }),
|
|
8074
|
+
busyBehavior: nativeSettingSelect("Busy prompt behavior", elements.busyBehavior.value || "followUp", [
|
|
8075
|
+
{ value: "followUp", label: "follow-up" },
|
|
8076
|
+
{ value: "steer", label: "steer" },
|
|
8077
|
+
], "When you submit a normal prompt while a tab is already busy.", { label: "browser", tone: "browser" }),
|
|
8078
|
+
thinkingOutput: nativeSettingToggle("Show thinking output", settings.hideThinkingBlock !== true, "Browser transcript visibility; also writes Pi's hide-thinking setting.", { label: "browser", tone: "browser" }),
|
|
8079
|
+
doneNotifications: nativeSettingToggle("Agent done notifications", agentDoneNotificationsEnabled, "Browser notification after background tab work completes.", { label: "browser", tone: "browser" }),
|
|
8080
|
+
autocompleteMax: nativeSettingSelect("Autocomplete max items", settings.autocompleteMaxVisible ?? autocompleteMaxVisible, SETTINGS_AUTOCOMPLETE_OPTIONS, "Maximum visible slash/path suggestions.", { label: "browser", tone: "browser" }),
|
|
8081
|
+
doubleEscape: nativeSettingSelect("Double-escape action", settings.doubleEscapeAction || doubleEscapeAction, SETTINGS_DOUBLE_ESCAPE_OPTIONS, "Action when pressing Escape twice with an empty composer.", { label: "browser", tone: "browser" }),
|
|
8082
|
+
treeFilter: nativeSettingSelect("Tree filter mode", settings.treeFilterMode || treeFilterMode, SETTINGS_TREE_FILTER_OPTIONS, "Default filter when opening /tree.", { label: "browser", tone: "browser" }),
|
|
8083
|
+
autoResizeImages: nativeSettingToggle("Auto-resize images", settings.autoResizeImages !== false, "Resize large images to 2000x2000 max for better model compatibility.", { label: "reload", tone: "reload" }),
|
|
8084
|
+
blockImages: nativeSettingToggle("Block images", settings.blockImages === true, "Prevent images from being sent to LLM providers.", { label: "reload", tone: "reload" }),
|
|
8085
|
+
showImages: nativeSettingToggle("Show terminal images", settings.showImages !== false, "Native TUI inline image rendering preference.", { label: "TUI", tone: "tui" }),
|
|
8086
|
+
imageWidth: nativeSettingSelect("Terminal image width", settings.imageWidthCells || 60, SETTINGS_IMAGE_WIDTH_OPTIONS, "Native TUI inline image width in terminal cells.", { label: "TUI", tone: "tui" }),
|
|
8087
|
+
skillCommands: nativeSettingToggle("Skill commands", settings.enableSkillCommands !== false, "Register skills as /skill:name commands.", { label: "reload", tone: "reload" }),
|
|
8088
|
+
anthropicWarning: nativeSettingToggle("Anthropic extra usage warning", settings.warnings?.anthropicExtraUsage !== false, "Warn when Anthropic subscription auth may use paid extra usage.", { label: "safety", tone: "safety" }),
|
|
8089
|
+
collapseChangelog: nativeSettingToggle("Collapse changelog", settings.collapseChangelog === true, "Show condensed changelog after updates.", { label: "startup", tone: "startup" }),
|
|
8090
|
+
quietStartup: nativeSettingToggle("Quiet startup", settings.quietStartup === true, "Disable verbose printing at startup.", { label: "startup", tone: "startup" }),
|
|
8091
|
+
installTelemetry: nativeSettingToggle("Install telemetry", settings.enableInstallTelemetry !== false, "Send anonymous version/update ping after changelog-detected updates.", { label: "startup", tone: "startup" }),
|
|
8092
|
+
hardwareCursor: nativeSettingToggle("Show hardware cursor", settings.showHardwareCursor === true, "Native TUI cursor display for IME support.", { label: "TUI", tone: "tui" }),
|
|
8093
|
+
editorPadding: nativeSettingSelect("Editor padding", settings.editorPaddingX ?? 0, SETTINGS_EDITOR_PADDING_OPTIONS, "Native TUI horizontal input padding.", { label: "TUI", tone: "tui" }),
|
|
8094
|
+
clearOnShrink: nativeSettingToggle("Clear on shrink", settings.clearOnShrink === true, "Native TUI row clearing when content shrinks; may flicker.", { label: "TUI", tone: "tui" }),
|
|
8095
|
+
terminalProgress: nativeSettingToggle("Terminal progress", settings.showTerminalProgress === true, "Native TUI OSC 9;4 terminal progress indicators.", { label: "TUI", tone: "tui" }),
|
|
8096
|
+
};
|
|
8097
|
+
|
|
8098
|
+
const body = make("div", "native-settings-panel");
|
|
8099
|
+
body.append(
|
|
8100
|
+
nativeSettingsNote("Scopes", "Runtime settings apply to the active tab. Reload-badged settings are saved globally and need a tab reload for the running Pi process."),
|
|
8101
|
+
nativeSettingsSection("Runtime", "Model behavior and request transport.", [controls.thinking, controls.autoCompact, controls.steering, controls.followUp, controls.transport, controls.httpIdleTimeout], { open: true }),
|
|
8102
|
+
nativeSettingsSection("Browser workflow", "Local Web UI behavior plus shared composer defaults.", [controls.busyBehavior, controls.thinkingOutput, controls.doneNotifications, controls.autocompleteMax, controls.doubleEscape, controls.treeFilter], { open: true }),
|
|
8103
|
+
nativeSettingsSection("Images", "Provider image policy and native terminal image display.", [controls.autoResizeImages, controls.blockImages, controls.showImages, controls.imageWidth], { open: true }),
|
|
8104
|
+
nativeSettingsSection("Startup & safety", "Command registration, warnings, update/startup behavior.", [controls.skillCommands, controls.anthropicWarning, controls.collapseChangelog, controls.quietStartup, controls.installTelemetry], { open: false }),
|
|
8105
|
+
nativeSettingsSection("Native TUI advanced", "Saved for the terminal UI; mostly informational in the browser.", [controls.hardwareCursor, controls.editorPadding, controls.clearOnShrink, controls.terminalProgress], { open: false })
|
|
8106
|
+
);
|
|
8107
|
+
elements.nativeCommandBody.replaceChildren(body);
|
|
7724
8108
|
elements.nativeCommandActions.replaceChildren();
|
|
7725
8109
|
addNativeCommandAction("Model…", () => openNativeModelSelector());
|
|
7726
8110
|
addNativeCommandAction("Theme…", () => openNativeThemeSelector());
|
|
7727
8111
|
addNativeCommandAction("Cancel", closeNativeCommandDialog);
|
|
7728
|
-
|
|
7729
|
-
|
|
8112
|
+
|
|
8113
|
+
const applySettings = async (reload, button) => {
|
|
8114
|
+
setNativeActionBusy(button, true, reload ? "Applying & reloading…" : "Applying…");
|
|
7730
8115
|
setNativeCommandError("");
|
|
7731
8116
|
try {
|
|
7732
8117
|
const requests = [];
|
|
7733
|
-
|
|
7734
|
-
if (
|
|
7735
|
-
if (
|
|
7736
|
-
if (
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
if (
|
|
8118
|
+
const thinkingLevelChanged = controls.thinking.select.value !== (state.thinkingLevel || "off");
|
|
8119
|
+
if (thinkingLevelChanged) requests.push(nativeCommandApi("/api/thinking", { method: "POST", body: { level: controls.thinking.select.value } }));
|
|
8120
|
+
if (controls.steering.select.value !== (state.steeringMode || "one-at-a-time")) requests.push(nativeCommandApi("/api/steering-mode", { method: "POST", body: { mode: controls.steering.select.value } }));
|
|
8121
|
+
if (controls.followUp.select.value !== (state.followUpMode || "one-at-a-time")) requests.push(nativeCommandApi("/api/follow-up-mode", { method: "POST", body: { mode: controls.followUp.select.value } }));
|
|
8122
|
+
if (controls.autoCompact.input.checked !== (state.autoCompactionEnabled !== false)) requests.push(nativeCommandApi("/api/auto-compaction", { method: "POST", body: { enabled: controls.autoCompact.input.checked } }));
|
|
8123
|
+
elements.busyBehavior.value = controls.busyBehavior.select.value;
|
|
8124
|
+
if (controls.thinkingOutput.input.checked !== thinkingOutputVisible) setThinkingOutputVisible(controls.thinkingOutput.input.checked);
|
|
8125
|
+
if (controls.doneNotifications.input.checked !== agentDoneNotificationsEnabled) await setAgentDoneNotificationsEnabled(controls.doneNotifications.input.checked);
|
|
7740
8126
|
await Promise.all(requests);
|
|
7741
|
-
|
|
8127
|
+
const response = await nativeCommandApi("/api/settings", { method: "POST", body: { settings: collectNativeSettingsPayload(controls), reload } });
|
|
8128
|
+
applyResponseTab(response);
|
|
8129
|
+
applyNativeSettingsForBrowser(response.data?.settings || collectNativeSettingsPayload(controls));
|
|
8130
|
+
if (thinkingLevelChanged) requestGitFooterWebuiPayload(activeTabContext(), { force: true });
|
|
8131
|
+
addTransientMessage({ role: "native", title: "/settings", content: nativeSettingsChangedMessage(response, reload), level: response.data?.reloadRecommended?.length && !response.data?.reloaded ? "warn" : "info" });
|
|
7742
8132
|
closeNativeCommandDialog();
|
|
7743
|
-
await
|
|
8133
|
+
await refreshAll();
|
|
7744
8134
|
} catch (error) {
|
|
7745
8135
|
setNativeCommandError(error.message || String(error));
|
|
7746
8136
|
} finally {
|
|
7747
|
-
setNativeActionBusy(
|
|
8137
|
+
setNativeActionBusy(button, false);
|
|
7748
8138
|
}
|
|
7749
|
-
}
|
|
8139
|
+
};
|
|
8140
|
+
|
|
8141
|
+
const reloadButton = addNativeCommandAction("Apply & reload tab", () => applySettings(true, reloadButton));
|
|
8142
|
+
const save = addNativeCommandAction("Apply", () => applySettings(false, save), "primary");
|
|
7750
8143
|
}
|
|
7751
8144
|
|
|
7752
8145
|
async function openNativeForkSelector() {
|
|
@@ -7811,6 +8204,41 @@ function openNativeCloneDialog() {
|
|
|
7811
8204
|
}, "primary");
|
|
7812
8205
|
}
|
|
7813
8206
|
|
|
8207
|
+
function openNativeNameDialog() {
|
|
8208
|
+
openNativeCommandDialog({ title: "/name", message: "Set the session and browser tab name." });
|
|
8209
|
+
const field = make("label", "native-settings-field");
|
|
8210
|
+
field.append(make("span", "native-settings-label", "Session name"));
|
|
8211
|
+
const input = make("input", "dialog-input");
|
|
8212
|
+
input.type = "text";
|
|
8213
|
+
input.autocomplete = "off";
|
|
8214
|
+
input.placeholder = "New session name";
|
|
8215
|
+
input.value = activeTab()?.title || "";
|
|
8216
|
+
field.append(input);
|
|
8217
|
+
elements.nativeCommandBody.append(field);
|
|
8218
|
+
elements.nativeCommandActions.replaceChildren();
|
|
8219
|
+
addNativeCommandAction("Cancel", closeNativeCommandDialog);
|
|
8220
|
+
const save = addNativeCommandAction("Name session", async () => {
|
|
8221
|
+
const name = input.value.trim();
|
|
8222
|
+
if (!name) {
|
|
8223
|
+
setNativeCommandError("Enter a session name.");
|
|
8224
|
+
input.focus();
|
|
8225
|
+
return;
|
|
8226
|
+
}
|
|
8227
|
+
setNativeActionBusy(save, true, "Saving…");
|
|
8228
|
+
closeNativeCommandDialog();
|
|
8229
|
+
await sendPrompt("prompt", `/name ${name}`);
|
|
8230
|
+
}, "primary");
|
|
8231
|
+
input.addEventListener("keydown", (event) => {
|
|
8232
|
+
if (event.key !== "Enter") return;
|
|
8233
|
+
event.preventDefault();
|
|
8234
|
+
save.click();
|
|
8235
|
+
});
|
|
8236
|
+
queueMicrotask(() => {
|
|
8237
|
+
input.focus();
|
|
8238
|
+
input.select();
|
|
8239
|
+
});
|
|
8240
|
+
}
|
|
8241
|
+
|
|
7814
8242
|
async function openNativeResumeSelector(scope = "current") {
|
|
7815
8243
|
openNativeCommandDialog({ title: "/resume", message: "Resume another persisted Pi session.", searchPlaceholder: "Filter sessions…" });
|
|
7816
8244
|
renderNativeLoading("Loading sessions…");
|
|
@@ -7852,21 +8280,39 @@ async function openNativeResumeSelector(scope = "current") {
|
|
|
7852
8280
|
}
|
|
7853
8281
|
}
|
|
7854
8282
|
|
|
8283
|
+
function nativeTreeFilterMatches(node, filter) {
|
|
8284
|
+
const settingsTypes = new Set(["label", "custom", "custom_message", "model_change", "thinking_level_change", "session_info"]);
|
|
8285
|
+
switch (filter) {
|
|
8286
|
+
case "user-only":
|
|
8287
|
+
return node.type === "message" && node.role === "user";
|
|
8288
|
+
case "no-tools":
|
|
8289
|
+
return !settingsTypes.has(node.type) && !(node.type === "message" && node.role === "toolResult");
|
|
8290
|
+
case "labeled-only":
|
|
8291
|
+
return node.label !== undefined;
|
|
8292
|
+
case "all":
|
|
8293
|
+
return true;
|
|
8294
|
+
default:
|
|
8295
|
+
return !settingsTypes.has(node.type);
|
|
8296
|
+
}
|
|
8297
|
+
}
|
|
8298
|
+
|
|
7855
8299
|
async function openNativeTreeSelector() {
|
|
7856
8300
|
openNativeCommandDialog({ title: "/tree", message: "Navigate the current session tree. Choosing a user message restores it into the editor.", searchPlaceholder: "Filter tree…" });
|
|
7857
8301
|
renderNativeLoading("Loading session tree…");
|
|
7858
8302
|
try {
|
|
7859
8303
|
const response = await nativeCommandApi("/api/session-tree");
|
|
7860
8304
|
const nodes = response.data?.nodes || [];
|
|
8305
|
+
let selectedFilter = treeFilterMode || "default";
|
|
8306
|
+
const filterField = nativeSettingSelect("Filter", selectedFilter, SETTINGS_TREE_FILTER_OPTIONS, "Temporary filter for this tree view.");
|
|
7861
8307
|
const summarize = nativeSettingToggle("Summarize abandoned branch", false, "Optional; may call the active model before switching branches.");
|
|
7862
8308
|
const labelField = make("label", "native-settings-field");
|
|
7863
|
-
labelField.append(
|
|
8309
|
+
labelField.append(nativeSettingsLabelRow("Optional label"));
|
|
7864
8310
|
const labelInput = make("input", "dialog-input");
|
|
7865
8311
|
labelInput.placeholder = "checkpoint label";
|
|
7866
8312
|
labelField.append(labelInput);
|
|
7867
8313
|
const options = make("div", "native-tree-options");
|
|
7868
|
-
options.append(summarize.field, labelField);
|
|
7869
|
-
const
|
|
8314
|
+
options.append(filterField.field, summarize.field, labelField);
|
|
8315
|
+
const toItems = () => nodes.filter((node) => nativeTreeFilterMatches(node, selectedFilter)).map((node) => ({
|
|
7870
8316
|
id: node.id,
|
|
7871
8317
|
label: `${node.title}${node.label ? ` · ${node.label}` : ""}`,
|
|
7872
8318
|
description: node.text || "",
|
|
@@ -7895,9 +8341,13 @@ async function openNativeTreeSelector() {
|
|
|
7895
8341
|
}
|
|
7896
8342
|
};
|
|
7897
8343
|
const render = () => {
|
|
7898
|
-
renderNativeSelectorItems(
|
|
8344
|
+
renderNativeSelectorItems(toItems(), { emptyText: "No session tree entries match this filter.", onSelect: navigate });
|
|
7899
8345
|
elements.nativeCommandBody.prepend(options);
|
|
7900
8346
|
};
|
|
8347
|
+
filterField.select.addEventListener("change", () => {
|
|
8348
|
+
selectedFilter = filterField.select.value;
|
|
8349
|
+
render();
|
|
8350
|
+
});
|
|
7901
8351
|
elements.nativeCommandSearch.oninput = render;
|
|
7902
8352
|
render();
|
|
7903
8353
|
} catch (error) {
|
|
@@ -8094,7 +8544,7 @@ async function handleNativeSlashSelectorCommand(message, { usesPromptInput = fal
|
|
|
8094
8544
|
await openNativeModelSelector();
|
|
8095
8545
|
return true;
|
|
8096
8546
|
case "settings":
|
|
8097
|
-
openNativeSettingsDialog();
|
|
8547
|
+
await openNativeSettingsDialog();
|
|
8098
8548
|
return true;
|
|
8099
8549
|
case "theme":
|
|
8100
8550
|
openNativeThemeSelector();
|
|
@@ -8105,6 +8555,9 @@ async function handleNativeSlashSelectorCommand(message, { usesPromptInput = fal
|
|
|
8105
8555
|
case "clone":
|
|
8106
8556
|
openNativeCloneDialog();
|
|
8107
8557
|
return true;
|
|
8558
|
+
case "name":
|
|
8559
|
+
openNativeNameDialog();
|
|
8560
|
+
return true;
|
|
8108
8561
|
case "resume":
|
|
8109
8562
|
await openNativeResumeSelector();
|
|
8110
8563
|
return true;
|
|
@@ -8335,15 +8788,26 @@ function handleMessageUpdate(event) {
|
|
|
8335
8788
|
}
|
|
8336
8789
|
}
|
|
8337
8790
|
|
|
8791
|
+
function modelStateKey(model) {
|
|
8792
|
+
return model ? `${model.provider || ""}/${model.id || ""}` : "";
|
|
8793
|
+
}
|
|
8794
|
+
|
|
8795
|
+
function gitFooterRelevantStateChanged(previousState, nextState) {
|
|
8796
|
+
if (!previousState || !nextState) return false;
|
|
8797
|
+
return previousState.thinkingLevel !== nextState.thinkingLevel || modelStateKey(previousState.model) !== modelStateKey(nextState.model);
|
|
8798
|
+
}
|
|
8799
|
+
|
|
8338
8800
|
async function refreshState(tabContext = activeTabContext()) {
|
|
8339
8801
|
if (!tabContext.tabId) return;
|
|
8340
8802
|
const response = await api("/api/state", { tabId: tabContext.tabId });
|
|
8341
8803
|
if (!isCurrentTabContext(tabContext)) return;
|
|
8804
|
+
const previousState = currentState;
|
|
8342
8805
|
currentState = response.data || null;
|
|
8806
|
+
const shouldRefreshGitFooter = gitFooterRelevantStateChanged(previousState, currentState);
|
|
8343
8807
|
syncActiveTabActivityFromState(currentState);
|
|
8344
8808
|
syncRunIndicatorFromState(currentState);
|
|
8345
8809
|
renderStatus();
|
|
8346
|
-
requestGitFooterWebuiPayload(tabContext);
|
|
8810
|
+
requestGitFooterWebuiPayload(tabContext, { force: shouldRefreshGitFooter });
|
|
8347
8811
|
}
|
|
8348
8812
|
|
|
8349
8813
|
async function refreshStats(tabContext = activeTabContext()) {
|
|
@@ -8602,10 +9066,23 @@ function getCommandMatches(query) {
|
|
|
8602
9066
|
.map((command) => ({ command, score: scoreCommandSuggestion(command, query) }))
|
|
8603
9067
|
.filter((item) => Number.isFinite(item.score))
|
|
8604
9068
|
.sort((a, b) => a.score - b.score || a.command.name.localeCompare(b.command.name))
|
|
8605
|
-
.slice(0,
|
|
9069
|
+
.slice(0, clampAutocompleteMaxVisible(autocompleteMaxVisible))
|
|
8606
9070
|
.map((item) => item.command);
|
|
8607
9071
|
}
|
|
8608
9072
|
|
|
9073
|
+
function commandSearchQuery() {
|
|
9074
|
+
return String(elements.commandSearchInput?.value || "").trim().replace(/^\/+/, "").toLowerCase();
|
|
9075
|
+
}
|
|
9076
|
+
|
|
9077
|
+
function commandMatchesSearch(command, query) {
|
|
9078
|
+
if (!query) return true;
|
|
9079
|
+
return [command.name, command.description, command.source, command.location]
|
|
9080
|
+
.filter(Boolean)
|
|
9081
|
+
.join(" ")
|
|
9082
|
+
.toLowerCase()
|
|
9083
|
+
.includes(query);
|
|
9084
|
+
}
|
|
9085
|
+
|
|
8609
9086
|
function activeSuggestionCount() {
|
|
8610
9087
|
return suggestionMode === "path" ? pathSuggestions.length : commandSuggestions.length;
|
|
8611
9088
|
}
|
|
@@ -8877,8 +9354,16 @@ function renderCommands() {
|
|
|
8877
9354
|
hideCommandSuggestions();
|
|
8878
9355
|
return;
|
|
8879
9356
|
}
|
|
9357
|
+
const searchQuery = commandSearchQuery();
|
|
9358
|
+
const filteredCommands = commandsToShow.filter((command) => commandMatchesSearch(command, searchQuery));
|
|
9359
|
+
if (!filteredCommands.length) {
|
|
9360
|
+
elements.commandsBox.textContent = `No commands match “${elements.commandSearchInput?.value.trim() || searchQuery}”.`;
|
|
9361
|
+
elements.commandsBox.classList.add("muted");
|
|
9362
|
+
renderCommandSuggestions();
|
|
9363
|
+
return;
|
|
9364
|
+
}
|
|
8880
9365
|
elements.commandsBox.classList.remove("muted");
|
|
8881
|
-
for (const command of
|
|
9366
|
+
for (const command of filteredCommands.slice(0, 80)) {
|
|
8882
9367
|
const item = make("button", "command-item");
|
|
8883
9368
|
item.type = "button";
|
|
8884
9369
|
item.title = `Send /${command.name}`;
|
|
@@ -8912,6 +9397,7 @@ async function refreshAll(tabContext = activeTabContext()) {
|
|
|
8912
9397
|
refreshCommands(tabContext),
|
|
8913
9398
|
refreshStats(tabContext),
|
|
8914
9399
|
refreshWorkspace(tabContext),
|
|
9400
|
+
refreshNativeSettings(tabContext),
|
|
8915
9401
|
refreshNetworkStatus(),
|
|
8916
9402
|
refreshWebuiVersion(),
|
|
8917
9403
|
]);
|
|
@@ -9162,6 +9648,7 @@ async function cycleModelFromShortcut(direction = "forward") {
|
|
|
9162
9648
|
const model = response.data?.model;
|
|
9163
9649
|
const scope = response.data?.scoped ? `scoped (${response.data.scopeSource})` : "all models";
|
|
9164
9650
|
if (isCurrentTabContext(tabContext)) {
|
|
9651
|
+
applyOptimisticModelSelection(model, tabContext);
|
|
9165
9652
|
addTransientMessage({ role: "native", title: "model cycle", content: `Model set to ${appShortcutModelLabel(model)} via ${direction} cycle over ${scope}.`, level: "info" });
|
|
9166
9653
|
await Promise.allSettled([refreshState(tabContext), refreshModels(tabContext), refreshStats(tabContext)]);
|
|
9167
9654
|
}
|
|
@@ -9178,9 +9665,10 @@ async function cycleThinkingFromShortcut() {
|
|
|
9178
9665
|
if (!tabContext.tabId) return;
|
|
9179
9666
|
try {
|
|
9180
9667
|
const response = await api("/api/thinking-cycle", { method: "POST", body: {}, tabId: tabContext.tabId });
|
|
9181
|
-
if (response.data?.level && currentState) currentState = { ...currentState, thinkingLevel: response.data.level };
|
|
9182
9668
|
if (isCurrentTabContext(tabContext)) {
|
|
9669
|
+
applyOptimisticThinkingSelection(response.data, tabContext);
|
|
9183
9670
|
addTransientMessage({ role: "native", title: "thinking", content: response.data?.level ? `Thinking level: ${response.data.level}` : "Thinking level did not change.", level: "info" });
|
|
9671
|
+
if (response.data?.level) requestGitFooterWebuiPayload(tabContext, { force: true });
|
|
9184
9672
|
await Promise.allSettled([refreshState(tabContext), refreshStats(tabContext)]);
|
|
9185
9673
|
}
|
|
9186
9674
|
} catch (error) {
|
|
@@ -9802,7 +10290,13 @@ function handleEvent(event) {
|
|
|
9802
10290
|
syncRunIndicatorFromState(currentState);
|
|
9803
10291
|
renderStatus();
|
|
9804
10292
|
} else if (["set_model", "cycle_model", "set_thinking_level", "cycle_thinking_level", "new_session", "compact"].includes(event.command)) {
|
|
9805
|
-
if (event.command === "
|
|
10293
|
+
if (event.command === "set_model") {
|
|
10294
|
+
applyOptimisticModelSelection(event.data, tabContext);
|
|
10295
|
+
} else if (event.command === "cycle_model") {
|
|
10296
|
+
applyOptimisticModelSelection(event.data?.model, tabContext);
|
|
10297
|
+
} else if (event.command === "set_thinking_level" || event.command === "cycle_thinking_level") {
|
|
10298
|
+
applyOptimisticThinkingSelection(event.data, tabContext);
|
|
10299
|
+
} else if (event.command === "new_session") {
|
|
9806
10300
|
const tabId = event.tabId || activeTabId;
|
|
9807
10301
|
forgetLastUserPrompt(tabId);
|
|
9808
10302
|
resetGitWorkflowForTab(tabId);
|
|
@@ -9840,6 +10334,7 @@ function connectEvents(tabContext = activeTabContext()) {
|
|
|
9840
10334
|
|
|
9841
10335
|
elements.copyServerCommandButton?.addEventListener("click", copyServerStartCommand);
|
|
9842
10336
|
elements.retryServerConnectionButton?.addEventListener("click", retryServerConnection);
|
|
10337
|
+
elements.commandSearchInput?.addEventListener("input", renderCommands);
|
|
9843
10338
|
elements.sendFeedbackButton.addEventListener("click", () => submitQueuedActionFeedback());
|
|
9844
10339
|
elements.composer.addEventListener("submit", (event) => {
|
|
9845
10340
|
event.preventDefault();
|
|
@@ -9862,15 +10357,18 @@ elements.gitWorkflowButton.addEventListener("click", () => {
|
|
|
9862
10357
|
const publishMenuContainer = elements.publishButton.parentElement;
|
|
9863
10358
|
elements.publishButton.addEventListener("click", () => {
|
|
9864
10359
|
setNativeCommandMenuOpen(false);
|
|
10360
|
+
setOptionsMenuOpen(false);
|
|
9865
10361
|
setPublishMenuOpen(true);
|
|
9866
10362
|
});
|
|
9867
10363
|
publishMenuContainer?.addEventListener("pointerenter", () => {
|
|
9868
10364
|
setNativeCommandMenuOpen(false);
|
|
10365
|
+
setOptionsMenuOpen(false);
|
|
9869
10366
|
setPublishMenuOpen(true);
|
|
9870
10367
|
});
|
|
9871
10368
|
publishMenuContainer?.addEventListener("pointerleave", () => setPublishMenuOpen(false));
|
|
9872
10369
|
publishMenuContainer?.addEventListener("focusin", () => {
|
|
9873
10370
|
setNativeCommandMenuOpen(false);
|
|
10371
|
+
setOptionsMenuOpen(false);
|
|
9874
10372
|
setPublishMenuOpen(true);
|
|
9875
10373
|
});
|
|
9876
10374
|
publishMenuContainer?.addEventListener("focusout", () => {
|
|
@@ -9881,15 +10379,18 @@ publishMenuContainer?.addEventListener("focusout", () => {
|
|
|
9881
10379
|
const nativeCommandMenuContainer = elements.nativeCommandMenuButton.parentElement;
|
|
9882
10380
|
elements.nativeCommandMenuButton.addEventListener("click", () => {
|
|
9883
10381
|
setPublishMenuOpen(false);
|
|
10382
|
+
setOptionsMenuOpen(false);
|
|
9884
10383
|
setNativeCommandMenuOpen(true);
|
|
9885
10384
|
});
|
|
9886
10385
|
nativeCommandMenuContainer?.addEventListener("pointerenter", () => {
|
|
9887
10386
|
setPublishMenuOpen(false);
|
|
10387
|
+
setOptionsMenuOpen(false);
|
|
9888
10388
|
setNativeCommandMenuOpen(true);
|
|
9889
10389
|
});
|
|
9890
10390
|
nativeCommandMenuContainer?.addEventListener("pointerleave", () => setNativeCommandMenuOpen(false));
|
|
9891
10391
|
nativeCommandMenuContainer?.addEventListener("focusin", () => {
|
|
9892
10392
|
setPublishMenuOpen(false);
|
|
10393
|
+
setOptionsMenuOpen(false);
|
|
9893
10394
|
setNativeCommandMenuOpen(true);
|
|
9894
10395
|
});
|
|
9895
10396
|
nativeCommandMenuContainer?.addEventListener("focusout", () => {
|
|
@@ -9897,10 +10398,40 @@ nativeCommandMenuContainer?.addEventListener("focusout", () => {
|
|
|
9897
10398
|
if (!nativeCommandMenuContainer?.contains(document.activeElement)) setNativeCommandMenuOpen(false);
|
|
9898
10399
|
}, 0);
|
|
9899
10400
|
});
|
|
10401
|
+
const optionsMenuContainer = elements.optionsMenuButton.parentElement;
|
|
10402
|
+
elements.optionsMenuButton.addEventListener("click", () => {
|
|
10403
|
+
setPublishMenuOpen(false);
|
|
10404
|
+
setNativeCommandMenuOpen(false);
|
|
10405
|
+
setOptionsMenuOpen(true);
|
|
10406
|
+
});
|
|
10407
|
+
optionsMenuContainer?.addEventListener("pointerenter", () => {
|
|
10408
|
+
setPublishMenuOpen(false);
|
|
10409
|
+
setNativeCommandMenuOpen(false);
|
|
10410
|
+
setOptionsMenuOpen(true);
|
|
10411
|
+
});
|
|
10412
|
+
optionsMenuContainer?.addEventListener("pointerleave", () => setOptionsMenuOpen(false));
|
|
10413
|
+
optionsMenuContainer?.addEventListener("focusin", () => {
|
|
10414
|
+
setPublishMenuOpen(false);
|
|
10415
|
+
setNativeCommandMenuOpen(false);
|
|
10416
|
+
setOptionsMenuOpen(true);
|
|
10417
|
+
});
|
|
10418
|
+
optionsMenuContainer?.addEventListener("focusout", () => {
|
|
10419
|
+
setTimeout(() => {
|
|
10420
|
+
if (!optionsMenuContainer?.contains(document.activeElement)) setOptionsMenuOpen(false);
|
|
10421
|
+
}, 0);
|
|
10422
|
+
});
|
|
9900
10423
|
elements.releaseNpmButton.addEventListener("click", () => runPublishWorkflow("/release-npm"));
|
|
9901
10424
|
elements.releaseAurButton.addEventListener("click", () => runPublishWorkflow("/release-aur"));
|
|
9902
10425
|
elements.nativeSkillsButton.addEventListener("click", () => runNativeCommandMenu("/skills"));
|
|
9903
10426
|
elements.nativeToolsButton.addEventListener("click", () => runNativeCommandMenu("/tools"));
|
|
10427
|
+
elements.optionsResumeButton.addEventListener("click", () => runNativeCommandMenu("/resume"));
|
|
10428
|
+
elements.optionsReloadButton.addEventListener("click", () => runNativeCommandMenu("/reload"));
|
|
10429
|
+
elements.optionsNameButton.addEventListener("click", () => runNativeCommandMenu("/name"));
|
|
10430
|
+
elements.optionsCloneButton.addEventListener("click", () => runNativeCommandMenu("/clone"));
|
|
10431
|
+
elements.optionsSettingsButton.addEventListener("click", () => runNativeCommandMenu("/settings"));
|
|
10432
|
+
elements.optionsExportButton.addEventListener("click", () => runNativeCommandMenu("/export"));
|
|
10433
|
+
elements.optionsForkButton.addEventListener("click", () => runNativeCommandMenu("/fork"));
|
|
10434
|
+
elements.optionsTreeButton.addEventListener("click", () => runNativeCommandMenu("/tree"));
|
|
9904
10435
|
elements.gitWorkflowCancelButton.addEventListener("click", () => cancelGitWorkflow());
|
|
9905
10436
|
elements.nativeCommandDialog.addEventListener("close", () => {
|
|
9906
10437
|
elements.nativeCommandSearch.oninput = null;
|
|
@@ -10020,8 +10551,11 @@ elements.setModelButton.addEventListener("click", async () => {
|
|
|
10020
10551
|
const tabContext = activeTabContext();
|
|
10021
10552
|
try {
|
|
10022
10553
|
const selected = JSON.parse(elements.modelSelect.value);
|
|
10023
|
-
await api("/api/model", { method: "POST", body: selected, tabId: tabContext.tabId });
|
|
10024
|
-
if (isCurrentTabContext(tabContext))
|
|
10554
|
+
const response = await api("/api/model", { method: "POST", body: selected, tabId: tabContext.tabId });
|
|
10555
|
+
if (isCurrentTabContext(tabContext)) {
|
|
10556
|
+
applyOptimisticModelSelection(response.data || selected, tabContext);
|
|
10557
|
+
await refreshState(tabContext);
|
|
10558
|
+
}
|
|
10025
10559
|
} catch (error) {
|
|
10026
10560
|
if (isCurrentTabContext(tabContext)) addEvent(error.message, "error");
|
|
10027
10561
|
}
|
|
@@ -10031,7 +10565,14 @@ elements.setThinkingButton.addEventListener("click", async () => {
|
|
|
10031
10565
|
try {
|
|
10032
10566
|
const response = await api("/api/thinking", { method: "POST", body: { level: elements.thinkingSelect.value }, tabId: tabContext.tabId });
|
|
10033
10567
|
if (isCurrentTabContext(tabContext)) {
|
|
10034
|
-
|
|
10568
|
+
applyOptimisticThinkingSelection(response.data, tabContext);
|
|
10569
|
+
if (response.data?.pending) {
|
|
10570
|
+
addEvent(response.data.message || `Thinking level ${response.data.level} will apply to the next prompt.`, "info");
|
|
10571
|
+
} else if (response.data?.level) {
|
|
10572
|
+
const requested = response.data.requestedLevel;
|
|
10573
|
+
const effective = response.data.level;
|
|
10574
|
+
addEvent(requested && requested !== effective ? `Thinking level set to ${effective} (requested ${requested}).` : `Thinking level set to ${effective}.`, "info");
|
|
10575
|
+
}
|
|
10035
10576
|
await refreshState(tabContext);
|
|
10036
10577
|
}
|
|
10037
10578
|
} catch (error) {
|
|
@@ -10104,11 +10645,15 @@ document.addEventListener("pointerdown", (event) => {
|
|
|
10104
10645
|
if (nativeCommandMenuOpen && !event.target?.closest?.(".composer-native-command-menu")) {
|
|
10105
10646
|
setNativeCommandMenuOpen(false);
|
|
10106
10647
|
}
|
|
10648
|
+
if (optionsMenuOpen && !event.target?.closest?.(".composer-options-menu")) {
|
|
10649
|
+
setOptionsMenuOpen(false);
|
|
10650
|
+
}
|
|
10107
10651
|
if (document.body.classList.contains("mobile-tabs-expanded") && !elements.tabBar.contains(event.target) && !elements.terminalTabsToggleButton.contains(event.target)) {
|
|
10108
10652
|
setMobileTabsExpanded(false);
|
|
10109
10653
|
}
|
|
10110
|
-
if (
|
|
10654
|
+
if (isFooterPickerOpen() && !elements.statusBar.contains(event.target)) {
|
|
10111
10655
|
setFooterModelPickerOpen(false);
|
|
10656
|
+
setFooterThinkingPickerOpen(false);
|
|
10112
10657
|
}
|
|
10113
10658
|
}, { passive: true });
|
|
10114
10659
|
document.addEventListener("pointermove", (event) => {
|
|
@@ -10194,6 +10739,10 @@ window.addEventListener("keydown", (event) => {
|
|
|
10194
10739
|
setNativeCommandMenuOpen(false);
|
|
10195
10740
|
return;
|
|
10196
10741
|
}
|
|
10742
|
+
if (optionsMenuOpen) {
|
|
10743
|
+
setOptionsMenuOpen(false);
|
|
10744
|
+
return;
|
|
10745
|
+
}
|
|
10197
10746
|
if (document.body.classList.contains("composer-actions-open")) {
|
|
10198
10747
|
setComposerActionsOpen(false);
|
|
10199
10748
|
return;
|
|
@@ -10202,14 +10751,25 @@ window.addEventListener("keydown", (event) => {
|
|
|
10202
10751
|
setMobileTabsExpanded(false);
|
|
10203
10752
|
return;
|
|
10204
10753
|
}
|
|
10205
|
-
if (
|
|
10754
|
+
if (isFooterPickerOpen()) {
|
|
10206
10755
|
setFooterModelPickerOpen(false);
|
|
10756
|
+
setFooterThinkingPickerOpen(false);
|
|
10207
10757
|
return;
|
|
10208
10758
|
}
|
|
10209
10759
|
if (!elements.commandSuggest.hidden) {
|
|
10210
10760
|
hideCommandSuggestions();
|
|
10211
10761
|
return;
|
|
10212
10762
|
}
|
|
10763
|
+
if (document.activeElement === elements.promptInput && !elements.promptInput.value.trim() && doubleEscapeAction !== "none") {
|
|
10764
|
+
const now = Date.now();
|
|
10765
|
+
if (now - lastEmptyPromptEscapeTime < 500) {
|
|
10766
|
+
event.preventDefault();
|
|
10767
|
+
lastEmptyPromptEscapeTime = 0;
|
|
10768
|
+
runNativeCommandMenu(`/${doubleEscapeAction}`).catch((error) => addEvent(error.message || String(error), "error"));
|
|
10769
|
+
return;
|
|
10770
|
+
}
|
|
10771
|
+
lastEmptyPromptEscapeTime = now;
|
|
10772
|
+
}
|
|
10213
10773
|
if (isMobileView() && !document.body.classList.contains("side-panel-collapsed")) {
|
|
10214
10774
|
setSidePanelCollapsed(true);
|
|
10215
10775
|
return;
|