@weiping/mbridge-page 0.4.0 → 0.5.1
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/package.json +1 -1
- package/page/app.js +13 -9
- package/page/style.css +28 -0
- package/page/view.js +119 -2
package/package.json
CHANGED
package/page/app.js
CHANGED
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
(async () => {
|
|
7
7
|
const V = globalThis.MbridgeView;
|
|
8
8
|
const theme = await magpie.ready;
|
|
9
|
+
let lang = "en";
|
|
9
10
|
if (theme && theme.lang) {
|
|
10
11
|
if (theme.lang.toLowerCase().startsWith("zh")) {
|
|
11
|
-
|
|
12
|
+
lang = "zh";
|
|
12
13
|
}
|
|
14
|
+
} else if (typeof navigator !== "undefined" && (navigator.language || "").toLowerCase().startsWith("zh")) {
|
|
15
|
+
lang = "zh";
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
let opts = {};
|
|
@@ -61,10 +64,14 @@
|
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
function renderView(data) {
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
// When mbridge is not running or not installed (empty options or no valid snapshot with a running pid):
|
|
68
|
+
// Display the setup and installation guide instead of empty dashboard!
|
|
69
|
+
const isLive = data && data.pid && (data.service ? data.service.running !== false : true);
|
|
70
|
+
if (!isLive) {
|
|
71
|
+
V.renderGuide(null, lang);
|
|
66
72
|
return;
|
|
67
73
|
}
|
|
74
|
+
|
|
68
75
|
V.render(data);
|
|
69
76
|
|
|
70
77
|
// Wire up tab switching in the rendered view
|
|
@@ -74,7 +81,6 @@
|
|
|
74
81
|
if (kind === "1d" || kind === "7d" || kind === "30d") {
|
|
75
82
|
btn.onclick = async () => {
|
|
76
83
|
await setWindow(kind);
|
|
77
|
-
// optimistically update tab highlight
|
|
78
84
|
tabButtons.forEach((b) => b.setAttribute("aria-pressed", String(b.textContent.trim() === kind)));
|
|
79
85
|
};
|
|
80
86
|
}
|
|
@@ -99,13 +105,11 @@
|
|
|
99
105
|
|
|
100
106
|
async function poll() {
|
|
101
107
|
const snap = await readOptions();
|
|
102
|
-
|
|
103
|
-
renderView(snap);
|
|
104
|
-
} else {
|
|
105
|
-
fail("鹊桥 正在等待 mbridge 发布快照数据…");
|
|
106
|
-
}
|
|
108
|
+
renderView(snap);
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
globalThis.pollMbridge = poll;
|
|
112
|
+
|
|
109
113
|
await poll();
|
|
110
114
|
setInterval(poll, 5000);
|
|
111
115
|
})();
|
package/page/style.css
CHANGED
|
@@ -85,3 +85,31 @@
|
|
|
85
85
|
.chip.tier-balanced { border-color: var(--warn); }
|
|
86
86
|
.chip.tier-performance { border-color: var(--bad); }
|
|
87
87
|
.chip.differs { border-color: var(--fg); }
|
|
88
|
+
|
|
89
|
+
/* Setup guide view (when mbridge is not running or not installed) */
|
|
90
|
+
.setup-guide { max-width: 760px; margin: 0 auto; padding: 1rem 0; }
|
|
91
|
+
.guide-card {
|
|
92
|
+
border: 1px solid var(--line); border-radius: 8px;
|
|
93
|
+
padding: 1.25rem; margin-bottom: 1.25rem; background: var(--chip);
|
|
94
|
+
}
|
|
95
|
+
.guide-card h2 { margin: 0 0 .5rem; font-size: 1rem; color: var(--fg); text-transform: none; letter-spacing: normal; }
|
|
96
|
+
.guide-desc { color: var(--dim); margin: 0 0 1rem; font-size: .9em; }
|
|
97
|
+
.guide-steps { display: flex; flex-direction: column; gap: 1rem; }
|
|
98
|
+
.guide-step { display: flex; flex-direction: column; gap: .3rem; }
|
|
99
|
+
.step-title { font-weight: 600; font-size: .9em; }
|
|
100
|
+
.step-cmd { position: relative; display: flex; align-items: center; }
|
|
101
|
+
.step-cmd pre {
|
|
102
|
+
margin: 0; padding: .6rem .8rem; background: var(--bg); border: 1px solid var(--line);
|
|
103
|
+
border-radius: 6px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
104
|
+
font-size: .88em; width: 100%; overflow-x: auto; color: var(--fg);
|
|
105
|
+
}
|
|
106
|
+
.guide-tip { margin-top: .6rem; font-size: .85em; color: var(--dim); }
|
|
107
|
+
.guide-tip a { color: var(--ok); text-decoration: none; }
|
|
108
|
+
.guide-refresh {
|
|
109
|
+
display: inline-flex; align-items: center; gap: .5rem;
|
|
110
|
+
padding: .4rem .9rem; border-radius: 6px; border: 1px solid var(--line);
|
|
111
|
+
background: var(--bg); color: var(--fg); cursor: pointer; font: inherit;
|
|
112
|
+
}
|
|
113
|
+
.guide-refresh:hover { background: var(--chip); }
|
|
114
|
+
|
|
115
|
+
.pagever { color: var(--dim); font-size: .75em; font-weight: 400; margin-left: .5rem; }
|
package/page/view.js
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
const REFRESH_MS = 5000;
|
|
8
8
|
|
|
9
|
+
// PAGE_VERSION is this page's own build: the sidebar keeps the iframe it
|
|
10
|
+
// built the first time, so a stale tab shows an old version here (SP14 §2:
|
|
11
|
+
// the plugin page is a one-way ticket — toggle the plugin off and on to
|
|
12
|
+
// get a new frame).
|
|
13
|
+
const PAGE_VERSION = "0.5.1";
|
|
14
|
+
|
|
9
15
|
const TEXT = {
|
|
10
16
|
en: {
|
|
11
17
|
title: "mbridge",
|
|
@@ -336,7 +342,114 @@ function calibrationTable(c) {
|
|
|
336
342
|
return el("div", { class: "cal" }, [t, el("div", { class: "muted", text: foot })]);
|
|
337
343
|
}
|
|
338
344
|
|
|
339
|
-
|
|
345
|
+
|
|
346
|
+
function detectOS() {
|
|
347
|
+
const nav = typeof navigator !== "undefined" ? navigator : {};
|
|
348
|
+
const ua = (nav.userAgent || "").toLowerCase();
|
|
349
|
+
const plat = (nav.platform || "").toLowerCase();
|
|
350
|
+
if (plat.includes("win") || ua.includes("windows")) return "windows";
|
|
351
|
+
if (plat.includes("linux") || ua.includes("linux") || ua.includes("x11")) return "linux";
|
|
352
|
+
return "darwin"; // default mac
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function renderGuide(osName, lang) {
|
|
356
|
+
const isZh = String(lang || "").toLowerCase().startsWith("zh");
|
|
357
|
+
const os = osName || detectOS();
|
|
358
|
+
const root = document.getElementById("root");
|
|
359
|
+
root.textContent = "";
|
|
360
|
+
|
|
361
|
+
const box = el("div", { class: "setup-guide" });
|
|
362
|
+
|
|
363
|
+
// Header
|
|
364
|
+
const head = el("header", { class: "head" }, [
|
|
365
|
+
el("h1", {}, [el("span", { text: isZh ? "鹊桥" : "mbridge" })]),
|
|
366
|
+
el("p", { class: "sub", text: isZh ? "未检测到运行中的 mbridge 实例。请按以下步骤安装、配置并启动:" : "mbridge instance is not running or not installed. Follow the steps below to setup:" }),
|
|
367
|
+
]);
|
|
368
|
+
box.appendChild(head);
|
|
369
|
+
|
|
370
|
+
// Guide card 1: Service Install & Start
|
|
371
|
+
const card1 = el("div", { class: "guide-card" });
|
|
372
|
+
const osTitles = { darwin: "macOS", linux: "Linux", windows: "Windows" };
|
|
373
|
+
card1.appendChild(el("h2", { text: (isZh ? "步骤一:安装与启动 · " : "Step 1: Install & Start · ") + (osTitles[os] || "macOS") }));
|
|
374
|
+
card1.appendChild(el("p", { class: "guide-desc", text: isZh ? "鹊桥 是独立于 magpie 的智能模型选档守护进程,服务跑在 127.0.0.1:3426。" : "Magpie Bridge is a per-turn model router daemon running on 127.0.0.1:3426." }));
|
|
375
|
+
|
|
376
|
+
const steps1 = el("div", { class: "guide-steps" });
|
|
377
|
+
|
|
378
|
+
// 1. Install
|
|
379
|
+
const s1 = el("div", { class: "guide-step" });
|
|
380
|
+
s1.appendChild(el("div", { class: "step-title", text: isZh ? "1. 安装 mbridge 并注册自启服务" : "1. Install mbridge & register login service" }));
|
|
381
|
+
const cmd1 = os === "windows"
|
|
382
|
+
? "irm https://raw.githubusercontent.com/weiping/mbridge/main/install.ps1 | iex"
|
|
383
|
+
: "curl -fsSL https://raw.githubusercontent.com/weiping/mbridge/main/install.sh | sh";
|
|
384
|
+
s1.appendChild(el("div", { class: "step-cmd" }, [el("pre", { text: cmd1 })]));
|
|
385
|
+
steps1.appendChild(s1);
|
|
386
|
+
|
|
387
|
+
// 2. Init groups
|
|
388
|
+
const s2 = el("div", { class: "guide-step" });
|
|
389
|
+
s2.appendChild(el("div", { class: "step-title", text: isZh ? "2. 在 magpie 中初始化路由分组与配置" : "2. Initialize routing groups & config in magpie" }));
|
|
390
|
+
const cmd2 = "mbridge router init --preset cn --groups-only";
|
|
391
|
+
s2.appendChild(el("div", { class: "step-cmd" }, [el("pre", { text: cmd2 })]));
|
|
392
|
+
s2.appendChild(el("div", { class: "guide-tip", text: isZh ? "(也可选择 --preset frontier 或 anthropic 预设)" : "(or choose --preset frontier / anthropic)" }));
|
|
393
|
+
steps1.appendChild(s2);
|
|
394
|
+
|
|
395
|
+
// 3. Start / Verify
|
|
396
|
+
const s3 = el("div", { class: "guide-step" });
|
|
397
|
+
s3.appendChild(el("div", { class: "step-title", text: isZh ? "3. 启动与验证" : "3. Start & Verify" }));
|
|
398
|
+
const cmd3 = "# 检查服务运行状态\nmbridge status\n\n# 或手动在前台启动\nmbridge serve";
|
|
399
|
+
s3.appendChild(el("div", { class: "step-cmd" }, [el("pre", { text: cmd3 })]));
|
|
400
|
+
steps1.appendChild(s3);
|
|
401
|
+
|
|
402
|
+
card1.appendChild(steps1);
|
|
403
|
+
box.appendChild(card1);
|
|
404
|
+
|
|
405
|
+
// Guide card 2: Agent Plugins (Claude Code, Codex, Pi)
|
|
406
|
+
const card2 = el("div", { class: "guide-card" });
|
|
407
|
+
card2.appendChild(el("h2", { text: isZh ? "步骤二:安装 Agent 插件与使用" : "Step 2: Install Agent Plugins & Usage" }));
|
|
408
|
+
card2.appendChild(el("p", { class: "guide-desc", text: isZh ? "主流编码 Agent 均有一键插件支持,按需安装对应客户端:" : "Supported coding agents have dedicated plugins. Install as needed:" }));
|
|
409
|
+
|
|
410
|
+
const steps2 = el("div", { class: "guide-steps" });
|
|
411
|
+
|
|
412
|
+
// Claude Code
|
|
413
|
+
const pClaude = el("div", { class: "guide-step" });
|
|
414
|
+
pClaude.appendChild(el("div", { class: "step-title", text: "Claude Code(≥ v2.1.287)" }));
|
|
415
|
+
const cmdClaude = `# 1. 安装插件\nclaude plugin marketplace add weiping/mbridge\nclaude plugin install mbridge@mbridge\n\n# 2. 项目配置:在项目的 .claude/settings.json 中指定模型\n{\n "model": "group/mbridge[1m]"\n}`;
|
|
416
|
+
pClaude.appendChild(el("div", { class: "step-cmd" }, [el("pre", { text: cmdClaude })]));
|
|
417
|
+
steps2.appendChild(pClaude);
|
|
418
|
+
|
|
419
|
+
// Codex
|
|
420
|
+
const pCodex = el("div", { class: "guide-step" });
|
|
421
|
+
pCodex.appendChild(el("div", { class: "step-title", text: "Codex" }));
|
|
422
|
+
const cmdCodex = `# 1. 安装插件(使用 --sparse 避免拉取庞大历史)\ncodex plugin marketplace add weiping/mbridge --sparse .agents --sparse clients/codex\ncodex plugin add mbridge@mbridge\n\n# 2. 启动使用:通过 mbridge 配置文件启动\ncodex -p mbridge\n# 或在项目的 .codex/config.toml 中指定\nmodel = "group/mbridge"`;
|
|
423
|
+
pCodex.appendChild(el("div", { class: "step-cmd" }, [el("pre", { text: cmdCodex })]));
|
|
424
|
+
steps2.appendChild(pCodex);
|
|
425
|
+
|
|
426
|
+
// Pi
|
|
427
|
+
const pPi = el("div", { class: "guide-step" });
|
|
428
|
+
pPi.appendChild(el("div", { class: "step-title", text: "Pi" }));
|
|
429
|
+
const cmdPi = `# 1. 安装扩展\npi install npm:@weiping/pi-mbridge\n\n# 2. 项目配置:在项目的 .pi/settings.json 中指定\n{\n "defaultProvider": "magpie",\n "defaultModel": "group/mbridge"\n}`;
|
|
430
|
+
pPi.appendChild(el("div", { class: "step-cmd" }, [el("pre", { text: cmdPi })]));
|
|
431
|
+
steps2.appendChild(pPi);
|
|
432
|
+
|
|
433
|
+
card2.appendChild(steps2);
|
|
434
|
+
box.appendChild(card2);
|
|
435
|
+
|
|
436
|
+
// Actions & Live polling indicator
|
|
437
|
+
const actions = el("div", { style: "margin: 1rem 0; display: flex; align-items: center; gap: 1rem;" });
|
|
438
|
+
const refBtn = el("button", { type: "button", class: "guide-refresh", text: isZh ? "🔄 检查服务状态" : "🔄 Check Status" });
|
|
439
|
+
refBtn.onclick = () => {
|
|
440
|
+
refBtn.textContent = isZh ? "正在刷新…" : "Refreshing…";
|
|
441
|
+
if (globalThis.pollMbridge) globalThis.pollMbridge();
|
|
442
|
+
};
|
|
443
|
+
actions.appendChild(refBtn);
|
|
444
|
+
|
|
445
|
+
const tipLink = el("span", { class: "guide-tip", text: isZh ? "mbridge 服务启动后,本页面将自动实时切换为数据监控大盘。" : "Once mbridge starts, this page will automatically display the dashboard." });
|
|
446
|
+
actions.appendChild(tipLink);
|
|
447
|
+
|
|
448
|
+
box.appendChild(actions);
|
|
449
|
+
root.appendChild(box);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function render(data) {
|
|
340
453
|
const root = document.getElementById("root");
|
|
341
454
|
root.textContent = "";
|
|
342
455
|
const t = T();
|
|
@@ -351,7 +464,8 @@ function render(data) {
|
|
|
351
464
|
// the headline: mbridge's own numbers, not the process table
|
|
352
465
|
root.appendChild(el("header", { class: "head" }, [
|
|
353
466
|
el("h1", {}, [el("span", { text: "mbridge" }),
|
|
354
|
-
el("span", { class: "version", text: " " + (data.version || "") })
|
|
467
|
+
el("span", { class: "version", text: " " + (data.version || "") }),
|
|
468
|
+
el("span", { class: "pagever", text: "page " + PAGE_VERSION })]),
|
|
355
469
|
el("p", { class: "sub", text: t.subtitle }),
|
|
356
470
|
]));
|
|
357
471
|
|
|
@@ -561,6 +675,7 @@ function render(data) {
|
|
|
561
675
|
}
|
|
562
676
|
|
|
563
677
|
globalThis.MbridgeView = {
|
|
678
|
+
PAGE_VERSION,
|
|
564
679
|
TEXT,
|
|
565
680
|
REASONS,
|
|
566
681
|
TIERS,
|
|
@@ -580,5 +695,7 @@ function render(data) {
|
|
|
580
695
|
distribution,
|
|
581
696
|
calibrationTable,
|
|
582
697
|
render,
|
|
698
|
+
renderGuide,
|
|
699
|
+
detectOS,
|
|
583
700
|
};
|
|
584
701
|
})();
|