@firstpick/pi-package-webui 0.2.7 → 0.2.9
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 +195 -0
- package/package.json +6 -1
- package/public/app.js +616 -77
- package/public/index.html +43 -2
- package/public/styles.css +223 -7
- package/tests/mobile-static.test.mjs +34 -4
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";
|
|
@@ -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,83 +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
|
-
const thinkingLevelChanged = thinking.select.value !== state.thinkingLevel;
|
|
7734
|
-
if (thinkingLevelChanged) requests.push(nativeCommandApi("/api/thinking", { method: "POST", body: { level: thinking.select.value } }));
|
|
7735
|
-
if (steering.select.value !== state.steeringMode) requests.push(nativeCommandApi("/api/steering-mode", { method: "POST", body: { mode: steering.select.value } }));
|
|
7736
|
-
if (followUp.select.value !== state.followUpMode) requests.push(nativeCommandApi("/api/follow-up-mode", { method: "POST", body: { mode: followUp.select.value } }));
|
|
7737
|
-
if (autoCompact.input.checked !== state.autoCompactionEnabled) requests.push(nativeCommandApi("/api/auto-compaction", { method: "POST", body: { enabled: autoCompact.input.checked } }));
|
|
7738
|
-
elements.busyBehavior.value = busyBehavior.select.value;
|
|
7739
|
-
if (thinkingOutput.input.checked !== thinkingOutputVisible) setThinkingOutputVisible(thinkingOutput.input.checked);
|
|
7740
|
-
if (doneNotifications.input.checked !== agentDoneNotificationsEnabled) await setAgentDoneNotificationsEnabled(doneNotifications.input.checked);
|
|
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);
|
|
7741
8126
|
await Promise.all(requests);
|
|
8127
|
+
const response = await nativeCommandApi("/api/settings", { method: "POST", body: { settings: collectNativeSettingsPayload(controls), reload } });
|
|
8128
|
+
applyResponseTab(response);
|
|
8129
|
+
applyNativeSettingsForBrowser(response.data?.settings || collectNativeSettingsPayload(controls));
|
|
7742
8130
|
if (thinkingLevelChanged) requestGitFooterWebuiPayload(activeTabContext(), { force: true });
|
|
7743
|
-
addTransientMessage({ role: "native", title: "/settings", content:
|
|
8131
|
+
addTransientMessage({ role: "native", title: "/settings", content: nativeSettingsChangedMessage(response, reload), level: response.data?.reloadRecommended?.length && !response.data?.reloaded ? "warn" : "info" });
|
|
7744
8132
|
closeNativeCommandDialog();
|
|
7745
|
-
await
|
|
8133
|
+
await refreshAll();
|
|
7746
8134
|
} catch (error) {
|
|
7747
8135
|
setNativeCommandError(error.message || String(error));
|
|
7748
8136
|
} finally {
|
|
7749
|
-
setNativeActionBusy(
|
|
8137
|
+
setNativeActionBusy(button, false);
|
|
7750
8138
|
}
|
|
7751
|
-
}
|
|
8139
|
+
};
|
|
8140
|
+
|
|
8141
|
+
const reloadButton = addNativeCommandAction("Apply & reload tab", () => applySettings(true, reloadButton));
|
|
8142
|
+
const save = addNativeCommandAction("Apply", () => applySettings(false, save), "primary");
|
|
7752
8143
|
}
|
|
7753
8144
|
|
|
7754
8145
|
async function openNativeForkSelector() {
|
|
@@ -7813,6 +8204,41 @@ function openNativeCloneDialog() {
|
|
|
7813
8204
|
}, "primary");
|
|
7814
8205
|
}
|
|
7815
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
|
+
|
|
7816
8242
|
async function openNativeResumeSelector(scope = "current") {
|
|
7817
8243
|
openNativeCommandDialog({ title: "/resume", message: "Resume another persisted Pi session.", searchPlaceholder: "Filter sessions…" });
|
|
7818
8244
|
renderNativeLoading("Loading sessions…");
|
|
@@ -7854,21 +8280,39 @@ async function openNativeResumeSelector(scope = "current") {
|
|
|
7854
8280
|
}
|
|
7855
8281
|
}
|
|
7856
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
|
+
|
|
7857
8299
|
async function openNativeTreeSelector() {
|
|
7858
8300
|
openNativeCommandDialog({ title: "/tree", message: "Navigate the current session tree. Choosing a user message restores it into the editor.", searchPlaceholder: "Filter tree…" });
|
|
7859
8301
|
renderNativeLoading("Loading session tree…");
|
|
7860
8302
|
try {
|
|
7861
8303
|
const response = await nativeCommandApi("/api/session-tree");
|
|
7862
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.");
|
|
7863
8307
|
const summarize = nativeSettingToggle("Summarize abandoned branch", false, "Optional; may call the active model before switching branches.");
|
|
7864
8308
|
const labelField = make("label", "native-settings-field");
|
|
7865
|
-
labelField.append(
|
|
8309
|
+
labelField.append(nativeSettingsLabelRow("Optional label"));
|
|
7866
8310
|
const labelInput = make("input", "dialog-input");
|
|
7867
8311
|
labelInput.placeholder = "checkpoint label";
|
|
7868
8312
|
labelField.append(labelInput);
|
|
7869
8313
|
const options = make("div", "native-tree-options");
|
|
7870
|
-
options.append(summarize.field, labelField);
|
|
7871
|
-
const
|
|
8314
|
+
options.append(filterField.field, summarize.field, labelField);
|
|
8315
|
+
const toItems = () => nodes.filter((node) => nativeTreeFilterMatches(node, selectedFilter)).map((node) => ({
|
|
7872
8316
|
id: node.id,
|
|
7873
8317
|
label: `${node.title}${node.label ? ` · ${node.label}` : ""}`,
|
|
7874
8318
|
description: node.text || "",
|
|
@@ -7897,9 +8341,13 @@ async function openNativeTreeSelector() {
|
|
|
7897
8341
|
}
|
|
7898
8342
|
};
|
|
7899
8343
|
const render = () => {
|
|
7900
|
-
renderNativeSelectorItems(
|
|
8344
|
+
renderNativeSelectorItems(toItems(), { emptyText: "No session tree entries match this filter.", onSelect: navigate });
|
|
7901
8345
|
elements.nativeCommandBody.prepend(options);
|
|
7902
8346
|
};
|
|
8347
|
+
filterField.select.addEventListener("change", () => {
|
|
8348
|
+
selectedFilter = filterField.select.value;
|
|
8349
|
+
render();
|
|
8350
|
+
});
|
|
7903
8351
|
elements.nativeCommandSearch.oninput = render;
|
|
7904
8352
|
render();
|
|
7905
8353
|
} catch (error) {
|
|
@@ -8096,7 +8544,7 @@ async function handleNativeSlashSelectorCommand(message, { usesPromptInput = fal
|
|
|
8096
8544
|
await openNativeModelSelector();
|
|
8097
8545
|
return true;
|
|
8098
8546
|
case "settings":
|
|
8099
|
-
openNativeSettingsDialog();
|
|
8547
|
+
await openNativeSettingsDialog();
|
|
8100
8548
|
return true;
|
|
8101
8549
|
case "theme":
|
|
8102
8550
|
openNativeThemeSelector();
|
|
@@ -8107,6 +8555,9 @@ async function handleNativeSlashSelectorCommand(message, { usesPromptInput = fal
|
|
|
8107
8555
|
case "clone":
|
|
8108
8556
|
openNativeCloneDialog();
|
|
8109
8557
|
return true;
|
|
8558
|
+
case "name":
|
|
8559
|
+
openNativeNameDialog();
|
|
8560
|
+
return true;
|
|
8110
8561
|
case "resume":
|
|
8111
8562
|
await openNativeResumeSelector();
|
|
8112
8563
|
return true;
|
|
@@ -8615,10 +9066,23 @@ function getCommandMatches(query) {
|
|
|
8615
9066
|
.map((command) => ({ command, score: scoreCommandSuggestion(command, query) }))
|
|
8616
9067
|
.filter((item) => Number.isFinite(item.score))
|
|
8617
9068
|
.sort((a, b) => a.score - b.score || a.command.name.localeCompare(b.command.name))
|
|
8618
|
-
.slice(0,
|
|
9069
|
+
.slice(0, clampAutocompleteMaxVisible(autocompleteMaxVisible))
|
|
8619
9070
|
.map((item) => item.command);
|
|
8620
9071
|
}
|
|
8621
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
|
+
|
|
8622
9086
|
function activeSuggestionCount() {
|
|
8623
9087
|
return suggestionMode === "path" ? pathSuggestions.length : commandSuggestions.length;
|
|
8624
9088
|
}
|
|
@@ -8890,8 +9354,16 @@ function renderCommands() {
|
|
|
8890
9354
|
hideCommandSuggestions();
|
|
8891
9355
|
return;
|
|
8892
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
|
+
}
|
|
8893
9365
|
elements.commandsBox.classList.remove("muted");
|
|
8894
|
-
for (const command of
|
|
9366
|
+
for (const command of filteredCommands.slice(0, 80)) {
|
|
8895
9367
|
const item = make("button", "command-item");
|
|
8896
9368
|
item.type = "button";
|
|
8897
9369
|
item.title = `Send /${command.name}`;
|
|
@@ -8925,6 +9397,7 @@ async function refreshAll(tabContext = activeTabContext()) {
|
|
|
8925
9397
|
refreshCommands(tabContext),
|
|
8926
9398
|
refreshStats(tabContext),
|
|
8927
9399
|
refreshWorkspace(tabContext),
|
|
9400
|
+
refreshNativeSettings(tabContext),
|
|
8928
9401
|
refreshNetworkStatus(),
|
|
8929
9402
|
refreshWebuiVersion(),
|
|
8930
9403
|
]);
|
|
@@ -9175,6 +9648,7 @@ async function cycleModelFromShortcut(direction = "forward") {
|
|
|
9175
9648
|
const model = response.data?.model;
|
|
9176
9649
|
const scope = response.data?.scoped ? `scoped (${response.data.scopeSource})` : "all models";
|
|
9177
9650
|
if (isCurrentTabContext(tabContext)) {
|
|
9651
|
+
applyOptimisticModelSelection(model, tabContext);
|
|
9178
9652
|
addTransientMessage({ role: "native", title: "model cycle", content: `Model set to ${appShortcutModelLabel(model)} via ${direction} cycle over ${scope}.`, level: "info" });
|
|
9179
9653
|
await Promise.allSettled([refreshState(tabContext), refreshModels(tabContext), refreshStats(tabContext)]);
|
|
9180
9654
|
}
|
|
@@ -9191,8 +9665,8 @@ async function cycleThinkingFromShortcut() {
|
|
|
9191
9665
|
if (!tabContext.tabId) return;
|
|
9192
9666
|
try {
|
|
9193
9667
|
const response = await api("/api/thinking-cycle", { method: "POST", body: {}, tabId: tabContext.tabId });
|
|
9194
|
-
if (response.data?.level && currentState) currentState = { ...currentState, thinkingLevel: response.data.level };
|
|
9195
9668
|
if (isCurrentTabContext(tabContext)) {
|
|
9669
|
+
applyOptimisticThinkingSelection(response.data, tabContext);
|
|
9196
9670
|
addTransientMessage({ role: "native", title: "thinking", content: response.data?.level ? `Thinking level: ${response.data.level}` : "Thinking level did not change.", level: "info" });
|
|
9197
9671
|
if (response.data?.level) requestGitFooterWebuiPayload(tabContext, { force: true });
|
|
9198
9672
|
await Promise.allSettled([refreshState(tabContext), refreshStats(tabContext)]);
|
|
@@ -9816,7 +10290,13 @@ function handleEvent(event) {
|
|
|
9816
10290
|
syncRunIndicatorFromState(currentState);
|
|
9817
10291
|
renderStatus();
|
|
9818
10292
|
} else if (["set_model", "cycle_model", "set_thinking_level", "cycle_thinking_level", "new_session", "compact"].includes(event.command)) {
|
|
9819
|
-
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") {
|
|
9820
10300
|
const tabId = event.tabId || activeTabId;
|
|
9821
10301
|
forgetLastUserPrompt(tabId);
|
|
9822
10302
|
resetGitWorkflowForTab(tabId);
|
|
@@ -9854,6 +10334,7 @@ function connectEvents(tabContext = activeTabContext()) {
|
|
|
9854
10334
|
|
|
9855
10335
|
elements.copyServerCommandButton?.addEventListener("click", copyServerStartCommand);
|
|
9856
10336
|
elements.retryServerConnectionButton?.addEventListener("click", retryServerConnection);
|
|
10337
|
+
elements.commandSearchInput?.addEventListener("input", renderCommands);
|
|
9857
10338
|
elements.sendFeedbackButton.addEventListener("click", () => submitQueuedActionFeedback());
|
|
9858
10339
|
elements.composer.addEventListener("submit", (event) => {
|
|
9859
10340
|
event.preventDefault();
|
|
@@ -9876,15 +10357,18 @@ elements.gitWorkflowButton.addEventListener("click", () => {
|
|
|
9876
10357
|
const publishMenuContainer = elements.publishButton.parentElement;
|
|
9877
10358
|
elements.publishButton.addEventListener("click", () => {
|
|
9878
10359
|
setNativeCommandMenuOpen(false);
|
|
10360
|
+
setOptionsMenuOpen(false);
|
|
9879
10361
|
setPublishMenuOpen(true);
|
|
9880
10362
|
});
|
|
9881
10363
|
publishMenuContainer?.addEventListener("pointerenter", () => {
|
|
9882
10364
|
setNativeCommandMenuOpen(false);
|
|
10365
|
+
setOptionsMenuOpen(false);
|
|
9883
10366
|
setPublishMenuOpen(true);
|
|
9884
10367
|
});
|
|
9885
10368
|
publishMenuContainer?.addEventListener("pointerleave", () => setPublishMenuOpen(false));
|
|
9886
10369
|
publishMenuContainer?.addEventListener("focusin", () => {
|
|
9887
10370
|
setNativeCommandMenuOpen(false);
|
|
10371
|
+
setOptionsMenuOpen(false);
|
|
9888
10372
|
setPublishMenuOpen(true);
|
|
9889
10373
|
});
|
|
9890
10374
|
publishMenuContainer?.addEventListener("focusout", () => {
|
|
@@ -9895,15 +10379,18 @@ publishMenuContainer?.addEventListener("focusout", () => {
|
|
|
9895
10379
|
const nativeCommandMenuContainer = elements.nativeCommandMenuButton.parentElement;
|
|
9896
10380
|
elements.nativeCommandMenuButton.addEventListener("click", () => {
|
|
9897
10381
|
setPublishMenuOpen(false);
|
|
10382
|
+
setOptionsMenuOpen(false);
|
|
9898
10383
|
setNativeCommandMenuOpen(true);
|
|
9899
10384
|
});
|
|
9900
10385
|
nativeCommandMenuContainer?.addEventListener("pointerenter", () => {
|
|
9901
10386
|
setPublishMenuOpen(false);
|
|
10387
|
+
setOptionsMenuOpen(false);
|
|
9902
10388
|
setNativeCommandMenuOpen(true);
|
|
9903
10389
|
});
|
|
9904
10390
|
nativeCommandMenuContainer?.addEventListener("pointerleave", () => setNativeCommandMenuOpen(false));
|
|
9905
10391
|
nativeCommandMenuContainer?.addEventListener("focusin", () => {
|
|
9906
10392
|
setPublishMenuOpen(false);
|
|
10393
|
+
setOptionsMenuOpen(false);
|
|
9907
10394
|
setNativeCommandMenuOpen(true);
|
|
9908
10395
|
});
|
|
9909
10396
|
nativeCommandMenuContainer?.addEventListener("focusout", () => {
|
|
@@ -9911,10 +10398,40 @@ nativeCommandMenuContainer?.addEventListener("focusout", () => {
|
|
|
9911
10398
|
if (!nativeCommandMenuContainer?.contains(document.activeElement)) setNativeCommandMenuOpen(false);
|
|
9912
10399
|
}, 0);
|
|
9913
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
|
+
});
|
|
9914
10423
|
elements.releaseNpmButton.addEventListener("click", () => runPublishWorkflow("/release-npm"));
|
|
9915
10424
|
elements.releaseAurButton.addEventListener("click", () => runPublishWorkflow("/release-aur"));
|
|
9916
10425
|
elements.nativeSkillsButton.addEventListener("click", () => runNativeCommandMenu("/skills"));
|
|
9917
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"));
|
|
9918
10435
|
elements.gitWorkflowCancelButton.addEventListener("click", () => cancelGitWorkflow());
|
|
9919
10436
|
elements.nativeCommandDialog.addEventListener("close", () => {
|
|
9920
10437
|
elements.nativeCommandSearch.oninput = null;
|
|
@@ -10034,8 +10551,11 @@ elements.setModelButton.addEventListener("click", async () => {
|
|
|
10034
10551
|
const tabContext = activeTabContext();
|
|
10035
10552
|
try {
|
|
10036
10553
|
const selected = JSON.parse(elements.modelSelect.value);
|
|
10037
|
-
await api("/api/model", { method: "POST", body: selected, tabId: tabContext.tabId });
|
|
10038
|
-
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
|
+
}
|
|
10039
10559
|
} catch (error) {
|
|
10040
10560
|
if (isCurrentTabContext(tabContext)) addEvent(error.message, "error");
|
|
10041
10561
|
}
|
|
@@ -10045,13 +10565,13 @@ elements.setThinkingButton.addEventListener("click", async () => {
|
|
|
10045
10565
|
try {
|
|
10046
10566
|
const response = await api("/api/thinking", { method: "POST", body: { level: elements.thinkingSelect.value }, tabId: tabContext.tabId });
|
|
10047
10567
|
if (isCurrentTabContext(tabContext)) {
|
|
10568
|
+
applyOptimisticThinkingSelection(response.data, tabContext);
|
|
10048
10569
|
if (response.data?.pending) {
|
|
10049
10570
|
addEvent(response.data.message || `Thinking level ${response.data.level} will apply to the next prompt.`, "info");
|
|
10050
10571
|
} else if (response.data?.level) {
|
|
10051
10572
|
const requested = response.data.requestedLevel;
|
|
10052
10573
|
const effective = response.data.level;
|
|
10053
10574
|
addEvent(requested && requested !== effective ? `Thinking level set to ${effective} (requested ${requested}).` : `Thinking level set to ${effective}.`, "info");
|
|
10054
|
-
requestGitFooterWebuiPayload(tabContext, { force: true });
|
|
10055
10575
|
}
|
|
10056
10576
|
await refreshState(tabContext);
|
|
10057
10577
|
}
|
|
@@ -10125,11 +10645,15 @@ document.addEventListener("pointerdown", (event) => {
|
|
|
10125
10645
|
if (nativeCommandMenuOpen && !event.target?.closest?.(".composer-native-command-menu")) {
|
|
10126
10646
|
setNativeCommandMenuOpen(false);
|
|
10127
10647
|
}
|
|
10648
|
+
if (optionsMenuOpen && !event.target?.closest?.(".composer-options-menu")) {
|
|
10649
|
+
setOptionsMenuOpen(false);
|
|
10650
|
+
}
|
|
10128
10651
|
if (document.body.classList.contains("mobile-tabs-expanded") && !elements.tabBar.contains(event.target) && !elements.terminalTabsToggleButton.contains(event.target)) {
|
|
10129
10652
|
setMobileTabsExpanded(false);
|
|
10130
10653
|
}
|
|
10131
|
-
if (
|
|
10654
|
+
if (isFooterPickerOpen() && !elements.statusBar.contains(event.target)) {
|
|
10132
10655
|
setFooterModelPickerOpen(false);
|
|
10656
|
+
setFooterThinkingPickerOpen(false);
|
|
10133
10657
|
}
|
|
10134
10658
|
}, { passive: true });
|
|
10135
10659
|
document.addEventListener("pointermove", (event) => {
|
|
@@ -10215,6 +10739,10 @@ window.addEventListener("keydown", (event) => {
|
|
|
10215
10739
|
setNativeCommandMenuOpen(false);
|
|
10216
10740
|
return;
|
|
10217
10741
|
}
|
|
10742
|
+
if (optionsMenuOpen) {
|
|
10743
|
+
setOptionsMenuOpen(false);
|
|
10744
|
+
return;
|
|
10745
|
+
}
|
|
10218
10746
|
if (document.body.classList.contains("composer-actions-open")) {
|
|
10219
10747
|
setComposerActionsOpen(false);
|
|
10220
10748
|
return;
|
|
@@ -10223,14 +10751,25 @@ window.addEventListener("keydown", (event) => {
|
|
|
10223
10751
|
setMobileTabsExpanded(false);
|
|
10224
10752
|
return;
|
|
10225
10753
|
}
|
|
10226
|
-
if (
|
|
10754
|
+
if (isFooterPickerOpen()) {
|
|
10227
10755
|
setFooterModelPickerOpen(false);
|
|
10756
|
+
setFooterThinkingPickerOpen(false);
|
|
10228
10757
|
return;
|
|
10229
10758
|
}
|
|
10230
10759
|
if (!elements.commandSuggest.hidden) {
|
|
10231
10760
|
hideCommandSuggestions();
|
|
10232
10761
|
return;
|
|
10233
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
|
+
}
|
|
10234
10773
|
if (isMobileView() && !document.body.classList.contains("side-panel-collapsed")) {
|
|
10235
10774
|
setSidePanelCollapsed(true);
|
|
10236
10775
|
return;
|