dsh-mobile-plugin 0.5.28 → 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,459 @@
|
|
|
1
|
+
// Settings › Network and sharing's gateway rows (the gateway plugin's part
|
|
2
|
+
// of Settings): its port, model ids without the provider, sharing on the
|
|
3
|
+
// local network and the addresses let in, and the web pages that may call
|
|
4
|
+
// it. Run after app.js, whose helpers it shares (el, segs, input, api,
|
|
5
|
+
// writingPrefs), and before start.js draws Settings.
|
|
6
|
+
|
|
7
|
+
// renderPort: the gateway's address on this computer (Magic_zero on Discord:
|
|
8
|
+
// 3425 taken by another program, or one easier to tell apart). Only its
|
|
9
|
+
// port, 1024 up, is typed here; magpie moves its gateway there and every
|
|
10
|
+
// agent it connected with it, or says why it can't (another program has
|
|
11
|
+
// it). MAGPIE_ADDR, a whole host:port, comes first and the field shows it,
|
|
12
|
+
// off (Chicring on Discord: the row was titled a port while it showed one).
|
|
13
|
+
let portDraft = { value: null, err: "" };
|
|
14
|
+
// portSays is the gateway's word on a port it couldn't take, in the page's
|
|
15
|
+
// language: it says the port, so it is matched rather than looked up
|
|
16
|
+
function portSays(m) {
|
|
17
|
+
let x;
|
|
18
|
+
if ((x = /^port (\d+) is in use by another program/.exec(m))) return t("Port {p} is in use by another program: pick another one", { p: x[1] });
|
|
19
|
+
if ((x = /^another magpie serves the gateway at (\S+):/.exec(m))) return t("Another magpie serves the gateway at {url}: set its port there, or quit it first", { url: x[1] });
|
|
20
|
+
if (/from 1024 to 65535/.test(m)) return t("A port is a number from 1024 to 65535");
|
|
21
|
+
return t(m);
|
|
22
|
+
}
|
|
23
|
+
function renderPort(s) {
|
|
24
|
+
const box = $("#portList");
|
|
25
|
+
const r = el("div", "row pref port-row");
|
|
26
|
+
const who = el("div", "who");
|
|
27
|
+
who.append(el("div", "name", t("Gateway address")));
|
|
28
|
+
const sub = el("div", "sub");
|
|
29
|
+
const d = portDraft;
|
|
30
|
+
const now = s.addrEnv ? "" : String(s.port || 3425);
|
|
31
|
+
const field = input(d.value ?? now, "3425");
|
|
32
|
+
field.className = "words gateway-port";
|
|
33
|
+
field.inputMode = "numeric";
|
|
34
|
+
field.setAttribute("aria-label", t("Gateway address"));
|
|
35
|
+
const save = el("button", "text", t("Apply"));
|
|
36
|
+
if (s.addrEnv) {
|
|
37
|
+
field.value = s.addrEnv;
|
|
38
|
+
field.disabled = save.disabled = true;
|
|
39
|
+
sub.textContent = t("MAGPIE_ADDR={addr} sets the gateway’s whole address, host:port: unset it to set the port here", { addr: s.addrEnv });
|
|
40
|
+
} else if (d.err) {
|
|
41
|
+
sub.textContent = d.err;
|
|
42
|
+
sub.classList.add("err");
|
|
43
|
+
} else sub.textContent = t("Only the port is set here, 1024 to 65535; MAGPIE_ADDR=host:port sets the whole address instead. Agents magpie connected move with it. Now {url}", { url: s.gateway || "" });
|
|
44
|
+
who.append(sub);
|
|
45
|
+
save.onclick = () => {
|
|
46
|
+
const p = Number(field.value.trim());
|
|
47
|
+
if (!Number.isInteger(p) || p < 1024 || p > 65535) {
|
|
48
|
+
d.value = field.value; d.err = t("A port is a number from 1024 to 65535");
|
|
49
|
+
return renderPort(s);
|
|
50
|
+
}
|
|
51
|
+
if (p === (s.port || 3425)) { d.value = null; d.err = ""; return renderPort(s); }
|
|
52
|
+
save.disabled = true;
|
|
53
|
+
writingPrefs(api("settings/port", { port: p }).then((res) => res.settings && (prefs = res.settings, res)))
|
|
54
|
+
.then((res) => {
|
|
55
|
+
portDraft = { value: null, err: "" };
|
|
56
|
+
const n = res.port?.moved?.length || 0;
|
|
57
|
+
if (res.port?.error) status(res.port.error, "err");
|
|
58
|
+
else status(n ? t("Gateway on port {p}; {n} agents moved with it", { p, n }) : t("Gateway on port {p}", { p }), "ok", 2500);
|
|
59
|
+
// the sidebar's gateway line, and the list's address the pages
|
|
60
|
+
// copy, say where it is now, not the port before (noahhrreion on
|
|
61
|
+
// Discord: it kept the old one until Providers was opened)
|
|
62
|
+
if (res.settings?.gateway) renderSideGw({ ...sideGwAt, url: res.settings.gateway, running: true });
|
|
63
|
+
if (providers) loadProviders().catch(() => {});
|
|
64
|
+
renderSettings();
|
|
65
|
+
})
|
|
66
|
+
.catch((e) => { d.value = field.value; d.err = portSays(e.message); status(d.err, "err"); renderSettings(); });
|
|
67
|
+
};
|
|
68
|
+
field.oninput = () => { d.value = field.value; };
|
|
69
|
+
field.onkeydown = (e) => { e.stopPropagation(); if (e.key === "Enter") save.onclick(); if (e.key === "Escape") { portDraft = { value: null, err: "" }; renderPort(s); } };
|
|
70
|
+
const val = el("div", "val");
|
|
71
|
+
val.append(field, save);
|
|
72
|
+
r.append(who, val);
|
|
73
|
+
box.replaceChildren(r); // swapped whole: the list is never laid out empty
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// renderBareIds: Model ids without the provider (settings.bareModelIDs,
|
|
77
|
+
// love1sbug on X, moving from CLIProxyAPI): the gateway's /v1/models
|
|
78
|
+
// lists each model by its own id as well, gpt-5.6-sol beside
|
|
79
|
+
// codex/gpt-5.6-sol. Posted on its own; off by default.
|
|
80
|
+
function renderBareIds(s) {
|
|
81
|
+
renderBusyLoad(s); // the row under it
|
|
82
|
+
$("#bareIdsSegs").replaceChildren(segs([[false, t("Off")], [true, t("On")]], !!s.bareModelIDs, (on) =>
|
|
83
|
+
writingPrefs(api("settings/bare-ids", { on })).then((ns) => { prefs = state.settings = ns; renderSettings(); })
|
|
84
|
+
.catch((e) => { status(t(e.message), "err"); renderSettings(); })));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// renderBusyLoad: the load past which a model its plugin says is busy
|
|
88
|
+
// (magpie.load) goes behind the others (settings.BusyLoad, axelburks on
|
|
89
|
+
// Discord). Custom… opens a field for any other number, 1 to 9999, which
|
|
90
|
+
// is then shown as its own choice; Enter or Apply saves it, Escape closes
|
|
91
|
+
let busyDraft = null;
|
|
92
|
+
const BUSY_CUSTOM = -1;
|
|
93
|
+
function renderBusyLoad(s) {
|
|
94
|
+
const at = s.busyLoad || 0, sub = $("#busyLoadRow .sub"), d = busyDraft;
|
|
95
|
+
const put = (n) => writingPrefs(api("settings/busy-load", { at: n })).then((ns) => { busyDraft = null; prefs = state.settings = ns; renderSettings(); })
|
|
96
|
+
.catch((e) => { status(t(e.message), "err"); renderSettings(); });
|
|
97
|
+
sub.dataset.en ??= sub.textContent; // its English, as translateStatic keeps it
|
|
98
|
+
sub.classList.toggle("err", !!d?.err);
|
|
99
|
+
sub.textContent = d ? d.err || t("A load is in percent, as the plugin reports it: 100 is all its vendor serves at once, and past 100 requests queue (200 is twice that). Type 1 to 9999") : t(sub.dataset.en);
|
|
100
|
+
if (d) {
|
|
101
|
+
const field = input(d.value, "150");
|
|
102
|
+
field.className = "words busy-load-at";
|
|
103
|
+
field.style.cssText = "width:84px;flex:none";
|
|
104
|
+
field.inputMode = "numeric";
|
|
105
|
+
field.setAttribute("aria-label", t("Busy models last"));
|
|
106
|
+
const save = el("button", "text", t("Apply")), no = el("button", "text", t("Cancel"));
|
|
107
|
+
const close = () => { busyDraft = null; renderBusyLoad(s); };
|
|
108
|
+
save.onclick = () => {
|
|
109
|
+
const v = field.value.trim(), n = Number(v);
|
|
110
|
+
if (!/^\d+$/.test(v) || n < 1 || n > 9999) { busyDraft = { value: field.value, err: t("Type a whole number from 1 to 9999") }; renderBusyLoad(s); return $("#busyLoadRow .busy-load-at").focus(NO_SCROLL); }
|
|
111
|
+
save.disabled = true;
|
|
112
|
+
put(n);
|
|
113
|
+
};
|
|
114
|
+
no.onclick = close;
|
|
115
|
+
field.oninput = () => { d.value = field.value; };
|
|
116
|
+
field.onkeydown = (e) => { e.stopPropagation(); if (e.key === "Enter") save.onclick(); if (e.key === "Escape") close(); };
|
|
117
|
+
const val = el("div", "val");
|
|
118
|
+
val.append(field, save, no);
|
|
119
|
+
return $("#busyLoadSegs").replaceChildren(val);
|
|
120
|
+
}
|
|
121
|
+
const opts = [0, 50, 70, 80, 90];
|
|
122
|
+
if (!opts.includes(at)) opts.push(at);
|
|
123
|
+
opts.sort((a, b) => a - b);
|
|
124
|
+
$("#busyLoadSegs").replaceChildren(segs([...opts.map((n) => [n, n ? t("Over {n}%", { n }) : t("Off")]), [BUSY_CUSTOM, t("Custom…")]], at, (n) => {
|
|
125
|
+
if (n !== BUSY_CUSTOM) return put(n);
|
|
126
|
+
busyDraft = { value: [0, 50, 70, 80, 90].includes(at) ? "" : String(at), err: "" };
|
|
127
|
+
renderBusyLoad(s);
|
|
128
|
+
$("#busyLoadRow .busy-load-at").focus(NO_SCROLL);
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// renderCORS: the web pages whose scripts may call the gateway from a
|
|
133
|
+
// browser (#1051), by origin — a row to add one, and one row each to take
|
|
134
|
+
// it away. A page listed calls with a gateway key; none listed, no page
|
|
135
|
+
// can read what the gateway answers, as before.
|
|
136
|
+
let corsDraft = { value: "", err: "" };
|
|
137
|
+
// corsSays is the gateway's word on an origin it didn't take, in the
|
|
138
|
+
// page's language
|
|
139
|
+
function corsSays(m) {
|
|
140
|
+
if (/wildcard/.test(m)) return t("Name each origin: a wildcard would let every web page use magpie");
|
|
141
|
+
if (/is not an (extension's )?origin/.test(m)) return t("Write an origin as http://localhost:3000 or https://app.example.com, or a browser extension's as chrome-extension://, moz-extension:// or safari-web-extension:// followed by its ID");
|
|
142
|
+
return t(m);
|
|
143
|
+
}
|
|
144
|
+
function renderCORS(s) {
|
|
145
|
+
renderWebPage(); // Browser access, the section under this one
|
|
146
|
+
const box = $("#corsList");
|
|
147
|
+
box.replaceChildren();
|
|
148
|
+
const origins = s.corsOrigins || [];
|
|
149
|
+
const d = corsDraft;
|
|
150
|
+
const set = (next, done) => writingPrefs(api("settings/cors", { origins: next }))
|
|
151
|
+
.then((ns) => { prefs = ns; d.err = ""; done?.(); renderSettings(); status(t("Saved"), "ok", 1500); })
|
|
152
|
+
.catch((e) => { d.err = corsSays(e.message); status(d.err, "err"); renderSettings(); });
|
|
153
|
+
const field = input(d.value, "http://localhost:3000");
|
|
154
|
+
field.className = "words rule-match";
|
|
155
|
+
field.setAttribute("aria-label", t("Web page origin"));
|
|
156
|
+
field.autocomplete = "off";
|
|
157
|
+
field.spellcheck = false;
|
|
158
|
+
const add = el("button", "text", t("Add"));
|
|
159
|
+
add.onclick = () => {
|
|
160
|
+
d.value = field.value.trim();
|
|
161
|
+
if (!d.value) return field.focus();
|
|
162
|
+
set([...origins, d.value], () => { corsDraft = { value: "", err: "" }; });
|
|
163
|
+
};
|
|
164
|
+
field.oninput = () => { d.value = field.value; };
|
|
165
|
+
field.onkeydown = (e) => { e.stopPropagation(); if (e.key === "Enter") add.onclick(); };
|
|
166
|
+
const r = el("div", "row pref rule-row");
|
|
167
|
+
const who = el("div", "who");
|
|
168
|
+
who.append(el("div", "name", t("Web pages that may call magpie")),
|
|
169
|
+
el("div", "sub" + (d.err ? " err" : ""), d.err || t("Scripts on these origins can call the gateway from a browser, each request with a gateway key from Gateway. A page from any other site is refused; pages on this computer are not")));
|
|
170
|
+
const val = el("div", "val rule-add");
|
|
171
|
+
val.append(field, add);
|
|
172
|
+
r.append(who, val);
|
|
173
|
+
box.append(r);
|
|
174
|
+
for (const o of origins) {
|
|
175
|
+
const row = el("div", "row pref cors-origin"); // an extension's id runs long: wrapped, never cut
|
|
176
|
+
const w = el("div", "who");
|
|
177
|
+
w.append(el("div", "name", o));
|
|
178
|
+
const x = el("button", "text", t("Remove"));
|
|
179
|
+
x.onclick = () => set(origins.filter((v) => v !== o));
|
|
180
|
+
const v = el("div", "val");
|
|
181
|
+
v.append(x);
|
|
182
|
+
row.append(w, v);
|
|
183
|
+
box.append(row);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// renderAllow: the addresses other computers may reach magpie from at
|
|
188
|
+
// all (lc on Discord: magpie in Docker on a public host), as IPs and CIDR
|
|
189
|
+
// ranges, under Share on local network — a row to add one, and one row
|
|
190
|
+
// each to take it away. None listed lets every address in, with a gateway
|
|
191
|
+
// key, as before; this computer is always let in. MAGPIE_ALLOW_IPS, where
|
|
192
|
+
// set, is the list, shown and not edited here.
|
|
193
|
+
// A list that would shut out the page saving it names the address magpie
|
|
194
|
+
// sees the page from (Mathresearcher on Discord: which address?), and
|
|
195
|
+
// offers to add it to the list that was refused; behind Docker or a proxy
|
|
196
|
+
// that is the proxy's, not the user's computer's (gateway.peerAddr).
|
|
197
|
+
let allowDraft = { value: "", err: "", addr: "", tried: null };
|
|
198
|
+
function allowSays(m) {
|
|
199
|
+
if (/is not an IP address/.test(m)) return t("Write an address as 203.0.113.7, or a range as 10.0.0.0/8 or 2001:db8::/32");
|
|
200
|
+
const out = /shut out (\S+), the address/.exec(m);
|
|
201
|
+
if (out) return t("That list leaves out {addr}, the address magpie sees this page open from: add it first, or this page can’t reach magpie", { addr: out[1] });
|
|
202
|
+
if (/MAGPIE_ALLOW_IPS/.test(m)) return t("MAGPIE_ALLOW_IPS sets this list; change it there");
|
|
203
|
+
return t(m);
|
|
204
|
+
}
|
|
205
|
+
function renderAllow(s) {
|
|
206
|
+
const box = $("#allowList");
|
|
207
|
+
box.replaceChildren();
|
|
208
|
+
const env = s.allowIPsEnv || "";
|
|
209
|
+
if (!s.lan && !s.onNetwork && !env) return;
|
|
210
|
+
const list = env ? (s.allowIPsFromEnv || []) : (s.allowIPs || []);
|
|
211
|
+
const d = allowDraft;
|
|
212
|
+
const set = (next, done) => writingPrefs(api("settings/allowips", { ips: next }))
|
|
213
|
+
.then((ns) => { prefs = ns; d.err = d.addr = ""; d.tried = null; done?.(); renderSettings(); status(t("Saved"), "ok", 1500); })
|
|
214
|
+
.catch((e) => {
|
|
215
|
+
d.err = allowSays(e.message);
|
|
216
|
+
d.addr = /shut out (\S+), the address/.exec(e.message)?.[1] || "";
|
|
217
|
+
d.tried = d.addr ? { next, done } : null;
|
|
218
|
+
status(d.err, "err"); renderSettings();
|
|
219
|
+
});
|
|
220
|
+
const r = el("div", "row pref rule-row");
|
|
221
|
+
const who = el("div", "who");
|
|
222
|
+
const sub = d.err || (env ? t("Set by MAGPIE_ALLOW_IPS. Other computers reach magpie only from these addresses, even with a gateway key; this computer always can")
|
|
223
|
+
: t("Other computers reach magpie only from these addresses or ranges (CIDR), even with a gateway key; this computer always can. None listed lets every address in"));
|
|
224
|
+
who.append(el("div", "name", t("Allowed addresses")), el("div", "sub" + (d.err ? " err" : ""), sub));
|
|
225
|
+
// the address the refusal named, added to the list that was refused in
|
|
226
|
+
// one click
|
|
227
|
+
if (d.err && d.addr && d.tried && !env) {
|
|
228
|
+
const fix = el("button", "text allow-fix", t("Add {addr}", { addr: d.addr }));
|
|
229
|
+
fix.type = "button";
|
|
230
|
+
fix.title = t("Behind Docker or a reverse proxy, this is the proxy’s address, not your computer’s: adding it lets in everyone who comes through that proxy");
|
|
231
|
+
const { next, done } = d.tried;
|
|
232
|
+
fix.onclick = () => set([...next, d.addr], done);
|
|
233
|
+
who.append(fix);
|
|
234
|
+
}
|
|
235
|
+
r.append(who);
|
|
236
|
+
if (!env) {
|
|
237
|
+
const field = input(d.value, "203.0.113.7, 10.0.0.0/8");
|
|
238
|
+
field.className = "words rule-match allow-ip";
|
|
239
|
+
field.setAttribute("aria-label", t("IP address or range"));
|
|
240
|
+
field.autocomplete = "off";
|
|
241
|
+
field.spellcheck = false;
|
|
242
|
+
const add = el("button", "text", t("Add"));
|
|
243
|
+
add.onclick = () => {
|
|
244
|
+
d.value = field.value.trim();
|
|
245
|
+
if (!d.value) return field.focus();
|
|
246
|
+
set([...list, ...d.value.split(/[\s,;]+/).filter(Boolean)], () => { allowDraft = { value: "", err: "" }; });
|
|
247
|
+
};
|
|
248
|
+
field.oninput = () => { d.value = field.value; };
|
|
249
|
+
field.onkeydown = (e) => { e.stopPropagation(); if (e.key === "Enter") add.onclick(); };
|
|
250
|
+
const val = el("div", "val rule-add");
|
|
251
|
+
val.append(field, add);
|
|
252
|
+
r.append(val);
|
|
253
|
+
}
|
|
254
|
+
box.append(r);
|
|
255
|
+
for (const a of list) {
|
|
256
|
+
const row = el("div", "row pref allow-ip-row");
|
|
257
|
+
const w = el("div", "who");
|
|
258
|
+
w.append(el("div", "name", a));
|
|
259
|
+
const v = el("div", "val");
|
|
260
|
+
if (!env) {
|
|
261
|
+
const x = el("button", "text", t("Remove"));
|
|
262
|
+
x.onclick = () => set(list.filter((o) => o !== a));
|
|
263
|
+
v.append(x);
|
|
264
|
+
}
|
|
265
|
+
row.append(w, v);
|
|
266
|
+
box.append(row);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// renderLAN: the gateway shared on the local network, for agents on other
|
|
271
|
+
// machines. Gateway keys and connection examples live together in Gateway.
|
|
272
|
+
let lanSelectedURL = "", lanProtocol = "openai";
|
|
273
|
+
// the address picked under Share on local network is kept like Connect's
|
|
274
|
+
let lanWant = "";
|
|
275
|
+
try { lanWant = localStorage.getItem("magpie.lanAddress") || ""; } catch {}
|
|
276
|
+
// lanShown: the addresses the gateway is offered at beyond this computer,
|
|
277
|
+
// those of them for other containers on this host, and a line on them
|
|
278
|
+
// when magpie runs in a container, for Settings, Gateway, Connect, the
|
|
279
|
+
// sidebar and the key page alike. In a container magpie finds only its
|
|
280
|
+
// own addresses; the page opened over the network was reached at the
|
|
281
|
+
// host's, so the gateway is offered there, on its port. Other containers
|
|
282
|
+
// on the host reach it at the host's Docker bridge address, the
|
|
283
|
+
// container's default route (jiezhengj on Discord: other containers on a
|
|
284
|
+
// NAS needed http://172.17.0.1:3425), which follows, said to be theirs.
|
|
285
|
+
function lanShown(urls, container, bridge) {
|
|
286
|
+
const others = bridge ? [bridge] : [];
|
|
287
|
+
if (!container) return { urls: [...urls, ...others.filter((u) => !urls.includes(u))], others, sub: "" };
|
|
288
|
+
const host = location.hostname, at = urls[0] || bridge ? new URL(urls[0] || bridge) : null;
|
|
289
|
+
if (web && host && at?.port && !/^(localhost|127\.|\[::1\]$)/.test(host))
|
|
290
|
+
return { urls: [at.protocol + "//" + host + ":" + at.port, ...others], others, // an IPv6 hostname comes bracketed
|
|
291
|
+
sub: others.length ? t("Other devices use the host’s address this page was opened at, and other containers on this host the host’s Docker bridge address, both on the gateway’s port; MAGPIE_PUBLIC_URL sets another")
|
|
292
|
+
: t("Where this page was opened, on the gateway’s port; MAGPIE_PUBLIC_URL sets another") };
|
|
293
|
+
if (others.length) return { urls: others, others,
|
|
294
|
+
sub: t("Other containers on this host use the host’s Docker bridge address, on the gateway’s port; other devices use the host’s own, which MAGPIE_PUBLIC_URL puts here") };
|
|
295
|
+
return { urls, others, sub: t("The container’s own addresses, which other devices can’t reach: set MAGPIE_PUBLIC_URL to the host’s") };
|
|
296
|
+
}
|
|
297
|
+
// gwLAN: lanShown for the gateway's read (providers', or boot.js's line),
|
|
298
|
+
// which says whether its addresses are a container's and where other
|
|
299
|
+
// containers reach it
|
|
300
|
+
function gwLAN(g) {
|
|
301
|
+
return lanShown(g?.lan && g.lanURLs || [], !!g?.container, g?.lan && g.containerHost || "");
|
|
302
|
+
}
|
|
303
|
+
// gwFar: where other machines reach the gateway, for the sidebar and the
|
|
304
|
+
// key page: other containers' address only when there is no other
|
|
305
|
+
function gwFar(g) {
|
|
306
|
+
const { urls, others } = gwLAN(g), far = urls.filter((u) => !others.includes(u));
|
|
307
|
+
return far.length ? far : urls;
|
|
308
|
+
}
|
|
309
|
+
// sideLAN: where other machines reach the gateway while it is shared, for
|
|
310
|
+
// the sidebar's line (coeo91 on Discord): MAGPIE_PUBLIC_URL, else the
|
|
311
|
+
// address picked here, else the first
|
|
312
|
+
function sideLAN(g) {
|
|
313
|
+
const u = gwFar(g);
|
|
314
|
+
return u[0] && (g.publicURL || (u.includes(lanWant) ? lanWant : u[0]));
|
|
315
|
+
}
|
|
316
|
+
// the line was drawn from boot.js before this script came
|
|
317
|
+
if (sideGwAt) renderSideGw();
|
|
318
|
+
function renderLAN(s) {
|
|
319
|
+
const box = $("#lanList");
|
|
320
|
+
box.replaceChildren();
|
|
321
|
+
const row = (name, sub, value, ...tools) => {
|
|
322
|
+
const r = el("div", "row pref");
|
|
323
|
+
const who = el("div", "who");
|
|
324
|
+
who.append(el("div", "name", name));
|
|
325
|
+
if (sub) who.append(el("div", "sub", sub));
|
|
326
|
+
const val = el("div", "val");
|
|
327
|
+
if (value) val.append(el("code", "", value));
|
|
328
|
+
val.append(...tools);
|
|
329
|
+
r.append(who, val);
|
|
330
|
+
box.append(r);
|
|
331
|
+
return r;
|
|
332
|
+
};
|
|
333
|
+
const set = (body) => writingPrefs(api("settings/lan", body)).then((ns) => {
|
|
334
|
+
prefs = ns; renderSettings();
|
|
335
|
+
// the sidebar's gateway names the address shared, or this computer's
|
|
336
|
+
api("providers").then((r) => renderSideGw(r?.gateway), () => {});
|
|
337
|
+
if (ns.rekeyed) status(ns.rekeyed, "err");
|
|
338
|
+
})
|
|
339
|
+
.catch((e) => { status(t(e.message), "err"); renderSettings(); });
|
|
340
|
+
row(t("Share on local network"), t("Agents on other computers on this network, or through a tunnel to this one such as Cloudflare Tunnel, can use magpie’s models with a gateway key from Gateway"), "",
|
|
341
|
+
segs([["off", t("Off")], ["on", t("On")]], s.lan ? "on" : "off", (v) => set({ on: v === "on" })));
|
|
342
|
+
if (!s.lan) return;
|
|
343
|
+
const { urls, others, sub } = lanShown(s.lanURLs || [], s.lanContainer, s.lanContainerHost);
|
|
344
|
+
if (!urls.length) row(t("Address"), t("This computer has no local network address right now"), "");
|
|
345
|
+
else {
|
|
346
|
+
lanSelectedURL = urls.includes(lanWant) ? lanWant : urls[0];
|
|
347
|
+
// an interface can hold an internet address (an ISP's static one, a
|
|
348
|
+
// VPN's): the gateway answers there too, so the row says so
|
|
349
|
+
const far = new Set(s.lanPublic || []);
|
|
350
|
+
const listed = (s.allowIPsEnv ? s.allowIPsFromEnv : s.allowIPs)?.length > 0;
|
|
351
|
+
const controls = el("div", "lan-address-controls");
|
|
352
|
+
const address = urls.length === 1 ? el("code", "lan-address-text") : el("button", "proto pick lan-interface");
|
|
353
|
+
if (urls.length > 1) {
|
|
354
|
+
address.type = "button";
|
|
355
|
+
address.setAttribute("aria-label", t("Address"));
|
|
356
|
+
address.onclick = (e) => {
|
|
357
|
+
e.stopPropagation();
|
|
358
|
+
if (address.classList.contains("open")) return closeProtoMenu();
|
|
359
|
+
const suffix = lanProtocol === "openai" ? "/v1" : "";
|
|
360
|
+
openProtoMenu(address, urls.map((u) => ({ v: u, name: u + suffix, note: far.has(u) ? t("On the internet") : others.includes(u) ? t("Other containers on this host") : "" })), lanSelectedURL,
|
|
361
|
+
(v) => { lanSelectedURL = lanWant = v; keepPick("magpie.lanAddress", v); update(); renderSideGw(); }, "Address", "lan-address-menu");
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const copyControl = el("span", "lan-copy");
|
|
365
|
+
const update = () => {
|
|
366
|
+
const label = lanProtocol === "openai" ? "OpenAI" : "Anthropic";
|
|
367
|
+
const url = lanSelectedURL + (lanProtocol === "openai" ? "/v1" : "");
|
|
368
|
+
if (urls.length === 1) address.textContent = url;
|
|
369
|
+
else address.replaceChildren(el("span", "lan-url", url), svg(CHEV, 11, 1.6));
|
|
370
|
+
address.title = url;
|
|
371
|
+
const what = t("{label} address", { label });
|
|
372
|
+
const button = copyBtn(url, what, t("Copied {label} address", { label }));
|
|
373
|
+
button.setAttribute("aria-label", t("Copy") + " " + what);
|
|
374
|
+
copyControl.replaceChildren(button);
|
|
375
|
+
const who = addressRow?.querySelector(".who");
|
|
376
|
+
if (!who) return;
|
|
377
|
+
who.querySelector(".sub")?.remove();
|
|
378
|
+
if (far.has(lanSelectedURL)) who.append(el("div", "sub", listed
|
|
379
|
+
? t("An internet address: only the addresses under Allowed addresses get in")
|
|
380
|
+
: t("An internet address: anyone online can reach magpie here with a gateway key. To keep it to this network, list its range under Allowed addresses")));
|
|
381
|
+
};
|
|
382
|
+
controls.append(segs([["openai", "OpenAI"], ["anthropic", "Anthropic"]], lanProtocol,
|
|
383
|
+
(v) => { lanProtocol = v; update(); }), address, copyControl);
|
|
384
|
+
const addressRow = row(t("Address"), "", "", controls);
|
|
385
|
+
addressRow.classList.add("lan-address-row");
|
|
386
|
+
update();
|
|
387
|
+
}
|
|
388
|
+
if (sub) row(t("In a container"), sub, "").classList.add("lan-container");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// renderWebPage: Browser access, the app's own page served to a browser
|
|
392
|
+
// (apppage.go; jiakun_zhao on X managed magpie through a Cloudflare tunnel
|
|
393
|
+
// with `magpie web` kept running in a terminal beside the app). Off, this
|
|
394
|
+
// computer alone (for a tunnel or an SSH forward to reach), or the local
|
|
395
|
+
// network. The page always asks for its key: the link carries it, and a
|
|
396
|
+
// browser without it gets nothing. The app's windows alone draw it; a
|
|
397
|
+
// browser on the page can't turn it off under itself.
|
|
398
|
+
let webPageState = null, webPagePort = null;
|
|
399
|
+
// Each draw asks again, as a port another program held can come free; the
|
|
400
|
+
// answer draws without asking.
|
|
401
|
+
function renderWebPage(asked) {
|
|
402
|
+
const head = $("#webPageHead"), box = $("#webPageList");
|
|
403
|
+
if (!box) return;
|
|
404
|
+
head.hidden = box.hidden = web;
|
|
405
|
+
if (web) return;
|
|
406
|
+
if (!asked) api("web-page").then((r) => { webPageState = r; renderWebPage(true); }).catch(() => {});
|
|
407
|
+
const s = webPageState;
|
|
408
|
+
if (!s) return;
|
|
409
|
+
const set = (body) => writingPrefs(api("web-page", body))
|
|
410
|
+
.then((r) => { webPageState = r; webPagePort = null; renderWebPage(true); })
|
|
411
|
+
.catch((e) => { status(t(e.message), "err"); webPageState = null; renderWebPage(); });
|
|
412
|
+
const rows = [];
|
|
413
|
+
const row = (name, sub, ...tools) => {
|
|
414
|
+
const r = el("div", "row pref");
|
|
415
|
+
const who = el("div", "who");
|
|
416
|
+
who.append(el("div", "name", name));
|
|
417
|
+
if (sub) who.append(sub instanceof Node ? sub : el("div", "sub", sub));
|
|
418
|
+
const val = el("div", "val");
|
|
419
|
+
val.append(...tools);
|
|
420
|
+
r.append(who, val);
|
|
421
|
+
rows.push(r);
|
|
422
|
+
return r;
|
|
423
|
+
};
|
|
424
|
+
row(t("Open magpie in a browser"),
|
|
425
|
+
t("The app serves this window’s page with a key in its link, as magpie web does, while it runs. For a tunnel such as Cloudflare Tunnel, point it at http://127.0.0.1:{port}", { port: s.port }),
|
|
426
|
+
segs([["", t("Off")], ["local", t("This computer")], ["lan", t("Local network")]], s.mode || "", (mode) => set({ mode }))).classList.add("web-page-mode");
|
|
427
|
+
if (s.mode) {
|
|
428
|
+
const why = s.error ? el("div", "sub err", s.error.startsWith("port ") ? t("Port {p} is taken by another program, magpie web perhaps: magpie tries again every few seconds", { p: s.port }) : t(s.error))
|
|
429
|
+
: el("div", "sub say", s.serving ? t("Served; anyone with this link can change every setting") : t("Starting…"));
|
|
430
|
+
const links = [s.link, ...(s.mode === "lan" ? s.network || [] : [])].filter(Boolean);
|
|
431
|
+
const box2 = el("div", "web-page-links");
|
|
432
|
+
for (const l of links) {
|
|
433
|
+
const line = el("span", "web-page-link");
|
|
434
|
+
line.append(el("code", "", l.replace(/\?k=.*/, "?k=…")), copyBtn(l, t("Link"), t("Copied")));
|
|
435
|
+
box2.append(line);
|
|
436
|
+
}
|
|
437
|
+
const rekey = el("button", "text", t("New key"));
|
|
438
|
+
rekey.onclick = () => set({ newKey: true });
|
|
439
|
+
row(t("Link"), why, box2, rekey).classList.add("web-page-link-row");
|
|
440
|
+
const field = input(webPagePort ?? String(s.port), "3430");
|
|
441
|
+
field.className = "words gateway-port";
|
|
442
|
+
field.inputMode = "numeric";
|
|
443
|
+
field.setAttribute("aria-label", t("Port"));
|
|
444
|
+
const save = el("button", "text", t("Apply"));
|
|
445
|
+
save.onclick = () => {
|
|
446
|
+
const p = Number(field.value.trim());
|
|
447
|
+
if (!Number.isInteger(p) || p < 1024 || p > 65535) return status(t("A port is a number from 1024 to 65535"), "err");
|
|
448
|
+
set({ port: p });
|
|
449
|
+
};
|
|
450
|
+
field.oninput = () => { webPagePort = field.value; };
|
|
451
|
+
field.onkeydown = (e) => { e.stopPropagation(); if (e.key === "Enter") save.onclick(); };
|
|
452
|
+
row(t("Port"), "", field, save);
|
|
453
|
+
if (s.mode === "lan" && !s.tls) {
|
|
454
|
+
const r = row(t("Not encrypted"), t("Other computers reach the page over plain HTTP, so anyone on this network can read its key: on a network you don’t trust, keep it on This computer and reach it through a tunnel"));
|
|
455
|
+
r.classList.add("web-page-warn");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
box.replaceChildren(...rows);
|
|
459
|
+
}
|