From 47a739ad7283520542ab550ac86c8d1519bdd373 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Tue, 27 Oct 2026 16:47:22 +0200 Subject: [PATCH] Theseus 0.3.119: LLMs out of the search dropdown; reorder by popularity; add DuckAI, Alisa, Qwen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The toolbar magnifier dropdown mixed search engines and AI chat engines in one list, which left the Pick-AI button on the sidebar strip looking redundant and cluttered the thing people reach for when they want to find something on the web. The dropdown is now search-only; LLMs are picked through the strip's own AI picker overlay (which already lists them with real icons). LLM order follows rough global popularity so a fresh profile defaults to the one most people reach for: ChatGPT, Gemini, Grok, Perplexity, DuckAI, Alisa, Qwen. Three additions along with it: * DuckAI (duck.ai) β€” anonymous, no sign-up, serves GPT-4o mini + Claude 3 Haiku + Llama 3 + Mistral behind one URL. Also wired as a toolbar search target with pre-filled answer via &ia=chat. * Alisa (ya.ru/alisa) β€” Russian-language assistant from Yandex. * Qwen (chat.qwen.ai) β€” Alibaba's Tongyi chat; email sign-up, the strongest non-mainland-phone access to a Chinese model. Phind stays frozen (Vercel 404 at the root). A saved defaultLlmId pointing at a frozen engine is cleared so the fallback resolves to the new list[0] rather than silently flipping to whatever was second. Engine home URLs can now opt into an override via a `home` field β€” needed by DuckAI (its chat lives at the apex of duck.ai but prefers that URL over the duckduckgo.com stem) and Alisa (chat UI lives at /alisa, not the apex of ya.ru). --- engine-icons/alisa.svg | 1 + engine-icons/duckai.svg | 1 + engine-icons/qwen.svg | 1 + main.js | 83 ++++++++++++++++++++++++++++++----------- package.json | 2 +- 5 files changed, 65 insertions(+), 23 deletions(-) create mode 100644 engine-icons/alisa.svg create mode 100644 engine-icons/duckai.svg create mode 100644 engine-icons/qwen.svg diff --git a/engine-icons/alisa.svg b/engine-icons/alisa.svg new file mode 100644 index 00000000..d4d1c346 --- /dev/null +++ b/engine-icons/alisa.svg @@ -0,0 +1 @@ +A diff --git a/engine-icons/duckai.svg b/engine-icons/duckai.svg new file mode 100644 index 00000000..7c7f2d3d --- /dev/null +++ b/engine-icons/duckai.svg @@ -0,0 +1 @@ +D diff --git a/engine-icons/qwen.svg b/engine-icons/qwen.svg new file mode 100644 index 00000000..6a89ecca --- /dev/null +++ b/engine-icons/qwen.svg @@ -0,0 +1 @@ +Q diff --git a/main.js b/main.js index a35dd842..777f6c20 100644 --- a/main.js +++ b/main.js @@ -153,19 +153,37 @@ const SEARCH_ENGINES = { // default becomes stale as instances rate-limit / die (searx.be is anti-bot-locked). // Users who want SearXNG add their preferred instance via the custom URL form. wikipedia: { kind: "search", tier: "catalog", name: "Wikipedia", sym: "πŸ“–", fav: "en.wikipedia.org", url: (q) => "https://en.wikipedia.org/wiki/Special:Search?search=" + encodeURIComponent(q) }, - // AI / LLM answer engines. The first group answers a URL query without any - // login, so they also work as a "search engine" in the toolbar. The second - // group needs an account (free tier) and bounces ?q= to the sign-in gate β€” - // they're useful as sidebar LLM targets (opens the chat home) but not as - // the toolbar default search; set one of the first group there instead. - perplexity: { kind: "llm", tier: "catalog", name: "Perplexity", sym: "🧠", fav: "www.perplexity.ai", url: (q) => "https://www.perplexity.ai/search?q=" + encodeURIComponent(q) }, - phind: { kind: "llm", tier: "catalog", name: "Phind", sym: "πŸ§‘β€πŸ’»", fav: "www.phind.com", frozen: "Phind's web UI returns a Vercel 404 at the root (2026-10-27).", url: (q) => "https://www.phind.com/search?q=" + encodeURIComponent(q) }, - // Account-gated LLMs β€” free tier available on all three. ChatGPT and Grok - // accept ?q= on their home URL and pre-fill the composer; Gemini's - // webapp ignores query strings so it just lands on the chat screen. + // AI / LLM answer engines, ordered by rough global popularity so the first + // entry is a sensible default for a new profile. `home` overrides the + // llmHomeUrl calculation when the chat UI lives on a path, not the apex. + // Perplexity + DuckAI answer a URL query without any login, so they also + // work as a toolbar search engine; the rest open their chat home. + // + // We used to list these engines in the toolbar search dropdown too, but as + // of 0.3.119 that dropdown is search-only β€” the Pick AI button in the + // sidebar strip is where you choose which LLM to launch, and emitEngines() + // filters LLMs out of what the toolbar engine picker sees. See the comment + // on emitEngines() for the invariant. chatgpt: { kind: "llm", tier: "catalog", name: "ChatGPT", sym: "πŸ’¬", fav: "chatgpt.com", url: (q) => "https://chatgpt.com/?q=" + encodeURIComponent(q) }, - grok: { kind: "llm", tier: "catalog", name: "Grok", sym: "πŸͺ", fav: "grok.com", url: (q) => "https://grok.com/?q=" + encodeURIComponent(q) }, gemini: { kind: "llm", tier: "catalog", name: "Gemini", sym: "✦", fav: "gemini.google.com", url: (q) => "https://gemini.google.com/app?q=" + encodeURIComponent(q) }, + grok: { kind: "llm", tier: "catalog", name: "Grok", sym: "πŸͺ", fav: "grok.com", url: (q) => "https://grok.com/?q=" + encodeURIComponent(q) }, + perplexity: { kind: "llm", tier: "catalog", name: "Perplexity", sym: "🧠", fav: "www.perplexity.ai", url: (q) => "https://www.perplexity.ai/search?q=" + encodeURIComponent(q) }, + // DuckDuckGo AI Chat β€” no sign-up, anonymous: GPT-4o mini + Claude 3 Haiku + + // 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" }, + // 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 + // prompt). + alisa: { kind: "llm", tier: "catalog", name: "Alisa", sym: "πŸ‡·πŸ‡Ί", fav: "ya.ru", home: "https://ya.ru/alisa", url: (q) => "https://ya.ru/?text=" + encodeURIComponent(q) }, + // Qwen chat β€” Alibaba's Tongyi Qianwen chat; email sign-up (no phone), the + // strongest open-weights Chinese model that is reachable without a mainland + // phone number. Query strings are ignored by the composer, so URL-query mode + // just lands on the chat home. + qwen: { kind: "llm", tier: "catalog", name: "Qwen", sym: "ι€š", fav: "chat.qwen.ai", url: (q) => "https://chat.qwen.ai/?q=" + encodeURIComponent(q) }, + phind: { kind: "llm", tier: "catalog", name: "Phind", sym: "πŸ§‘β€πŸ’»", fav: "www.phind.com", frozen: "Phind's web UI returns a Vercel 404 at the root (2026-10-27).", url: (q) => "https://www.phind.com/search?q=" + encodeURIComponent(q) }, // ---- Extras: wider bank, discoverable via the Search filter box in Settings. // These are known-working engines that don't require sign-in on ?q= but aren't // first-class enough to sit in the primary catalog. Keep the list vetted β€” if @@ -182,7 +200,7 @@ const SEARCH_ENGINES = { }; // Engines enabled by default (shown in the toolbar dropdown). The rest are in the // catalog and can be turned on from Settings. Custom + detected engines are always on. -const DEFAULT_ENABLED = ["startpage", "duckduckgo", "google", "brave", "bing", "perplexity", "chatgpt", "grok", "gemini"]; // Phind removed 2026-10-27 β€” frozen (Vercel 404 at root) +const DEFAULT_ENABLED = ["startpage", "duckduckgo", "google", "brave", "bing", "chatgpt", "gemini", "grok", "perplexity", "duckai", "alisa", "qwen"]; // LLM order 2026-10-27 by popularity; Phind frozen (Vercel 404 at root) // A frozen catalog engine (`frozen: ""`) stays visible in Settings so the user sees why it // is gone, but it is never enabled, never offered in the picker and never the default. Unfreeze by // deleting the field; the user's installed list is left alone, so the row comes back as it was. @@ -317,9 +335,11 @@ function enabledEnginesList() { return allEngines().filter((e) => e.enabled); } // and the "Pick…" menu lists everything here. function enabledLlms() { return enabledEnginesList().filter((e) => e.kind === "llm"); } // A home URL for an LLM engine so we can open its web UI in the panel. -// Each engine's `fav` field is the hostname; the home page is the root. +// `home` overrides when the chat UI lives on a path (duck.ai, ya.ru/alisa); +// otherwise each engine's `fav` field is the hostname and the home is its root. function llmHomeUrl(engineId) { const def = SEARCH_ENGINES[engineId]; + if (def && def.home) return def.home; if (def && def.fav) return "https://" + def.fav + "/"; // Custom engines fall back to the origin of their URL template. const custom = (settings.customEngines || []).find((e) => e.id === engineId); @@ -741,13 +761,17 @@ function loadSettings() { settings.searchEngine = enabled.find((x) => !isFrozen(x)) || DEFAULT_ENABLED[0]; } } catch (e) { console.warn("engine normalize:", e?.message); } - // One-time LLM-engine offer (0.3.101): three new account-gated LLMs were - // added to DEFAULT_ENABLED, but an existing profile's enabledEngines is - // already set so the fresh default doesn't take effect. Add each one the - // first time this upgrade runs; `enginesOffered` remembers it so a user - // who disables one keeps it disabled across restarts. + // One-time LLM-engine offer: new engines ride on top of DEFAULT_ENABLED but an + // existing profile's enabledEngines is already set, so a fresh default alone + // never takes effect. Each id appears here once; `enginesOffered` remembers + // every offer so a user who disables one keeps it disabled across restarts, + // and so adding another engine next release won't re-offer the old ones. + // β€’ 0.3.101 β€” chatgpt, grok, gemini + // β€’ 0.3.119 β€” duckai (anonymous, no sign-up), alisa (Yandex), + // qwen (Alibaba, email sign-up β€” strongest non-mainland-phone + // access to a Chinese LLM) try { - const OFFER = ["chatgpt", "grok", "gemini"]; + const OFFER = ["chatgpt", "grok", "gemini", "duckai", "alisa", "qwen"]; const offered = Array.isArray(settings.enginesOffered) ? settings.enginesOffered.slice() : []; let changed = false; for (const id of OFFER) { @@ -758,6 +782,14 @@ function loadSettings() { } if (changed) settings.enginesOffered = offered; } catch (e) { console.warn("engines offer:", e?.message); } + // Phind was marked frozen in 0.3.118 (its Vercel 404s), so if the user's + // saved default LLM was phind the sidebar strip falls through to list[0] β€” + // which, under the pre-0.3.119 ordering, happened to be Perplexity. The + // 0.3.119 reorder puts ChatGPT at list[0] instead, so clearing the stale + // pointer makes the fallback resolve to ChatGPT (currently the single most + // popular chat UI) rather than silently flipping their choice. + try { if (settings.defaultLlmId && isFrozen(settings.defaultLlmId)) settings.defaultLlmId = null; } + catch (e) { console.warn("defaultLlmId migrate:", e?.message); } // Quick-links default set changed in 0.3.70: drop Messenger + Spotify and // reorder to Telegram, WhatsApp, X, YouTube. Users who kept the previous // untouched default (same 6 ids, same order) move to the new set; any @@ -1372,9 +1404,16 @@ small{color:#b98b92}code{font:12px ui-monospace,monospace;word-break:break-all}<

To decide for yourself, type ${esc(host)} in the address bar: Theseus shows the full warning there, with the option to continue where one exists.

`; } +// INVARIANT: the toolbar's engine picker is search-only β€” LLMs are picked +// through the sidebar strip's Pick AI overlay (llm-picker.html), which reads +// enabledLlms() through llmPickerState(), not this payload. Keep LLMs out of +// what the toolbar sees so the dropdown stays focused on finding things on +// the web; Settings still gets the full list via `search-engines` IPC. +function searchDropdownEngines() { return enabledEnginesList().filter((e) => (e.kind || "search") !== "llm"); } function emitEngines() { - sendAllChrome("engines", { engines: enabledEnginesList(), current: settings.searchEngine }); - if (epVisible) try { enginePicker?.webContents.send("engines", { engines: enabledEnginesList(), current: settings.searchEngine, detected: activeTab()?.detected || null }); } catch {} + const engines = searchDropdownEngines(); + sendAllChrome("engines", { engines, current: settings.searchEngine }); + if (epVisible) try { enginePicker?.webContents.send("engines", { engines, current: settings.searchEngine, detected: activeTab()?.detected || null }); } catch {} } // WebRTC IP-handling policy β€” the same control the "WebRTC Network Limiter" // Chrome extension provides, done natively (that extension's chrome.privacy API @@ -4291,7 +4330,7 @@ function showEnginePicker(show) { raise(enginePicker); enginePicker.setVisible(true); epVisible = true; if (win.isFocused()) try { enginePicker.webContents.focus(); } catch {} // see closeOnClickAway - enginePicker.webContents.send("engines", { engines: enabledEnginesList(), current: settings.searchEngine, detected: activeTab()?.detected || null }); + enginePicker.webContents.send("engines", { engines: searchDropdownEngines(), current: settings.searchEngine, detected: activeTab()?.detected || null }); } else { cancelOverlayShow(enginePicker); enginePicker.setVisible(false); epVisible = false; } } // Floating language picker β€” same shape as the engine picker above. The diff --git a/package.json b/package.json index 68f53679..466e1fc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "theseus-navigator", - "version": "0.3.118", + "version": "0.3.119", "license": "MPL-2.0", "description": "Theseus Navigator β€” a browser that follows the thread. By Silent Mode, a Deviant project.", "author": "Silent Mode",