dsh-mobile-plugin 0.5.27 → 0.5.29
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/extensions/catalog.json +10 -1
- package/extensions/magpie/agentcompact.js +104 -0
- package/extensions/magpie/app.css +4902 -0
- package/extensions/magpie/app.js +24474 -0
- package/extensions/magpie/autobackup.js +185 -0
- package/extensions/magpie/autoresetown.js +151 -0
- package/extensions/magpie/balfound.js +38 -0
- package/extensions/magpie/boot.js +23 -0
- package/extensions/magpie/cfgblocked.js +39 -0
- package/extensions/magpie/checkinnow.js +111 -0
- package/extensions/magpie/claudemode.js +92 -0
- package/extensions/magpie/compat.js +41 -0
- package/extensions/magpie/context.css +433 -0
- package/extensions/magpie/context.js +1576 -0
- package/extensions/magpie/ctxtext.js +223 -0
- package/extensions/magpie/fonts.js +42 -0
- package/extensions/magpie/i18n.js +18942 -0
- package/extensions/magpie/icons/ai21.svg +1 -0
- package/extensions/magpie/icons/ai302-color.svg +1 -0
- package/extensions/magpie/icons/ai360-color.svg +1 -0
- package/extensions/magpie/icons/aihubmix-color.svg +1 -0
- package/extensions/magpie/icons/aionui.svg +1 -0
- package/extensions/magpie/icons/air.svg +4 -0
- package/extensions/magpie/icons/alibaba-color.svg +1 -0
- package/extensions/magpie/icons/alibabacloud-color.svg +1 -0
- package/extensions/magpie/icons/alma.png +0 -0
- package/extensions/magpie/icons/anthropic.svg +1 -0
- package/extensions/magpie/icons/antigravity-color.svg +1 -0
- package/extensions/magpie/icons/anyscale-color.svg +1 -0
- package/extensions/magpie/icons/app-alma.png +0 -0
- package/extensions/magpie/icons/app-cc-switch.png +0 -0
- package/extensions/magpie/icons/apple-touch-icon.png +0 -0
- package/extensions/magpie/icons/aside.svg +28 -0
- package/extensions/magpie/icons/astrbot.svg +1 -0
- package/extensions/magpie/icons/atomcode.png +0 -0
- package/extensions/magpie/icons/aws.svg +1 -0
- package/extensions/magpie/icons/azure-color.svg +1 -0
- package/extensions/magpie/icons/azureai-color.svg +1 -0
- package/extensions/magpie/icons/baichuan-color.svg +1 -0
- package/extensions/magpie/icons/baidu-color.svg +1 -0
- package/extensions/magpie/icons/baiducloud-color.svg +1 -0
- package/extensions/magpie/icons/bailian-color.svg +1 -0
- package/extensions/magpie/icons/baseten.svg +1 -0
- package/extensions/magpie/icons/bedrock-color.svg +1 -0
- package/extensions/magpie/icons/burncloud-color.svg +1 -0
- package/extensions/magpie/icons/bytedance-color.svg +1 -0
- package/extensions/magpie/icons/cerebras.svg +1 -0
- package/extensions/magpie/icons/chatglm-color.svg +1 -0
- package/extensions/magpie/icons/cherryin-color.svg +1 -0
- package/extensions/magpie/icons/chutes.svg +1 -0
- package/extensions/magpie/icons/cindy.png +0 -0
- package/extensions/magpie/icons/claude-color.svg +1 -0
- package/extensions/magpie/icons/claudecode-color.svg +1 -0
- package/extensions/magpie/icons/cline.svg +1 -0
- package/extensions/magpie/icons/cloudflare-color.svg +1 -0
- package/extensions/magpie/icons/codebuddy-color.svg +1 -0
- package/extensions/magpie/icons/codex-color.svg +1 -0
- package/extensions/magpie/icons/cohere-color.svg +1 -0
- package/extensions/magpie/icons/cometapi-color.svg +1 -0
- package/extensions/magpie/icons/commandcode.svg +1 -0
- package/extensions/magpie/icons/copilot-color.svg +1 -0
- package/extensions/magpie/icons/coze.svg +1 -0
- package/extensions/magpie/icons/crush.png +0 -0
- package/extensions/magpie/icons/curl.svg +1 -0
- package/extensions/magpie/icons/cursor.svg +1 -0
- package/extensions/magpie/icons/deepinfra-color.svg +1 -0
- package/extensions/magpie/icons/deepmind-color.svg +1 -0
- package/extensions/magpie/icons/deepseek-color.svg +1 -0
- package/extensions/magpie/icons/deveco-color.svg +12 -0
- package/extensions/magpie/icons/devin.svg +1 -0
- package/extensions/magpie/icons/doubao-color.svg +1 -0
- package/extensions/magpie/icons/empryo-color.svg +1 -0
- package/extensions/magpie/icons/factory.svg +1 -0
- package/extensions/magpie/icons/favicon.svg +1 -0
- package/extensions/magpie/icons/featherless-color.svg +1 -0
- package/extensions/magpie/icons/fireworks-color.svg +1 -0
- package/extensions/magpie/icons/friendli.svg +1 -0
- package/extensions/magpie/icons/fx.svg +1 -0
- package/extensions/magpie/icons/gemini-color.svg +1 -0
- package/extensions/magpie/icons/geminicli-color.svg +1 -0
- package/extensions/magpie/icons/gemma-color.svg +1 -0
- package/extensions/magpie/icons/giteeai.svg +1 -0
- package/extensions/magpie/icons/github.svg +1 -0
- package/extensions/magpie/icons/githubcopilot.svg +1 -0
- package/extensions/magpie/icons/glama.svg +1 -0
- package/extensions/magpie/icons/gmicloud.svg +1 -0
- package/extensions/magpie/icons/google-color.svg +1 -0
- package/extensions/magpie/icons/googlecloud-color.svg +1 -0
- package/extensions/magpie/icons/goose.svg +1 -0
- package/extensions/magpie/icons/grok.svg +1 -0
- package/extensions/magpie/icons/groq.svg +1 -0
- package/extensions/magpie/icons/hanako.png +0 -0
- package/extensions/magpie/icons/hermes.svg +1 -0
- package/extensions/magpie/icons/huawei-color.svg +1 -0
- package/extensions/magpie/icons/huaweicloud-color.svg +1 -0
- package/extensions/magpie/icons/huggingface-color.svg +1 -0
- package/extensions/magpie/icons/hunyuan-color.svg +1 -0
- package/extensions/magpie/icons/hyperbolic-color.svg +1 -0
- package/extensions/magpie/icons/icon-512.png +0 -0
- package/extensions/magpie/icons/iflytekcloud-color.svg +1 -0
- package/extensions/magpie/icons/inference.svg +1 -0
- package/extensions/magpie/icons/infinigence-color.svg +1 -0
- package/extensions/magpie/icons/internlm-color.svg +1 -0
- package/extensions/magpie/icons/jcode.svg +1 -0
- package/extensions/magpie/icons/jina.svg +1 -0
- package/extensions/magpie/icons/kilo.svg +1 -0
- package/extensions/magpie/icons/kilocode.svg +1 -0
- package/extensions/magpie/icons/kimi-code-desktop.png +0 -0
- package/extensions/magpie/icons/kimi.svg +1 -0
- package/extensions/magpie/icons/kiro-color.svg +1 -0
- package/extensions/magpie/icons/kling-color.svg +1 -0
- package/extensions/magpie/icons/lambda.svg +1 -0
- package/extensions/magpie/icons/leptonai-color.svg +1 -0
- package/extensions/magpie/icons/liquid.svg +1 -0
- package/extensions/magpie/icons/liveagent-color.svg +1 -0
- package/extensions/magpie/icons/llmapi.svg +1 -0
- package/extensions/magpie/icons/lmstudio.svg +1 -0
- package/extensions/magpie/icons/lody.png +0 -0
- package/extensions/magpie/icons/longcat.svg +1 -0
- package/extensions/magpie/icons/magpie.svg +1 -0
- package/extensions/magpie/icons/meta-color.svg +1 -0
- package/extensions/magpie/icons/metaai-color.svg +1 -0
- package/extensions/magpie/icons/microsoft-color.svg +1 -0
- package/extensions/magpie/icons/mimocode.svg +1 -0
- package/extensions/magpie/icons/minimax-color.svg +1 -0
- package/extensions/magpie/icons/mistermorph-color.svg +1 -0
- package/extensions/magpie/icons/mistral-color.svg +1 -0
- package/extensions/magpie/icons/mlx-serve.png +0 -0
- package/extensions/magpie/icons/modelscope-color.svg +1 -0
- package/extensions/magpie/icons/monica-color.svg +1 -0
- package/extensions/magpie/icons/moonshot.svg +1 -0
- package/extensions/magpie/icons/morph-color.svg +1 -0
- package/extensions/magpie/icons/mothx.png +0 -0
- package/extensions/magpie/icons/nebius.svg +1 -0
- package/extensions/magpie/icons/newapi-color.svg +1 -0
- package/extensions/magpie/icons/nousresearch.svg +1 -0
- package/extensions/magpie/icons/nova-color.svg +1 -0
- package/extensions/magpie/icons/novita-color.svg +1 -0
- package/extensions/magpie/icons/nvidia-color.svg +1 -0
- package/extensions/magpie/icons/ollama.svg +1 -0
- package/extensions/magpie/icons/omlx.svg +1 -0
- package/extensions/magpie/icons/omo.svg +1 -0
- package/extensions/magpie/icons/omp.svg +1 -0
- package/extensions/magpie/icons/openai.svg +1 -0
- package/extensions/magpie/icons/openchamber.svg +11 -0
- package/extensions/magpie/icons/openclaw.svg +1 -0
- package/extensions/magpie/icons/opencode.svg +1 -0
- package/extensions/magpie/icons/openmausbot.svg +1 -0
- package/extensions/magpie/icons/openrouter.svg +1 -0
- package/extensions/magpie/icons/openwebui.svg +1 -0
- package/extensions/magpie/icons/parasail.svg +1 -0
- package/extensions/magpie/icons/penguin-color.svg +1 -0
- package/extensions/magpie/icons/perplexity-color.svg +1 -0
- package/extensions/magpie/icons/phala-color.svg +1 -0
- package/extensions/magpie/icons/pi.svg +1 -0
- package/extensions/magpie/icons/pipellm-color.svg +5 -0
- package/extensions/magpie/icons/poe.svg +1 -0
- package/extensions/magpie/icons/ppio-color.svg +1 -0
- package/extensions/magpie/icons/prime-agent.svg +1 -0
- package/extensions/magpie/icons/qiniu-color.svg +1 -0
- package/extensions/magpie/icons/qoder.svg +1 -0
- package/extensions/magpie/icons/qwen-color.svg +1 -0
- package/extensions/magpie/icons/qwenpaw.svg +1 -0
- package/extensions/magpie/icons/raycast.svg +1 -0
- package/extensions/magpie/icons/reasonix-color.svg +48 -0
- package/extensions/magpie/icons/reka.svg +1 -0
- package/extensions/magpie/icons/replicate.svg +1 -0
- package/extensions/magpie/icons/roocode.svg +1 -0
- package/extensions/magpie/icons/rtk.png +0 -0
- package/extensions/magpie/icons/sambanova-color.svg +1 -0
- package/extensions/magpie/icons/sensenova-color.svg +1 -0
- package/extensions/magpie/icons/siliconcloud-color.svg +1 -0
- package/extensions/magpie/icons/snow.png +0 -0
- package/extensions/magpie/icons/snowflake-color.svg +1 -0
- package/extensions/magpie/icons/sophnet-color.svg +1 -0
- package/extensions/magpie/icons/spark-color.svg +1 -0
- package/extensions/magpie/icons/stepfun-color.svg +1 -0
- package/extensions/magpie/icons/straico-color.svg +1 -0
- package/extensions/magpie/icons/submodel-color.svg +1 -0
- package/extensions/magpie/icons/t3code.svg +4 -0
- package/extensions/magpie/icons/targon-color.svg +1 -0
- package/extensions/magpie/icons/tencent-color.svg +1 -0
- package/extensions/magpie/icons/tencentcloud-color.svg +1 -0
- package/extensions/magpie/icons/tiangong-color.svg +1 -0
- package/extensions/magpie/icons/together-color.svg +1 -0
- package/extensions/magpie/icons/trae.svg +1 -0
- package/extensions/magpie/icons/typesafe.png +0 -0
- package/extensions/magpie/icons/upstage-color.svg +1 -0
- package/extensions/magpie/icons/venice-color.svg +1 -0
- package/extensions/magpie/icons/vercel.svg +1 -0
- package/extensions/magpie/icons/vertexai-color.svg +1 -0
- package/extensions/magpie/icons/vllm-color.svg +1 -0
- package/extensions/magpie/icons/volcengine-color.svg +1 -0
- package/extensions/magpie/icons/voyage-color.svg +1 -0
- package/extensions/magpie/icons/vscode.svg +1 -0
- package/extensions/magpie/icons/wenxin-color.svg +1 -0
- package/extensions/magpie/icons/windsurf.svg +1 -0
- package/extensions/magpie/icons/workbuddy-color.svg +1 -0
- package/extensions/magpie/icons/workersai-color.svg +1 -0
- package/extensions/magpie/icons/xai.svg +1 -0
- package/extensions/magpie/icons/xiaomimimo.svg +1 -0
- package/extensions/magpie/icons/xinference-color.svg +1 -0
- package/extensions/magpie/icons/yi.svg +1 -0
- package/extensions/magpie/icons/yuanbao-color.svg +1 -0
- package/extensions/magpie/icons/yylx.svg +5 -0
- package/extensions/magpie/icons/zai.svg +1 -0
- package/extensions/magpie/icons/zcode.png +0 -0
- package/extensions/magpie/icons/zed.svg +1 -0
- package/extensions/magpie/icons/zenmux.svg +1 -0
- package/extensions/magpie/icons/zeroone.svg +1 -0
- package/extensions/magpie/icons/zhipu-color.svg +1 -0
- package/extensions/magpie/icons/zoo-code.png +0 -0
- package/extensions/magpie/index.html +572 -0
- package/extensions/magpie/kernel.js +274 -0
- package/extensions/magpie/keyspend.js +63 -0
- package/extensions/magpie/layout.js +26 -0
- package/extensions/magpie/manifest.json +37 -0
- package/extensions/magpie/navlamp.js +47 -0
- package/extensions/magpie/omarchy.css +72 -0
- package/extensions/magpie/omarchy.js +46 -0
- package/extensions/magpie/passerrors.js +21 -0
- package/extensions/magpie/pluginpages.js +405 -0
- package/extensions/magpie/plugins/gateway/client.js +1194 -0
- package/extensions/magpie/plugins/gateway/keys.js +972 -0
- package/extensions/magpie/plugins/gateway/settings.js +459 -0
- package/extensions/magpie/plugins/library/client.js +6788 -0
- package/extensions/magpie/plugins/library/i18n.js +3228 -0
- package/extensions/magpie/plugins/library/library.css +848 -0
- package/extensions/magpie/plugins/otel/i18n.js +48 -0
- package/extensions/magpie/plugins/otel/page.js +189 -0
- package/extensions/magpie/plugins/redact/client.js +46 -0
- package/extensions/magpie/plugins/redact/demo.js +461 -0
- package/extensions/magpie/plugins/redact/i18n.js +432 -0
- package/extensions/magpie/plugins/redact/page.js +985 -0
- package/extensions/magpie/plugins/redact/redact.css +385 -0
- package/extensions/magpie/plugins/redact/settings.js +248 -0
- package/extensions/magpie/plugins/redact/xq_dark.jpg +0 -0
- package/extensions/magpie/plugins/redact/xq_dark.webm +0 -0
- package/extensions/magpie/plugins/redact/xq_light.jpg +0 -0
- package/extensions/magpie/plugins/redact/xq_light.webm +0 -0
- package/extensions/magpie/plugins/routing/client.js +8593 -0
- package/extensions/magpie/plugins/routing/i18n.js +2696 -0
- package/extensions/magpie/plugins/routing/routing.css +1111 -0
- package/extensions/magpie/plugins/search/settings.js +460 -0
- package/extensions/magpie/plugins/sessions/client.js +1622 -0
- package/extensions/magpie/plugins/sessions/i18n.js +360 -0
- package/extensions/magpie/plugins/sessions/sessions.css +181 -0
- package/extensions/magpie/plugins/sessions/settings.js +100 -0
- package/extensions/magpie/plugins/usage/client.js +150 -0
- package/extensions/magpie/plugins/usage/i18n.js +984 -0
- package/extensions/magpie/plugins/usage/page.js +4307 -0
- package/extensions/magpie/plugins.css +319 -0
- package/extensions/magpie/plugins.js +1926 -0
- package/extensions/magpie/profilerename.js +27 -0
- package/extensions/magpie/purposes.js +2 -0
- package/extensions/magpie/quitask.js +79 -0
- package/extensions/magpie/quotaall.js +142 -0
- package/extensions/magpie/ratewindows.js +123 -0
- package/extensions/magpie/registry.js +87 -0
- package/extensions/magpie/resetask.js +137 -0
- package/extensions/magpie/ruleedit.js +53 -0
- package/extensions/magpie/scrollends.js +91 -0
- package/extensions/magpie/settingshints.js +75 -0
- package/extensions/magpie/signsheets.js +86 -0
- package/extensions/magpie/start.js +26 -0
- package/extensions/magpie/syncfiles.js +216 -0
- package/extensions/magpie/theme.js +29 -0
- package/extensions/magpie/traywindows.js +41 -0
- package/extensions/magpie/webfonts.js +130 -0
- package/extensions/magpie/whatsnew.js +252 -0
- package/extensions/magpie.zip +0 -0
- package/knowledge/apps/baijiahao.md +30 -0
- package/knowledge/apps/bilibili.md +1 -0
- package/knowledge/apps/dedao.md +33 -0
- package/knowledge/apps/feishu.md +35 -0
- package/knowledge/apps/gmail.md +33 -0
- package/knowledge/apps/jike.md +31 -0
- package/knowledge/apps/kuaishou.md +34 -0
- package/knowledge/apps/linkedin.md +33 -0
- package/knowledge/apps/qqmail.md +30 -0
- package/knowledge/apps/telegram.md +27 -0
- package/knowledge/apps/toutiao.md +32 -0
- package/knowledge/apps/uber-eats.md +26 -0
- package/knowledge/apps/uber.md +27 -0
- package/knowledge/apps/whatsapp.md +27 -0
- package/knowledge/apps/x.md +32 -0
- package/knowledge/apps/xiaohongshu.md +1 -0
- package/knowledge/apps/zhihu.md +1 -0
- package/knowledge/sites/bilibili.com/SITE.md +50 -0
- package/knowledge/sites/bilibili.com/references/view-fields.md +17 -0
- package/knowledge/sites/github.com/SITE.md +51 -0
- package/knowledge/sites/github.com/references/repo-fields.md +13 -0
- package/knowledge/sites/x.com/SITE.md +47 -0
- package/knowledge/sites/xiaohongshu.com/SITE.md +48 -0
- package/knowledge/sites/xiaohongshu.com/references/note-fields.md +18 -0
- package/knowledge/sites/youtube.com/SITE.md +49 -0
- package/knowledge/sites/youtube.com/references/innertube-fields.md +14 -0
- package/knowledge/sites/zhihu.com/SITE.md +47 -0
- package/knowledge/sites/zhihu.com/references/api-fields.md +21 -0
- package/lib/mobile/extension-catalog.js +41 -1
- package/lib/mobile/extension-data.js +224 -0
- package/lib/mobile/extension-dev.js +3 -1
- package/lib/mobile/file-ops.js +252 -0
- package/lib/mobile/history-derive.js +80 -4
- package/lib/mobile/host-bridge/endpoints.js +48 -6
- package/lib/mobile/host-bridge/event-synthesis.js +33 -6
- package/lib/mobile/jobs-control.js +118 -0
- package/lib/mobile/memory.js +635 -0
- package/lib/mobile/model-badges.js +77 -0
- package/lib/mobile/pet-tools.js +50 -4
- package/lib/mobile/pets.js +214 -18
- package/lib/mobile/phone/access.js +85 -0
- package/lib/mobile/phone/evidence.js +228 -0
- package/lib/mobile/phone/gate.js +290 -0
- package/lib/mobile/phone/grounding.js +314 -0
- package/lib/mobile/phone/guard.js +465 -0
- package/lib/mobile/phone/irreversible.js +88 -8
- package/lib/mobile/phone/knowledge-tools.js +665 -0
- package/lib/mobile/phone/learn.js +329 -0
- package/lib/mobile/phone/redact.js +237 -0
- package/lib/mobile/phone/risk.js +95 -0
- package/lib/mobile/phone/routes.js +28 -1
- package/lib/mobile/phone/service.js +23 -0
- package/lib/mobile/phone/site-commands.js +271 -0
- package/lib/mobile/phone/step-summary.js +201 -0
- package/lib/mobile/phone/stop-summary.js +235 -0
- package/lib/mobile/phone/tool-groups.js +125 -0
- package/lib/mobile/phone/tools.js +589 -0
- package/lib/mobile/phone/untrusted.js +65 -0
- package/lib/mobile/phone-knowledge.js +20 -4
- package/lib/mobile/schedule-edit.js +500 -0
- package/lib/mobile/schedule.js +5 -0
- package/lib/mobile/send-continuation.js +52 -0
- package/lib/mobile/session-models.js +27 -31
- package/lib/mobile/session-send.js +12 -12
- package/lib/mobile/skill-import.js +338 -0
- package/lib/mobile/skill-store.js +524 -0
- package/lib/routes/mobile-api.js +42 -0
- package/package.json +7 -6
- package/skills/skill-authoring/SKILL.md +46 -0
- package/skills/skill-authoring/references/examples.md +82 -0
- package/skills/skill-authoring/references/frontmatter-and-fields.md +46 -0
- package/skills/skill-authoring/scripts/check_skill.py +167 -0
- package/skills/skill-authoring/scripts/manifest.json +3 -0
- package/knowledge/sites/bilibili.com.md +0 -27
- package/knowledge/sites/github.com.md +0 -27
- package/knowledge/sites/x.com.md +0 -28
- package/knowledge/sites/xiaohongshu.com.md +0 -27
- package/knowledge/sites/youtube.com.md +0 -28
- package/knowledge/sites/zhihu.com.md +0 -27
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// Settings › Models › Web search's rows (the search plugin's part of
|
|
2
|
+
// Settings): the provider that searches for a model that can't, and the
|
|
3
|
+
// search APIs tried when no provider can, with their keys and order. Run
|
|
4
|
+
// after app.js, whose helpers it shares (el, segs, input, api, writingPrefs,
|
|
5
|
+
// dragRows, openPicker, sideEffortChip, savePrefs), and before start.js
|
|
6
|
+
// draws Settings.
|
|
7
|
+
|
|
8
|
+
// renderSearch: the web search APIs a model's search goes to when no
|
|
9
|
+
// provider can search (#419) — one row each, in the order they are tried,
|
|
10
|
+
// and a row to add one: which API, its key, and the address of one the user
|
|
11
|
+
// runs (SearXNG). They are set on their own; one magpie refuses is said in
|
|
12
|
+
// the row, what was typed kept.
|
|
13
|
+
let searchDraft = { vendor: "tavily", key: "", url: "", err: "" };
|
|
14
|
+
function renderSearch(s, keep) {
|
|
15
|
+
const box = $("#searchList");
|
|
16
|
+
box.replaceChildren();
|
|
17
|
+
renderSearcher(s, keep, box);
|
|
18
|
+
// rows go into the box, or into a search API's own unit (its row and its
|
|
19
|
+
// keys' row), which is dragged as one (#2286)
|
|
20
|
+
let into = box;
|
|
21
|
+
const row = (name, sub, ...tools) => {
|
|
22
|
+
const r = el("div", "row pref");
|
|
23
|
+
const who = el("div", "who");
|
|
24
|
+
who.append(el("div", "name", name), el("div", "sub", sub));
|
|
25
|
+
const val = el("div", "val");
|
|
26
|
+
val.append(...tools);
|
|
27
|
+
r.append(who, val);
|
|
28
|
+
into.append(r);
|
|
29
|
+
return r;
|
|
30
|
+
};
|
|
31
|
+
const set = (body, done) => writingPrefs(api("settings/search-api", body))
|
|
32
|
+
.then((ns) => { prefs = ns; searchDraft.err = ""; done?.(); renderSettings(); status(t("Saved"), "ok", 1500); })
|
|
33
|
+
.catch((e) => { searchDraft.err = e.message; status(e.message, "err"); renderSettings(); });
|
|
34
|
+
const vendors = s.searchVendors || [];
|
|
35
|
+
const d = searchDraft;
|
|
36
|
+
if (!vendors.some((v) => v.id === d.vendor)) d.vendor = vendors[0]?.id || "";
|
|
37
|
+
const vendor = () => vendors.find((v) => v.id === d.vendor) || {};
|
|
38
|
+
// the one of the picked vendor already added: Add then changes it, its
|
|
39
|
+
// key kept when none is typed
|
|
40
|
+
const saved = () => (s.searchAPIs || []).find((a) => a.vendor === d.vendor);
|
|
41
|
+
const pick = el("button", "proto pick search-vendor");
|
|
42
|
+
pick.type = "button";
|
|
43
|
+
pick.setAttribute("aria-label", t("Search API"));
|
|
44
|
+
const key = input(d.key, t("API key"), "password");
|
|
45
|
+
// any API's base URL (#1580): an endpoint of the user's own that answers
|
|
46
|
+
// as that API does; empty is the vendor's own, a SearXNG's is required
|
|
47
|
+
const url = input(d.url, "https://searx.example.com");
|
|
48
|
+
key.className = "words search-key";
|
|
49
|
+
url.className = "words search-url";
|
|
50
|
+
key.setAttribute("aria-label", t("API key"));
|
|
51
|
+
const get = el("button", "link", t("Get a key ↗"));
|
|
52
|
+
get.onclick = () => vendor().keysURL && api("open", { url: vendor().keysURL });
|
|
53
|
+
// a plugin's API that signs in as the plugin's provider takes no key: its
|
|
54
|
+
// accounts are the provider's, on Providers
|
|
55
|
+
const signs = el("span", "search-signs");
|
|
56
|
+
const draw = () => {
|
|
57
|
+
const v = vendor();
|
|
58
|
+
pick.replaceChildren(el("span", "", v.name || ""), svg(CHEV, 11, 1.6));
|
|
59
|
+
// the address it is set at shows in the field, so changing its key
|
|
60
|
+
// never puts the vendor's own back in its place
|
|
61
|
+
if (d.urlOf !== d.vendor) { d.url = saved()?.url || ""; d.urlOf = d.vendor; }
|
|
62
|
+
url.value = d.url;
|
|
63
|
+
url.placeholder = v.needURL ? "https://searx.example.com" : t("Base URL, empty for {url}", { url: v.base || "" });
|
|
64
|
+
url.setAttribute("aria-label", v.needURL ? t("Address") : t("Base URL"));
|
|
65
|
+
key.placeholder = v.needURL ? t("API key, if it needs one")
|
|
66
|
+
: saved()?.key ? t("A new key, or empty to keep {key}", { key: saved().key })
|
|
67
|
+
: v.keyOptional ? t("API key, or empty for its free search")
|
|
68
|
+
: t("API key, or several split by commas");
|
|
69
|
+
get.hidden = !v.keysURL || !!v.auth;
|
|
70
|
+
key.hidden = !!v.auth;
|
|
71
|
+
signs.hidden = !v.auth;
|
|
72
|
+
signs.textContent = v.auth ? t("Signs in with {name}", { name: v.auth.name }) : "";
|
|
73
|
+
};
|
|
74
|
+
pick.onclick = (e) => {
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
if (pick.classList.contains("open")) return closeProtoMenu();
|
|
77
|
+
openProtoMenu(pick, vendors.map((v) => ({ v: v.id, name: v.name, note: v.needURL ? t("your own") : "" })), d.vendor,
|
|
78
|
+
(id) => { d.vendor = id; draw(); }, "Search API");
|
|
79
|
+
};
|
|
80
|
+
const add = el("button", "text", t("Add"));
|
|
81
|
+
add.onclick = () => {
|
|
82
|
+
d.key = key.value.trim(); d.url = url.value.trim();
|
|
83
|
+
// one added before keeps its key when none is typed
|
|
84
|
+
// You.com is asked at its free search with none (#1626)
|
|
85
|
+
if (vendor().needURL ? !d.url : !d.key && !saved()?.key && !vendor().keyOptional && !vendor().auth) return (vendor().needURL ? url : key).focus();
|
|
86
|
+
set({ vendor: d.vendor, key: d.key, url: d.url },
|
|
87
|
+
() => { searchDraft = { vendor: d.vendor, key: "", url: "", err: "" }; });
|
|
88
|
+
};
|
|
89
|
+
for (const i of [key, url]) {
|
|
90
|
+
i.oninput = () => { d.key = key.value; d.url = url.value; };
|
|
91
|
+
i.onkeydown = (e) => { e.stopPropagation(); if (e.key === "Enter") add.onclick(); };
|
|
92
|
+
}
|
|
93
|
+
draw();
|
|
94
|
+
// the ones a search asks: one switched off is kept, never asked (#2286)
|
|
95
|
+
const asked = (s.searchAPIs || []).filter((a) => !a.off);
|
|
96
|
+
const apiFirst = s.searchFirst === "api" && asked.length > 0;
|
|
97
|
+
const by = s.searcher === "off" ? t("Provider search is off; only these APIs are used")
|
|
98
|
+
: !s.searchProvider ? t("No provider can search, so these are asked")
|
|
99
|
+
: apiFirst ? t("Asked before {who}, which searches when these fail", { who: s.searchProvider })
|
|
100
|
+
: t("Now done by {who}; these come after it", { who: s.searchProvider });
|
|
101
|
+
const head = row(t("Search APIs"), d.err || t("When a model can't search the web, magpie searches for it with these, in this order, and gives it what they found") + " · " + by,
|
|
102
|
+
pick, key, signs, get, add, url);
|
|
103
|
+
head.classList.add("rule-row", "search-add");
|
|
104
|
+
head.querySelector(".val").classList.add("rule-add");
|
|
105
|
+
if (d.err) head.querySelector(".sub").classList.add("err");
|
|
106
|
+
const units = [];
|
|
107
|
+
(s.searchAPIs || []).forEach((a, n) => {
|
|
108
|
+
const unit = el("div", "search-unit" + (a.off ? " off" : ""));
|
|
109
|
+
unit.dataset.vendor = a.vendor;
|
|
110
|
+
box.append(unit);
|
|
111
|
+
into = unit;
|
|
112
|
+
units.push(unit);
|
|
113
|
+
const x = el("button", "text", t("Remove"));
|
|
114
|
+
x.onclick = () => set({ vendor: a.vendor, remove: true });
|
|
115
|
+
// one that signs in says whose accounts it uses, or why it has none
|
|
116
|
+
const auth = vendors.find((v) => v.id === a.vendor)?.auth;
|
|
117
|
+
const signedIn = !auth ? ""
|
|
118
|
+
: !auth.signedIn ? t("Not signed in to {name}: sign in to it on Providers", { name: auth.name })
|
|
119
|
+
: !auth.on ? t("No account of {name} is on", { name: auth.name })
|
|
120
|
+
: t("Signed in to {name} · accounts on: {n}", { name: auth.name, n: auth.on });
|
|
121
|
+
const what = [signedIn || a.key || (a.ready ? "" : t("needs its key")), a.url].filter(Boolean).join(" · ");
|
|
122
|
+
const tools = [x];
|
|
123
|
+
if (a.key) {
|
|
124
|
+
// its saved key shown in its row and hidden again, as a provider's
|
|
125
|
+
// editor does (OnurBen on Discord)
|
|
126
|
+
const eye = el("button", "text search-eye", t("Show"));
|
|
127
|
+
eye.onclick = async () => {
|
|
128
|
+
const sub = r.querySelector(".sub");
|
|
129
|
+
if (eye.textContent === t("Show")) {
|
|
130
|
+
try { sub.textContent = [(await api("settings/search-key", { vendor: a.vendor })).key, a.url].filter(Boolean).join(" · "); } catch (e) { status(e.message, "err"); return; }
|
|
131
|
+
sub.classList.add("revealed");
|
|
132
|
+
eye.textContent = t("Hide");
|
|
133
|
+
} else {
|
|
134
|
+
sub.textContent = what;
|
|
135
|
+
sub.classList.remove("revealed");
|
|
136
|
+
eye.textContent = t("Show");
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const cp = el("button", "text search-copy", t("Copy"));
|
|
140
|
+
cp.onclick = async () => {
|
|
141
|
+
try { copy((await api("settings/search-key", { vendor: a.vendor })).key, t("API key"), cp); } catch (e) { status(e.message, "err"); }
|
|
142
|
+
};
|
|
143
|
+
tools.unshift(eye, cp);
|
|
144
|
+
}
|
|
145
|
+
// switched off it stays, key and all, and no search asks it (#2286)
|
|
146
|
+
const sw = el("button", "lib-switch use-credits search-on" + (a.off ? "" : " on"));
|
|
147
|
+
sw.type = "button";
|
|
148
|
+
sw.setAttribute("role", "switch");
|
|
149
|
+
sw.setAttribute("aria-checked", String(!a.off));
|
|
150
|
+
sw.setAttribute("aria-label", t("Ask {name}", { name: a.name }));
|
|
151
|
+
sw.title = a.off ? t("Off: kept with its key, and never asked") : t("On: asked in its turn");
|
|
152
|
+
sw.append(el("i"));
|
|
153
|
+
sw.onclick = () => {
|
|
154
|
+
if (sw.disabled) return;
|
|
155
|
+
sw.disabled = true;
|
|
156
|
+
set({ vendor: a.vendor, on: !!a.off });
|
|
157
|
+
};
|
|
158
|
+
tools.push(sw);
|
|
159
|
+
const r = row(`${n + 1}. ${a.name}`, a.off ? [t("Off"), what].filter(Boolean).join(" · ") : what, ...tools);
|
|
160
|
+
r.classList.add("search-api");
|
|
161
|
+
if (auth && !a.off && !(auth.signedIn && auth.on)) r.querySelector(".sub").classList.add("err");
|
|
162
|
+
// with-keys, not :has(), which Safari before 15.4 can't parse
|
|
163
|
+
if (a.keys > 1 || a.quota) { unit.classList.add("with-keys"); searchKeysRow(s, a, row, set); }
|
|
164
|
+
into = box;
|
|
165
|
+
});
|
|
166
|
+
searchArrange(box, units, set);
|
|
167
|
+
// which goes first when both a provider and a search API can search
|
|
168
|
+
// (#928); with only one of them there is nothing to choose
|
|
169
|
+
if (s.searchProvider && asked.length) {
|
|
170
|
+
const r = row(t("Search first with"), t("The other is asked when it fails"),
|
|
171
|
+
segs([["model", t("Model search")], ["api", t("Search APIs")]], apiFirst ? "api" : "model",
|
|
172
|
+
(v) => savePrefs({ ...keep, searchFirst: v === "api" ? "api" : "" })));
|
|
173
|
+
r.id = "searchFirstRow";
|
|
174
|
+
}
|
|
175
|
+
// a model whose provider searches the web by itself is given magpie's
|
|
176
|
+
// search instead, as every other model is (cybercaius on Discord). The
|
|
177
|
+
// row is always there: with nothing magpie can search with it can't be
|
|
178
|
+
// chosen, and says why and where to add a search ("开关在哪个位置")
|
|
179
|
+
{
|
|
180
|
+
const can = !!(s.searchProvider || asked.length);
|
|
181
|
+
const sw = segs([["own", t("Their own")], ["magpie", t("magpie's")]], s.searchAll ? "magpie" : "own",
|
|
182
|
+
(v) => writingPrefs(api("settings/search-all", { on: v === "magpie" }))
|
|
183
|
+
.then((ns) => { prefs = state.settings = ns; renderSettings(); }).catch(settingsErr));
|
|
184
|
+
if (!can) for (const b of sw.querySelectorAll("button")) b.disabled = true;
|
|
185
|
+
const r = row(t("Models that search by themselves"), can ? t("Search with their provider's own search, or with magpie's, as every other model does")
|
|
186
|
+
: t("magpie has nothing to search with: add a Search API above, or pick a provider under Searches for other models"), sw);
|
|
187
|
+
r.id = "searchAllRow";
|
|
188
|
+
r.classList.add("search-all-row", ...(can ? [] : ["off"]));
|
|
189
|
+
}
|
|
190
|
+
// how many results a search that names no count is answered with, as
|
|
191
|
+
// Claude Code's WebSearch and Codex's web_search never do (#5545,
|
|
192
|
+
// mdwsk88); a count set by hand in settings.json is offered too
|
|
193
|
+
{
|
|
194
|
+
const cur = s.searchCount > 0 ? Math.min(s.searchCount, 20) : 6;
|
|
195
|
+
const ns = [...new Set([3, 6, 10, 15, 20, cur])].sort((a, b) => a - b);
|
|
196
|
+
const r = row(t("Results per search"), t("More results give the model more to read: each carries up to 1500 bytes of its page"),
|
|
197
|
+
segs(ns.map((n) => [String(n), String(n)]), String(cur),
|
|
198
|
+
(v) => writingPrefs(api("settings/search-count", { n: Number(v) }))
|
|
199
|
+
.then((ns) => { prefs = state.settings = ns; renderSettings(); }).catch(settingsErr)));
|
|
200
|
+
r.id = "searchCountRow";
|
|
201
|
+
}
|
|
202
|
+
// the same search as an MCP server of the gateway's (Kayphoon on
|
|
203
|
+
// Discord), for an agent to mount as its web_search and web_fetch tools
|
|
204
|
+
const mcpURL = mcpAddress(s, "/mcp/magpie/web-search");
|
|
205
|
+
const mcp = row(t("MCP server"), t("Cursor, Claude Desktop and other agents can add this search as an MCP server (Streamable HTTP), with a web_search tool and a web_fetch tool that reads a page by its URL. From another computer, send a gateway key as Authorization: Bearer <key>."),
|
|
206
|
+
el("code", "", mcpURL), copyBtn(mcpURL, t("Address")));
|
|
207
|
+
mcp.id = "searchMcpRow";
|
|
208
|
+
// The Library gives this search to the agents picked for it, as Magpie
|
|
209
|
+
// Web Search, and lists it under its MCP servers: an address alone had
|
|
210
|
+
// the reader look for it there and not find it (lutra_fs on Discord).
|
|
211
|
+
// As Images' Magpie Image (#2091), the row says how to get there from
|
|
212
|
+
// where the reader is: gateway mode has no Library, and the Library can
|
|
213
|
+
// be turned off
|
|
214
|
+
mcp.querySelector(".who").append(...libWay("magpie-web-search", t("Give agents Magpie Web Search…"), "search-mcp-lib"));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// searchArrange: the search APIs are asked in the order they are listed,
|
|
218
|
+
// and put in another by dragging one anywhere off its buttons, or with
|
|
219
|
+
// Alt+↑/↓ on it (#2286, as routing's groups are, #779). The new order
|
|
220
|
+
// shows at once and is put back when it can't be saved.
|
|
221
|
+
let searchArranging = false;
|
|
222
|
+
function searchArrange(box, units, set) {
|
|
223
|
+
if (units.length < 2) return;
|
|
224
|
+
const move = (unit, to) => {
|
|
225
|
+
const from = units.indexOf(unit);
|
|
226
|
+
to = Math.max(0, Math.min(units.length - 1, to));
|
|
227
|
+
for (const u of units) u.style.transform = "";
|
|
228
|
+
if (to === from) return;
|
|
229
|
+
// moving a node in the page takes the keyboard off it
|
|
230
|
+
const focus = document.activeElement === unit;
|
|
231
|
+
const order = units.map((u) => u.dataset.vendor);
|
|
232
|
+
order.splice(to, 0, ...order.splice(from, 1));
|
|
233
|
+
const anchor = units.at(-1).nextSibling;
|
|
234
|
+
const next = order.map((v) => units.find((u) => u.dataset.vendor === v));
|
|
235
|
+
for (const u of next) box.insertBefore(u, anchor);
|
|
236
|
+
// a second move before the page is drawn again goes from this order
|
|
237
|
+
units.splice(0, units.length, ...next);
|
|
238
|
+
next.forEach((u, i) => {
|
|
239
|
+
const name = u.querySelector(".search-api .name");
|
|
240
|
+
if (name) name.textContent = name.textContent.replace(/^\d+\./, `${i + 1}.`);
|
|
241
|
+
});
|
|
242
|
+
if (focus) unit.focus({ preventScroll: true });
|
|
243
|
+
set({ arrange: order }, () => {
|
|
244
|
+
if (focus) requestAnimationFrame(() => document.querySelector(`#searchList .search-unit[data-vendor="${CSS.escape(unit.dataset.vendor)}"]`)?.focus({ preventScroll: true }));
|
|
245
|
+
});
|
|
246
|
+
// a refused order: set draws the page again from what is saved, the
|
|
247
|
+
// order as it was
|
|
248
|
+
};
|
|
249
|
+
for (const unit of units) {
|
|
250
|
+
const name = unit.querySelector(".search-api .name")?.textContent || "";
|
|
251
|
+
unit.tabIndex = 0;
|
|
252
|
+
unit.setAttribute("role", "group");
|
|
253
|
+
unit.setAttribute("aria-label", name + " · " + t("Drag to reorder · Alt+↑/↓ to move"));
|
|
254
|
+
unit.title = t("Drag to reorder · Alt+↑/↓ to move");
|
|
255
|
+
unit.prepend(el("span", "grip"));
|
|
256
|
+
unit.addEventListener("click", (e) => {
|
|
257
|
+
if (unit.dataset.dragged) { e.preventDefault(); e.stopImmediatePropagation(); }
|
|
258
|
+
}, true);
|
|
259
|
+
unit.onkeydown = (e) => {
|
|
260
|
+
if (e.target !== unit || !e.altKey || !["ArrowUp", "ArrowDown"].includes(e.key) || searchArranging) return;
|
|
261
|
+
e.preventDefault(); e.stopPropagation();
|
|
262
|
+
move(unit, units.indexOf(unit) + (e.key === "ArrowUp" ? -1 : 1));
|
|
263
|
+
};
|
|
264
|
+
unit.onpointerdown = (e) => {
|
|
265
|
+
if (searchArranging || e.target.closest("input, textarea, select, a, button, code, .segs, .sub.revealed")) return;
|
|
266
|
+
searchArranging = dragRows(e, unit, unit, box, units, (to) => move(unit, to), () => {},
|
|
267
|
+
() => { searchArranging = false; });
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// searchKeysRow: under a search API with several keys, or one whose keys
|
|
273
|
+
// quota magpie can read (Tavily's), a row of its keys (SlouchyQuill on
|
|
274
|
+
// Discord): how a search picks among them, in turn (the default) or in
|
|
275
|
+
// order, and what each has used of its limit, read once as the page is
|
|
276
|
+
// drawn and again on Check again. A read that fails says unknown and why,
|
|
277
|
+
// never 0; a key a search set aside lately says why and until when.
|
|
278
|
+
const searchQuotaSeen = {};
|
|
279
|
+
function searchKeysRow(s, a, row, set) {
|
|
280
|
+
const tools = [];
|
|
281
|
+
if (a.keys > 1) tools.push(segs([["", t("In turn")], ["order", t("In order")]], a.pick || "",
|
|
282
|
+
(v) => set({ vendor: a.vendor, pick: v || "turns" })));
|
|
283
|
+
const mine = el("div", "search-quota");
|
|
284
|
+
const seenAs = [a.key, a.url].join(" ");
|
|
285
|
+
const ask = (fresh) => {
|
|
286
|
+
searchQuotaSeen[a.vendor] = { as: seenAs, loading: true };
|
|
287
|
+
draw();
|
|
288
|
+
api("settings/search-quota", { vendor: a.vendor, fresh })
|
|
289
|
+
.then((r) => { searchQuotaSeen[a.vendor] = { as: seenAs, keys: r.keys || [] }; })
|
|
290
|
+
.catch((e) => { searchQuotaSeen[a.vendor] = { as: seenAs, err: e.message }; })
|
|
291
|
+
.finally(draw);
|
|
292
|
+
};
|
|
293
|
+
// the list in the page now: Settings may have been drawn again while
|
|
294
|
+
// the read was out
|
|
295
|
+
const draw = () => {
|
|
296
|
+
const list = document.querySelector(`.search-keys[data-vendor="${a.vendor}"] .search-quota`) || mine;
|
|
297
|
+
const seen = searchQuotaSeen[a.vendor];
|
|
298
|
+
list.replaceChildren();
|
|
299
|
+
if (!seen || seen.loading) return list.append(el("div", "faint", t("Reading what each key has used…")));
|
|
300
|
+
if (seen.err) return list.append(el("div", "err", t("Unknown: {why}", { why: seen.err })));
|
|
301
|
+
const num = (n) => Number(n).toLocaleString(intlLang());
|
|
302
|
+
for (const q of seen.keys) {
|
|
303
|
+
const line = el("div", "quota-key");
|
|
304
|
+
const said = !q.known ? t("Unknown: {why}", { why: q.err || "" })
|
|
305
|
+
: q.unlimited ? t("{used} used, no limit", { used: num(q.used) })
|
|
306
|
+
: t("{used} of {limit} used", { used: num(q.used), limit: num(q.limit) });
|
|
307
|
+
const notes = [];
|
|
308
|
+
if (q.known && q.of === "plan") notes.push(q.plan ? t("{plan} plan, shared by its account's keys", { plan: q.plan }) : t("the plan's, shared by its account's keys"));
|
|
309
|
+
if (q.paygoLeft > 0) notes.push(t("{n} pay-as-you-go left", { n: num(q.paygoLeft) }));
|
|
310
|
+
if (q.resting) notes.push(t("asked last until {time}: {why}", { time: hhmm(q.restUntil),
|
|
311
|
+
why: { "turned away": t("turned away"), "out of credit": t("out of credit"), "rate limited": t("rate limited"), "used up": t("used up") }[q.resting] || q.resting }));
|
|
312
|
+
const used = el("span", q.known ? (q.usedUp ? "used warn" : "used") : "used err", said);
|
|
313
|
+
line.append(el("code", "", q.key), used);
|
|
314
|
+
if (q.usedUp) line.append(el("span", "warn", t("used up")));
|
|
315
|
+
if (notes.length) line.append(el("span", "faint", notes.join(" · ")));
|
|
316
|
+
list.append(line);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
if (a.quota) {
|
|
320
|
+
const again = el("button", "text search-quota-again", t("Check again"));
|
|
321
|
+
again.onclick = () => ask(true);
|
|
322
|
+
tools.push(again);
|
|
323
|
+
}
|
|
324
|
+
const r = row(t("Keys of {name}", { name: a.name }),
|
|
325
|
+
a.keys > 1 ? t("In turn spreads searches over the keys; in order uses the first until it is refused. A key refused lately is asked last.") : t("What the key has used of its limit"),
|
|
326
|
+
...tools);
|
|
327
|
+
r.classList.add("search-keys");
|
|
328
|
+
r.dataset.vendor = a.vendor;
|
|
329
|
+
if (a.quota) {
|
|
330
|
+
r.querySelector(".who").append(mine);
|
|
331
|
+
if (searchQuotaSeen[a.vendor]?.as !== seenAs) ask(false);
|
|
332
|
+
else draw();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// renderSearcher: the provider that searches the web for a model that
|
|
337
|
+
// can't — the one magpie picks, or one named, by itself (its small model)
|
|
338
|
+
// or with a model of it. One named that is gone, off or can't search gives
|
|
339
|
+
// way to magpie's pick, which the row says. Relays said to search can be
|
|
340
|
+
// named, but are never picked automatically (#359), and the row says so.
|
|
341
|
+
function renderSearcher(s, keep, box) {
|
|
342
|
+
const choices = s.searchChoices || [];
|
|
343
|
+
const v = s.searcher || "";
|
|
344
|
+
const off = v === "off";
|
|
345
|
+
const named = (id) => {
|
|
346
|
+
const [pid, ...rest] = id.split("/");
|
|
347
|
+
const c = choices.find((x) => x.id === pid);
|
|
348
|
+
if (!c) return id;
|
|
349
|
+
if (c.service || (c.search && rest.join("/") === "@search")) return `${c.name} · ${t("its web search")}`;
|
|
350
|
+
if (!rest.length) return `${c.name} · ${c.smallName || c.small}`;
|
|
351
|
+
const m = c.models.find((x) => x.id === id);
|
|
352
|
+
return `${m ? m.name : rest.join("/")} · ${c.name}`;
|
|
353
|
+
};
|
|
354
|
+
const icOf = (id) => choices.find((x) => x.id === id.split("/")[0])?.icon;
|
|
355
|
+
const r = el("div", "row pref searcher-row");
|
|
356
|
+
const who = el("div", "who");
|
|
357
|
+
const sub = el("div", "sub", off ? (s.searchAll ? t("When provider search is off, only configured Search APIs are used, for every model. Without a Search API, magpie does not add a search tool.") : t("When provider search is off, only configured Search APIs are used. Without a Search API, magpie does not add a search tool. Providers' native web search is unchanged.")) : t("When a model can't search the web directly, the selected provider searches for it and returns the results. Searches may use the service's quota or incur charges; if the provider magpie picks fails, it tries other available sources, and a provider you select here gives way to the ones you add after it, in their order, then, when it can't search at all (out of quota or rate-limited), to other available sources, then to your Search APIs."));
|
|
358
|
+
if (!off && v && s.searchUnused) {
|
|
359
|
+
const why = { gone: t("it is no longer in magpie"), off: t("it is turned off"), cant: t("it can't search the web by itself"), nomodel: t("it lists no model") }[s.searchUnused] || s.searchUnused;
|
|
360
|
+
sub.append(" · ", el("span", "warn searcher-unused", t("{who} isn't used: {why}, so magpie picks one", { who: named(v), why })));
|
|
361
|
+
}
|
|
362
|
+
// the one named didn't search last time, and no other provider searched
|
|
363
|
+
// in its place (Gearclickist on Discord: Grok picked answered without
|
|
364
|
+
// searching, and Codex's pricier model searched instead, unasked). First,
|
|
365
|
+
// so it reads before the help.
|
|
366
|
+
const failed = s.searchFailed;
|
|
367
|
+
if (!off && v && !s.searchUnused && failed) {
|
|
368
|
+
const why = failed.notSearched ? t("it answered without searching the web, so its account or plan may not offer web search") : failed.nothingFound ? t("its reply named no page it found") : failed.why || t("it failed");
|
|
369
|
+
const said = failed.instead ? t("{who} didn't search {when}: {why}. {next} searched in its place.", { who: named(failed.searcher), when: ago(failed.at), why, next: named(failed.instead) })
|
|
370
|
+
: (s.searcherThen || []).length ? t("{who} didn't search {when}: {why}. The ones after it didn't search either; add another provider after it, or a Search API.", { who: named(v), when: ago(failed.at), why })
|
|
371
|
+
: t("{who} didn't search {when}: {why}. No other provider searched in its place; pick Automatic or another provider, or add a Search API.", { who: named(v), when: ago(failed.at), why });
|
|
372
|
+
sub.prepend(el("span", "warn searcher-failed", said), " · ");
|
|
373
|
+
}
|
|
374
|
+
const plans = choices.filter((c) => c.service).map((c) => c.name);
|
|
375
|
+
if (!off && plans.length) sub.append(" · ", el("span", "searcher-own",
|
|
376
|
+
t("A Kimi Code plan ({names}) searches for its own models first, with its web search; for other models only when named here", { names: plans.join(", ") })));
|
|
377
|
+
const googles = choices.filter((c) => c.own).map((c) => c.name);
|
|
378
|
+
if (!off && googles.length) sub.append(" · ", el("span", "searcher-own",
|
|
379
|
+
t("{names} search for their own models first, with Gemini's Google Search", { names: googles.join(", ") })));
|
|
380
|
+
if (!off && s.searchRelays?.length) sub.append(" · ", el("span", "searcher-relays",
|
|
381
|
+
t("These relays must be selected manually and are not used for automatic selection or fallback: {names}.", { names: s.searchRelays.join(", ") })));
|
|
382
|
+
// Being left out can also mean no usable model, not just no search support.
|
|
383
|
+
if (!off && s.searchLeftOut?.length) sub.append(" · ", el("span", "searcher-left-out",
|
|
384
|
+
t("These providers can't be selected to search for other models with the current configuration: {names}. Their models can still get search results through other available search providers or configured search APIs.", { names: s.searchLeftOut.length > 6
|
|
385
|
+
? t("{names} and {n} more", { names: s.searchLeftOut.slice(0, 5).join(", "), n: s.searchLeftOut.length - 5 })
|
|
386
|
+
: s.searchLeftOut.join(", ") })));
|
|
387
|
+
who.append(el("div", "name", t("Searches for other models")), sub);
|
|
388
|
+
const b = el("button", "rt-cond on searcher-pick");
|
|
389
|
+
b.type = "button";
|
|
390
|
+
b.setAttribute("aria-label", t("Searches for other models"));
|
|
391
|
+
if (off) b.append(el("span", "", t("Off")));
|
|
392
|
+
else if (v && !s.searchUnused) b.append(icon(icOf(v) || "generic"), el("span", "", named(v)));
|
|
393
|
+
else {
|
|
394
|
+
if (s.searchAuto) b.append(icon(choices[0]?.icon || "generic"));
|
|
395
|
+
b.append(el("span", "", t("Automatic") + " · " + (s.searchAuto || t("no provider that searches"))));
|
|
396
|
+
}
|
|
397
|
+
const options = [{ value: "", label: t("Automatic"), note: s.searchAuto || t("no provider that searches"), reset: true },
|
|
398
|
+
{ value: "off", label: t("Off"), note: t("Search APIs"), reset: true }];
|
|
399
|
+
for (const c of choices) {
|
|
400
|
+
if (c.service) {
|
|
401
|
+
options.push({ value: c.id, label: t("its web search"), note: c.name, icon: c.icon, group: c.name });
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
// a ChatGPT account searches by its own web search, with no model's
|
|
405
|
+
// tokens spent (lucy_85908 on Discord); its models stay to be picked
|
|
406
|
+
if (c.search) options.push({ value: `${c.id}/@search`, label: t("its web search"), note: c.name, icon: c.icon, group: c.name });
|
|
407
|
+
// its small model is offered once (Player on Discord): the model of the
|
|
408
|
+
// same name below it searched just the same, "<provider>/<small>" being
|
|
409
|
+
// that model named rather than magpie's pick, so a pick saved so is
|
|
410
|
+
// ticked here. No "via magpie": no agent asks for these
|
|
411
|
+
const small = c.small ? `${c.id}/${c.small}` : "";
|
|
412
|
+
options.push({ value: v === small ? small : c.id, label: t("{model}, its small model", { model: c.smallName || c.small }), note: c.name, icon: c.icon, group: c.name });
|
|
413
|
+
for (const m of c.models) if (m.id !== small) options.push({ value: m.id, label: m.name || m.id, note: c.name, icon: c.icon, group: c.name });
|
|
414
|
+
}
|
|
415
|
+
b.onclick = (ev) => openPicker({ id: "", name: "", fields: [] }, { key: "searcher", label: "model", value: v, options,
|
|
416
|
+
onPick: (id) => { if (id !== v) savePrefs({ ...keep, searcher: id }); } }, b, ev);
|
|
417
|
+
const val = el("div", "val");
|
|
418
|
+
val.append(b);
|
|
419
|
+
if (!off) sideEffortChip(val, s.searchEfforts, s.searcherEffort || "", s.searchEffortNow, "searcher-effort",
|
|
420
|
+
"none sent, the model's own", (e) => savePrefs({ ...keep, searcherEffort: e }));
|
|
421
|
+
r.append(who, val);
|
|
422
|
+
box.append(r);
|
|
423
|
+
if (!off && v && !s.searchUnused) renderSearcherThen(s, keep, box, options, named, icOf);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// renderSearcherThen: the providers asked, in their order, when the one
|
|
427
|
+
// named above fails to search, and no other (axelburks on Discord: "代搜
|
|
428
|
+
// 供应商能不能支持设置多个,这样可以一个失败了换另一个供应商"). Each
|
|
429
|
+
// is a chip that changes or removes it, and Add puts one at the end.
|
|
430
|
+
function renderSearcherThen(s, keep, box, options, named, icOf) {
|
|
431
|
+
const v = s.searcher || "";
|
|
432
|
+
const then = s.searcherThen || [];
|
|
433
|
+
const save = (list) => savePrefs({ ...keep, searcherThen: list });
|
|
434
|
+
// what can still be added: not the one above, nor one listed already
|
|
435
|
+
const free = (keepOne) => options.filter((o) => !o.reset && o.value !== v && (o.value === keepOne || !then.includes(o.value)));
|
|
436
|
+
const r = el("div", "row pref searcher-then");
|
|
437
|
+
const who = el("div", "who");
|
|
438
|
+
who.append(el("div", "name", t("If it fails")),
|
|
439
|
+
el("div", "sub", t("These providers search in its place, in this order, when the one above fails; no other provider does.")));
|
|
440
|
+
const val = el("div", "val searcher-then-list");
|
|
441
|
+
then.forEach((x, n) => {
|
|
442
|
+
const b = el("button", "rt-cond on searcher-then-pick");
|
|
443
|
+
b.type = "button";
|
|
444
|
+
b.append(el("span", "searcher-then-n", `${n + 1}.`), icon(icOf(x) || "generic"), el("span", "", named(x)));
|
|
445
|
+
b.onclick = (ev) => openPicker({ id: "", name: "", fields: [] }, { key: "searcherThen", label: "model", value: x,
|
|
446
|
+
options: [{ value: "", label: t("Remove"), reset: true }, ...free(x)],
|
|
447
|
+
onPick: (id) => { if (id !== x) save(id ? then.map((y, i) => i === n ? id : y) : then.filter((_, i) => i !== n)); } }, b, ev);
|
|
448
|
+
val.append(b);
|
|
449
|
+
});
|
|
450
|
+
const left = free("");
|
|
451
|
+
if (left.length) {
|
|
452
|
+
const add = el("button", "text searcher-then-add", t("Add"));
|
|
453
|
+
add.type = "button";
|
|
454
|
+
add.onclick = (ev) => openPicker({ id: "", name: "", fields: [] }, { key: "searcherThen", label: "model", value: "", options: left,
|
|
455
|
+
onPick: (id) => { if (id) save([...then, id]); } }, add, ev);
|
|
456
|
+
val.append(add);
|
|
457
|
+
}
|
|
458
|
+
r.append(who, val);
|
|
459
|
+
box.append(r);
|
|
460
|
+
}
|