diff --git a/llm-picker.html b/llm-picker.html index 97f655cd..c1f56955 100644 --- a/llm-picker.html +++ b/llm-picker.html @@ -87,7 +87,11 @@ } document.getElementById("settingsRow").addEventListener("click", () => api.settings()); document.addEventListener("keydown", (e) => { if (e.key === "Escape") api.close(); }); - window.addEventListener("blur", () => api.close()); + // Close-on-click-away is driven from main (see closeOnClickAway registration); + // relying on a client-side window.blur here closes the picker the moment it + // opens if Electron's focus flickers between the strip view and the pop, + // which is what happened in 0.3.106–0.3.119 — the pop briefly rendered then + // vanished and users saw the Pick-AI button as a no-op. api.onState(render); api.ready(); // Report rendered size so the main process can size the WebContentsView diff --git a/main.js b/main.js index 777f6c20..370b0beb 100644 --- a/main.js +++ b/main.js @@ -172,7 +172,7 @@ const SEARCH_ENGINES = { // Llama 3 + Mistral behind one URL. Serves ?q= on the regular DDG host and // flips into the chat UI with &ia=chat, so it also doubles as a toolbar // search target with pre-filled answer. - duckai: { kind: "llm", tier: "catalog", name: "DuckDuckGo AI", sym: "🦆", fav: "duck.ai", home: "https://duck.ai/", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) + "&ia=chat" }, + duckai: { kind: "llm", tier: "catalog", name: "Duck AI", sym: "🦆", fav: "duck.ai", home: "https://duck.ai/", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) + "&ia=chat" }, // Yandex Alisa — Russian-language chatbot. The chat UI is on a path, so we // route home via `home`; URL-query mode falls back to regular Yandex search // (ya.ru does not expose a public URL to open the chat with a pre-filled @@ -5962,6 +5962,7 @@ function createWindow() { closeOnClickAway(enginePicker, () => epVisible, () => showEnginePicker(false)); closeOnClickAway(langPicker, () => lpVisible, () => showLangPicker(false)); closeOnClickAway(downloadsPop, () => dlVisible, () => showDownloads(false)); + // llmPickerPop joins this contract below, where it is created. // Floating address-bar suggestions dropdown. addressPicker = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "address-picker-preload.js") } }); try { addressPicker.setBackgroundColor("#00000000"); } catch {} @@ -6069,6 +6070,11 @@ function createWindow() { raise(llmPickerPop); deferOverlayLoad(llmPickerPop, "llm-picker.html"); llmPickerPop.setVisible(false); + // Same close-on-click-away contract the engine picker uses. The HTML page + // used to carry its own window.blur handler; it closed the picker the + // moment it opened because Electron's focus flickered between the strip + // view and the pop. + closeOnClickAway(llmPickerPop, () => llmPickerVisible, () => hideLlmPicker()); // Page dialogs (alert / confirm / prompt) — see the js-dialog block. jsDialogPop = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "js-dialog-preload.js") } }); try { jsDialogPop.setBackgroundColor("#00000000"); } catch {} @@ -7876,6 +7882,7 @@ ipcMain.handle("jsdialog-answer", (e, reqId, ok, value) => { let approvalPop = null; let unlockPop = null; // vault unlock prompt (unlock.html), see requestVaultUnlock let llmPickerPop = null; // sidebar-strip AI engine picker (llm-picker.html) +let llmPickerVisible = false; // kept in sync with setVisible so closeOnClickAway can gate let llmPickerSize = { w: 240, h: 160 }; // last reported pop size for layout const approvalQueue = []; let approvalCurrent = null; // { reqId, resolve } @@ -9955,8 +9962,9 @@ ipcMain.handle("quicklinks-llm-menu", (e, rect) => { try { llmPickerPop.webContents.send("llm-picker-state", llmPickerState()); llmPickerPop.setVisible(true); - raise(llmPickerPop, primaryW?.win); - llmPickerPop.webContents.focus(); + llmPickerVisible = true; + raise(llmPickerPop, primaryW); + if (primaryW?.win?.isFocused()) try { llmPickerPop.webContents.focus(); } catch {} } catch {} }); return true; @@ -9964,6 +9972,7 @@ ipcMain.handle("quicklinks-llm-menu", (e, rect) => { function hideLlmPicker() { if (!llmPickerPop) return; try { llmPickerPop.setVisible(false); } catch {} + llmPickerVisible = false; } ipcMain.handle("llm-picker-ready", (e) => { if (!llmPickerPop || e.sender !== llmPickerPop.webContents) return false; diff --git a/package.json b/package.json index 466e1fc3..ab5e9490 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "theseus-navigator", - "version": "0.3.119", + "version": "0.3.120", "license": "MPL-2.0", "description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.", "author": "Silent Mode",