@wenbin_wb/dsh-bridge 1.2.4 → 2.0.0
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/README.md +3 -2
- package/README.zh-CN.md +3 -2
- package/client/client.js +102 -76
- package/client/index.js +112 -89
- package/lib/bridge-rpc-constants.js +9 -0
- package/lib/bridge-rpc.js +93 -3
- package/lib/index.js +28 -10
- package/lib/platform/base.js +143 -0
- package/lib/platform/conversation-bridge.js +972 -0
- package/lib/platform/index.js +10 -0
- package/lib/platform/manager.js +74 -0
- package/lib/wechat/index.js +82 -37
- package/lib/wechat/node.js +64 -886
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -33,8 +33,9 @@ Seamlessly extend your local DeepSeek Harness to mobile phones, tablets, public
|
|
|
33
33
|
|
|
34
34
|
| Target | Description | Status |
|
|
35
35
|
|--------|-------------|--------|
|
|
36
|
-
| **
|
|
37
|
-
| **
|
|
36
|
+
| **Platform Abstraction** | Platform-agnostic core (sessions / approvals / commands / digest) shared across IM channels | ✅ **Completed** (v2.0.0) |
|
|
37
|
+
| **WeChat** | Chat with your Agent directly in WeChat | ✅ Supported (workspaces / persisted sessions / media / approvals) |
|
|
38
|
+
| **QQ Bot** | QQ bot integration for group/private chat | 🚧 Next (platform adapter implementation) |
|
|
38
39
|
| **Feishu** | Feishu message/bot integration for workplace scenarios | Planned |
|
|
39
40
|
| **OpenClaw** | Integration with OpenClaw ecosystem | Planned |
|
|
40
41
|
| **Telegram** | Self-hosted IM channel | Planned |
|
package/README.zh-CN.md
CHANGED
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
|
|
34
34
|
| 目标 | 说明 | 状态 |
|
|
35
35
|
|------|------|------|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
36
|
+
| **平台抽象层** | 平台无关的核心(会话/审批/命令/digest)跨 IM 渠道复用 | ✅ **已完成**(v2.0.0) |
|
|
37
|
+
| **微信** | 在微信里直接与你的 Agent 对话 | ✅ 已支持(多工作区 / 会话持久化 / 媒体 / 审批) |
|
|
38
|
+
| **QQ Bot** | 接入 QQ 机器人,群聊/私聊唤起 Agent | 🚧 下一步(平台适配器实现) |
|
|
38
39
|
| **飞书** | 飞书消息/机器人集成,办公场景直接调用 | 规划中 |
|
|
39
40
|
| **OpenClaw** | 与 OpenClaw 生态打通 | 规划中 |
|
|
40
41
|
| **Telegram** | 适合自托管的 IM 渠道 | 规划中 |
|
package/client/client.js
CHANGED
|
@@ -42,6 +42,15 @@ var BRIDGE_ENDPOINTS = {
|
|
|
42
42
|
resetCloudflared: "resetCloudflared",
|
|
43
43
|
saveCustomTunnelConfig: "saveCustomTunnelConfig",
|
|
44
44
|
checkVersion: "checkVersion",
|
|
45
|
+
// 平台管理器(多 IM 平台统一接口)
|
|
46
|
+
listPlatforms: "listPlatforms",
|
|
47
|
+
platformLogin: "platformLogin",
|
|
48
|
+
platformSetAllowFrom: "platformSetAllowFrom",
|
|
49
|
+
platformSetConfig: "platformSetConfig",
|
|
50
|
+
platformStop: "platformStop",
|
|
51
|
+
platformStart: "platformStart",
|
|
52
|
+
platformUnbind: "platformUnbind",
|
|
53
|
+
// 微信 Bot(v1.x 向后兼容别名,deprecated)
|
|
45
54
|
wechatGetStatus: "wechatGetStatus",
|
|
46
55
|
wechatLogin: "wechatLogin",
|
|
47
56
|
wechatSetAllowFrom: "wechatSetAllowFrom",
|
|
@@ -77,7 +86,7 @@ var s = {
|
|
|
77
86
|
muted: { color: "var(--dsw-alias-label-tertiary,#8b93a1)", fontSize: 12, lineHeight: 1.5 },
|
|
78
87
|
label: { color: "var(--dsw-alias-label-primary,currentColor)", fontSize: 13, fontWeight: 500 },
|
|
79
88
|
code: { fontFamily: "ui-monospace,Menlo,monospace", fontSize: 12, wordBreak: "break-all", color: "var(--dsw-alias-label-primary,currentColor)" },
|
|
80
|
-
btnPri: { font: "inherit", cursor: "pointer", border: "none", background: "var(--dsw-alias-
|
|
89
|
+
btnPri: { font: "inherit", cursor: "pointer", border: "none", background: "var(--dsw-alias-brand-primary,#4f6ef7)", color: "var(--dsw-alias-label-primary-foreground,#fff)", height: 32, padding: "0 14px", borderRadius: 999, fontSize: 13, fontWeight: 500, display: "inline-flex", alignItems: "center", gap: 4 },
|
|
81
90
|
btnGhost: { font: "inherit", cursor: "pointer", border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", background: "var(--dsw-alias-bg-layer-1,transparent)", color: "var(--dsw-alias-label-primary,currentColor)", height: 32, padding: "0 14px", borderRadius: 999, fontSize: 13, display: "inline-flex", alignItems: "center", gap: 4, textDecoration: "none" },
|
|
82
91
|
btnLink: { font: "inherit", cursor: "pointer", border: "none", background: "none", color: "var(--dsw-alias-brand-primary,#4f6ef7)", fontSize: 12, padding: 0, display: "inline-flex", alignItems: "center", gap: 3, textDecoration: "none" },
|
|
83
92
|
qr: { width: 200, height: 200, borderRadius: 10, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", margin: "8px 0", display: "block" },
|
|
@@ -267,50 +276,51 @@ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, o
|
|
|
267
276
|
)
|
|
268
277
|
);
|
|
269
278
|
});
|
|
270
|
-
function
|
|
271
|
-
const [
|
|
279
|
+
function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatusChange }) {
|
|
280
|
+
const [platform, setPlatform] = React.useState(null);
|
|
272
281
|
const [err, setErr] = React.useState(null);
|
|
273
282
|
const [busy, setBusy] = React.useState(false);
|
|
274
283
|
const [showAdvanced, setShowAdvanced] = React.useState(false);
|
|
275
284
|
const [showHelp, setShowHelp] = React.useState(false);
|
|
276
285
|
const [cfgDraft, setCfgDraft] = React.useState(null);
|
|
277
286
|
React.useEffect(() => {
|
|
278
|
-
if (
|
|
287
|
+
if (platform?.config && !cfgDraft) {
|
|
279
288
|
setCfgDraft({
|
|
280
|
-
digestIntervalSec: String(
|
|
281
|
-
approvalTimeoutSec: String(
|
|
282
|
-
maxMessageChars: String(
|
|
283
|
-
sendChunkDelayMs: String(
|
|
289
|
+
digestIntervalSec: String(platform.config.digestIntervalSec ?? 300),
|
|
290
|
+
approvalTimeoutSec: String(platform.config.approvalTimeoutSec ?? 600),
|
|
291
|
+
maxMessageChars: String(platform.config.maxMessageChars ?? 2e3),
|
|
292
|
+
sendChunkDelayMs: String(platform.config.sendChunkDelayMs ?? 1500)
|
|
284
293
|
});
|
|
285
294
|
}
|
|
286
|
-
}, [
|
|
295
|
+
}, [platform?.config]);
|
|
287
296
|
React.useEffect(() => {
|
|
288
|
-
const connected2 =
|
|
297
|
+
const connected2 = platform?.status === "connected" || platform?.status === "starting" || platform?.status === "reconnecting";
|
|
289
298
|
onStatusChange?.(connected2);
|
|
290
|
-
}, [
|
|
299
|
+
}, [platform?.status, onStatusChange]);
|
|
291
300
|
const load = React.useCallback(async (quiet = false) => {
|
|
292
301
|
try {
|
|
293
|
-
const r = await rpcCall(BRIDGE_ENDPOINTS.
|
|
302
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.listPlatforms, {});
|
|
294
303
|
if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
|
|
295
|
-
|
|
304
|
+
const allPlatforms = r.value ?? {};
|
|
305
|
+
setPlatform(allPlatforms[platformId] ?? null);
|
|
296
306
|
if (!quiet) setErr(null);
|
|
297
307
|
} catch (e) {
|
|
298
308
|
if (!quiet) setErr(e.message);
|
|
299
309
|
}
|
|
300
|
-
}, [rpcCall]);
|
|
310
|
+
}, [rpcCall, platformId]);
|
|
301
311
|
React.useEffect(() => {
|
|
302
312
|
load();
|
|
303
|
-
const activeLogin =
|
|
313
|
+
const activeLogin = platform?.login && (platform.login.phase === "qr" || platform.login.phase === "scaned");
|
|
304
314
|
const interval = activeLogin ? 1500 : 3e3;
|
|
305
315
|
const t = setInterval(() => load(true), interval);
|
|
306
316
|
return () => clearInterval(t);
|
|
307
|
-
}, [load,
|
|
317
|
+
}, [load, platform?.login?.phase]);
|
|
308
318
|
const act = React.useCallback(async (endpoint, payload) => {
|
|
309
319
|
setBusy(true);
|
|
310
320
|
try {
|
|
311
|
-
const r = await rpcCall(endpoint, payload
|
|
321
|
+
const r = await rpcCall(endpoint, { platformId, ...payload });
|
|
312
322
|
if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
|
|
313
|
-
|
|
323
|
+
setPlatform(r.value);
|
|
314
324
|
setErr(null);
|
|
315
325
|
await load(true);
|
|
316
326
|
} catch (e) {
|
|
@@ -318,44 +328,44 @@ function WechatCard({ rpcCall, onStatusChange }) {
|
|
|
318
328
|
} finally {
|
|
319
329
|
setBusy(false);
|
|
320
330
|
}
|
|
321
|
-
}, [rpcCall, load]);
|
|
322
|
-
const onLogin = React.useCallback(() => act(BRIDGE_ENDPOINTS.
|
|
323
|
-
const onStop = React.useCallback(() => act(BRIDGE_ENDPOINTS.
|
|
331
|
+
}, [rpcCall, load, platformId]);
|
|
332
|
+
const onLogin = React.useCallback(() => act(BRIDGE_ENDPOINTS.platformLogin, {}), [act]);
|
|
333
|
+
const onStop = React.useCallback(() => act(BRIDGE_ENDPOINTS.platformStop, {}), [act]);
|
|
324
334
|
const [newId, setNewId] = React.useState("");
|
|
325
335
|
const addAllow = React.useCallback(async () => {
|
|
326
336
|
const id = newId.trim();
|
|
327
337
|
if (!id) return;
|
|
328
|
-
const list = [...
|
|
329
|
-
await act(BRIDGE_ENDPOINTS.
|
|
338
|
+
const list = [...platform?.allowFrom ?? [], id];
|
|
339
|
+
await act(BRIDGE_ENDPOINTS.platformSetAllowFrom, { allowFrom: list });
|
|
330
340
|
setNewId("");
|
|
331
|
-
}, [act, newId,
|
|
341
|
+
}, [act, newId, platform?.allowFrom]);
|
|
332
342
|
const removeAllow = React.useCallback(async (id) => {
|
|
333
|
-
const list = (
|
|
334
|
-
await act(BRIDGE_ENDPOINTS.
|
|
335
|
-
}, [act,
|
|
343
|
+
const list = (platform?.allowFrom ?? []).filter((x) => x !== id);
|
|
344
|
+
await act(BRIDGE_ENDPOINTS.platformSetAllowFrom, { allowFrom: list });
|
|
345
|
+
}, [act, platform?.allowFrom]);
|
|
336
346
|
const handleNewId = React.useCallback((e) => setNewId(e.target.value), []);
|
|
337
347
|
const saveConfig = React.useCallback(async () => {
|
|
338
348
|
if (!cfgDraft) return;
|
|
339
|
-
await act(BRIDGE_ENDPOINTS.
|
|
349
|
+
await act(BRIDGE_ENDPOINTS.platformSetConfig, {
|
|
340
350
|
digestIntervalSec: Number(cfgDraft.digestIntervalSec),
|
|
341
351
|
approvalTimeoutSec: Number(cfgDraft.approvalTimeoutSec),
|
|
342
352
|
maxMessageChars: Number(cfgDraft.maxMessageChars),
|
|
343
353
|
sendChunkDelayMs: Number(cfgDraft.sendChunkDelayMs)
|
|
344
354
|
});
|
|
345
355
|
}, [act, cfgDraft]);
|
|
346
|
-
const cfgDirty = cfgDraft &&
|
|
347
|
-
if (!
|
|
356
|
+
const cfgDirty = cfgDraft && platform?.config && (Number(cfgDraft.digestIntervalSec) !== platform.config.digestIntervalSec || Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec || Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars || Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs);
|
|
357
|
+
if (!platform && !err) {
|
|
348
358
|
return React.createElement(
|
|
349
359
|
"div",
|
|
350
360
|
{ style: s.card },
|
|
351
|
-
React.createElement("div", { style: s.label },
|
|
361
|
+
React.createElement("div", { style: s.label }, platformName),
|
|
352
362
|
React.createElement("div", { style: { ...s.muted, marginTop: 6 } }, "\u52A0\u8F7D\u4E2D\u2026")
|
|
353
363
|
);
|
|
354
364
|
}
|
|
355
|
-
const connected =
|
|
356
|
-
const login =
|
|
365
|
+
const connected = platform?.status === "connected" || platform?.status === "starting";
|
|
366
|
+
const login = platform?.login ?? {};
|
|
357
367
|
const showQr = login.phase === "qr" || login.phase === "scaned";
|
|
358
|
-
const statusLabel =
|
|
368
|
+
const statusLabel = platform?.status === "connected" ? "\u5DF2\u8FDE\u63A5" : platform?.status === "starting" ? "\u8FDE\u63A5\u4E2D\u2026" : platform?.status === "reconnecting" ? "\u91CD\u8FDE\u4E2D\u2026" : platform?.status === "paused" ? "\u6682\u505C\uFF08\u4F1A\u8BDD\u8FC7\u671F\uFF09" : platform?.status === "error" ? "\u9519\u8BEF" : "\u672A\u8FDE\u63A5";
|
|
359
369
|
return React.createElement(
|
|
360
370
|
"div",
|
|
361
371
|
{ style: s.card },
|
|
@@ -365,12 +375,8 @@ function WechatCard({ rpcCall, onStatusChange }) {
|
|
|
365
375
|
React.createElement(
|
|
366
376
|
"div",
|
|
367
377
|
null,
|
|
368
|
-
React.createElement("div", { style: s.label },
|
|
369
|
-
React.createElement(
|
|
370
|
-
"div",
|
|
371
|
-
{ style: { ...s.muted, marginTop: 2 } },
|
|
372
|
-
"\u901A\u8FC7\u5FAE\u4FE1\u626B ClawBot \u4E8C\u7EF4\u7801\uFF0C\u5728\u5FAE\u4FE1\u91CC\u8FDC\u7A0B\u5BF9\u8BDD\u548C\u63A7\u5236 DSH agent"
|
|
373
|
-
)
|
|
378
|
+
React.createElement("div", { style: s.label }, platformName),
|
|
379
|
+
React.createElement("div", { style: { ...s.muted, marginTop: 2 } }, platformDesc)
|
|
374
380
|
),
|
|
375
381
|
React.createElement(StatusTag, { running: connected })
|
|
376
382
|
),
|
|
@@ -378,7 +384,7 @@ function WechatCard({ rpcCall, onStatusChange }) {
|
|
|
378
384
|
React.createElement(
|
|
379
385
|
"div",
|
|
380
386
|
{ style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap", alignItems: "center" } },
|
|
381
|
-
React.createElement("a", {
|
|
387
|
+
platformId === "wechat" && React.createElement("a", {
|
|
382
388
|
href: "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/wechat-usage.md",
|
|
383
389
|
target: "_blank",
|
|
384
390
|
rel: "noopener noreferrer",
|
|
@@ -387,7 +393,7 @@ function WechatCard({ rpcCall, onStatusChange }) {
|
|
|
387
393
|
React.createElement("button", {
|
|
388
394
|
style: s.btnGhost,
|
|
389
395
|
onClick: () => setShowHelp((v) => !v)
|
|
390
|
-
}, showHelp ? "\u6536\u8D77\u547D\u4EE4" : "\
|
|
396
|
+
}, showHelp ? "\u6536\u8D77\u547D\u4EE4" : "\u547D\u4EE4\u5217\u8868")
|
|
391
397
|
),
|
|
392
398
|
// 命令速查
|
|
393
399
|
showHelp && React.createElement(
|
|
@@ -405,25 +411,25 @@ function WechatCard({ rpcCall, onStatusChange }) {
|
|
|
405
411
|
),
|
|
406
412
|
err && React.createElement("div", { style: { ...s.warn, marginTop: 10 } }, err),
|
|
407
413
|
// 已配置:状态详情 + 白名单
|
|
408
|
-
|
|
414
|
+
platform?.configured && React.createElement(
|
|
409
415
|
"div",
|
|
410
416
|
{ style: s.block },
|
|
411
417
|
React.createElement(
|
|
412
418
|
"div",
|
|
413
419
|
{ style: { fontSize: 12, lineHeight: 1.7 } },
|
|
414
420
|
React.createElement("div", null, `\u72B6\u6001: ${statusLabel}`),
|
|
415
|
-
|
|
416
|
-
|
|
421
|
+
platform.accountId && React.createElement("div", null, `\u8D26\u53F7: ${platform.accountId}`),
|
|
422
|
+
platform.sessionId && React.createElement("div", null, `\u5F53\u524D\u4F1A\u8BDD: ${platform.sessionId}`)
|
|
417
423
|
),
|
|
418
424
|
React.createElement(
|
|
419
425
|
"div",
|
|
420
426
|
{ style: { ...s.muted, fontSize: 12, marginTop: 8, lineHeight: 1.6 } },
|
|
421
|
-
"\u767D\u540D\u5355\uFF08\u4EC5\u8FD9\u4E9B\
|
|
427
|
+
"\u767D\u540D\u5355\uFF08\u4EC5\u8FD9\u4E9B\u7528\u6237\u53EF\u9A71\u52A8 agent\uFF09:"
|
|
422
428
|
),
|
|
423
429
|
React.createElement(
|
|
424
430
|
"div",
|
|
425
431
|
{ style: { display: "flex", flexWrap: "wrap", gap: 6, marginTop: 6 } },
|
|
426
|
-
|
|
432
|
+
platform.allowFrom?.length ? platform.allowFrom.map(
|
|
427
433
|
(id) => React.createElement(
|
|
428
434
|
"span",
|
|
429
435
|
{ key: id, style: { ...s.tag, background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", color: "var(--dsw-alias-label-primary,currentColor)", gap: 6 } },
|
|
@@ -434,14 +440,18 @@ function WechatCard({ rpcCall, onStatusChange }) {
|
|
|
434
440
|
title: "\u79FB\u51FA\u767D\u540D\u5355"
|
|
435
441
|
}, "\xD7")
|
|
436
442
|
)
|
|
437
|
-
) : React.createElement(
|
|
443
|
+
) : React.createElement(
|
|
444
|
+
"div",
|
|
445
|
+
{ style: { ...s.muted, fontSize: 12 } },
|
|
446
|
+
platformId === "wechat" ? "(\u7A7A \u2014 \u626B\u7801\u540E\u9996\u4E2A\u53D1\u6D88\u606F\u7684\u5FAE\u4FE1\u7528\u6237\u5C06\u81EA\u52A8\u52A0\u5165)" : "(\u7A7A \u2014 \u9996\u4E2A\u53D1\u6D88\u606F\u7684\u7528\u6237\u5C06\u81EA\u52A8\u52A0\u5165)"
|
|
447
|
+
)
|
|
438
448
|
),
|
|
439
449
|
React.createElement(
|
|
440
450
|
"div",
|
|
441
451
|
{ style: { display: "flex", gap: 8, marginTop: 8, alignItems: "center" } },
|
|
442
452
|
React.createElement("input", {
|
|
443
453
|
style: { ...s.input, flex: 1 },
|
|
444
|
-
placeholder: "\u6DFB\u52A0\u5141\u8BB8\u7684\u5FAE\u4FE1 ID\uFF08\u5982 xxx@im.wechat\uFF09",
|
|
454
|
+
placeholder: platformId === "wechat" ? "\u6DFB\u52A0\u5141\u8BB8\u7684\u5FAE\u4FE1 ID\uFF08\u5982 xxx@im.wechat\uFF09" : "\u6DFB\u52A0\u5141\u8BB8\u7684\u7528\u6237 ID",
|
|
445
455
|
value: newId,
|
|
446
456
|
onChange: handleNewId
|
|
447
457
|
}),
|
|
@@ -454,30 +464,30 @@ function WechatCard({ rpcCall, onStatusChange }) {
|
|
|
454
464
|
React.createElement(
|
|
455
465
|
"div",
|
|
456
466
|
{ style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap", alignItems: "center" } },
|
|
457
|
-
|
|
458
|
-
(
|
|
467
|
+
platform.status !== "connected" && platform.status !== "starting" && React.createElement("button", { style: s.btnPri, onClick: onLogin, disabled: busy }, "\u91CD\u65B0\u767B\u5F55"),
|
|
468
|
+
(platform.status === "connected" || platform.status === "starting") && React.createElement("button", { style: s.btnGhost, onClick: onStop, disabled: busy }, "\u65AD\u5F00"),
|
|
459
469
|
React.createElement("button", {
|
|
460
470
|
style: { ...s.btnGhost, color: "var(--dsw-alias-state-error-primary,#dc2626)", borderColor: "var(--dsw-alias-state-error-primary,#dc2626)", opacity: busy ? 0.5 : 1 },
|
|
461
471
|
disabled: busy,
|
|
462
472
|
onClick: () => {
|
|
463
|
-
if (window.confirm("\u786E\u8BA4\u89E3\u7ED1\uFF1F\u8FD9\u5C06\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\
|
|
473
|
+
if (window.confirm("\u786E\u8BA4\u89E3\u7ED1\uFF1F\u8FD9\u5C06\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u767B\u5F55\u3002")) act(BRIDGE_ENDPOINTS.platformUnbind, {});
|
|
464
474
|
},
|
|
465
|
-
title: "\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\
|
|
475
|
+
title: "\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u767B\u5F55"
|
|
466
476
|
}, "\u89E3\u7ED1\u8D26\u53F7")
|
|
467
477
|
)
|
|
468
478
|
),
|
|
469
479
|
// 未配置 / 登录中:二维码
|
|
470
|
-
(!
|
|
480
|
+
(!platform?.configured || showQr) && React.createElement(
|
|
471
481
|
"div",
|
|
472
482
|
{ style: s.block },
|
|
473
483
|
showQr && login.qr ? React.createElement(
|
|
474
484
|
"div",
|
|
475
485
|
null,
|
|
476
|
-
React.createElement("img", { src: login.qr, alt: "
|
|
486
|
+
React.createElement("img", { src: login.qr, alt: "login QR", style: s.qr }),
|
|
477
487
|
React.createElement(
|
|
478
488
|
"div",
|
|
479
489
|
{ style: { ...s.muted, marginTop: 4 } },
|
|
480
|
-
login.phase === "scaned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u2026" : "\u8BF7\u4F7F\u7528\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF08ClawBot\uFF09"
|
|
490
|
+
login.phase === "scaned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u2026" : platformId === "wechat" ? "\u8BF7\u4F7F\u7528\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF08ClawBot\uFF09" : "\u8BF7\u626B\u7801\u767B\u5F55"
|
|
481
491
|
),
|
|
482
492
|
login.error && React.createElement("div", { style: { ...s.muted, marginTop: 4, color: "var(--dsw-alias-state-warn-primary,#92400e)" } }, login.error)
|
|
483
493
|
) : React.createElement(
|
|
@@ -571,7 +581,7 @@ function WechatCard({ rpcCall, onStatusChange }) {
|
|
|
571
581
|
React.createElement(
|
|
572
582
|
"div",
|
|
573
583
|
{ style: { ...s.tip, fontSize: 12 } },
|
|
574
|
-
"\u8BF4\u660E: \u626B\u7801\u6210\u529F\u540E\uFF0C\u5411\u8BE5\u5FAE\u4FE1 Bot \u53D1\u9001\u7B2C\u4E00\u6761\u6D88\u606F\u5373\u81EA\u52A8\u5B8C\u6210\u767D\u540D\u5355\u6388\u6743\u3002\u4EC5\u767D\u540D\u5355\u5185\u7684\u5FAE\u4FE1\u7528\u6237\u80FD\u9A71\u52A8 agent\uFF0C\u5176\u4ED6\u4EBA\u6D88\u606F\u4F1A\u88AB\u5FFD\u7565\u3002\u4F7F\u7528\u4E13\u7528\u5FAE\u4FE1\u53F7\uFF0C\u907F\u514D\u5F71\u54CD\u4E3B\u53F7\u3002"
|
|
584
|
+
platformId === "wechat" ? "\u8BF4\u660E: \u626B\u7801\u6210\u529F\u540E\uFF0C\u5411\u8BE5\u5FAE\u4FE1 Bot \u53D1\u9001\u7B2C\u4E00\u6761\u6D88\u606F\u5373\u81EA\u52A8\u5B8C\u6210\u767D\u540D\u5355\u6388\u6743\u3002\u4EC5\u767D\u540D\u5355\u5185\u7684\u5FAE\u4FE1\u7528\u6237\u80FD\u9A71\u52A8 agent\uFF0C\u5176\u4ED6\u4EBA\u6D88\u606F\u4F1A\u88AB\u5FFD\u7565\u3002\u4F7F\u7528\u4E13\u7528\u5FAE\u4FE1\u53F7\uFF0C\u907F\u514D\u5F71\u54CD\u4E3B\u53F7\u3002" : "\u8BF4\u660E: \u767B\u5F55\u6210\u529F\u540E\uFF0C\u53D1\u9001\u7B2C\u4E00\u6761\u6D88\u606F\u5373\u81EA\u52A8\u5B8C\u6210\u767D\u540D\u5355\u6388\u6743\u3002\u4EC5\u767D\u540D\u5355\u5185\u7684\u7528\u6237\u80FD\u9A71\u52A8 agent\uFF0C\u5176\u4ED6\u4EBA\u6D88\u606F\u4F1A\u88AB\u5FFD\u7565\u3002"
|
|
575
585
|
)
|
|
576
586
|
)
|
|
577
587
|
);
|
|
@@ -740,7 +750,8 @@ function BridgePanel({ rpcCall }) {
|
|
|
740
750
|
const [status, setStatus] = React.useState(null);
|
|
741
751
|
const [err, setErr] = React.useState(null);
|
|
742
752
|
const [activeTab, setActiveTab] = React.useState("lan");
|
|
743
|
-
const [
|
|
753
|
+
const [platforms, setPlatforms] = React.useState(null);
|
|
754
|
+
const [selectedPlatform, setSelectedPlatform] = React.useState("wechat");
|
|
744
755
|
const load = React.useCallback(async (quiet = false) => {
|
|
745
756
|
try {
|
|
746
757
|
const r = await rpcCall(BRIDGE_ENDPOINTS.getStatus, {});
|
|
@@ -755,10 +766,9 @@ function BridgePanel({ rpcCall }) {
|
|
|
755
766
|
let alive = true;
|
|
756
767
|
const poll = async () => {
|
|
757
768
|
try {
|
|
758
|
-
const r = await rpcCall(BRIDGE_ENDPOINTS.
|
|
769
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.listPlatforms, {});
|
|
759
770
|
if (alive && r?.ok) {
|
|
760
|
-
|
|
761
|
-
setWechatConnected(s2 === "connected" || s2 === "starting" || s2 === "reconnecting");
|
|
771
|
+
setPlatforms(r.value ?? {});
|
|
762
772
|
}
|
|
763
773
|
} catch {
|
|
764
774
|
}
|
|
@@ -803,10 +813,13 @@ function BridgePanel({ rpcCall }) {
|
|
|
803
813
|
}, "\u52A0\u8F7D\u4E2D\u2026");
|
|
804
814
|
}
|
|
805
815
|
const ct = status?.customTunnel;
|
|
816
|
+
const imConnected = platforms && Object.values(platforms).some(
|
|
817
|
+
(p) => p.status === "connected" || p.status === "starting" || p.status === "reconnecting"
|
|
818
|
+
);
|
|
806
819
|
const dots = {
|
|
807
820
|
lan: !!status?.proxy?.running,
|
|
808
821
|
tunnel: !!(status?.cloudflared?.running || ct?.running),
|
|
809
|
-
im:
|
|
822
|
+
im: !!imConnected
|
|
810
823
|
};
|
|
811
824
|
let tabContent;
|
|
812
825
|
if (activeTab === "lan") {
|
|
@@ -857,33 +870,38 @@ function BridgePanel({ rpcCall }) {
|
|
|
857
870
|
);
|
|
858
871
|
} else if (activeTab === "im") {
|
|
859
872
|
const IM_PLATFORMS = [
|
|
860
|
-
{ id: "wechat", label: "\u5FAE\u4FE1", desc: "iLink Bot API\uFF08ClawBot\uFF09"
|
|
861
|
-
{ id: "qq", label: "QQ", desc: "NapCat / Mirai"
|
|
862
|
-
{ id: "feishu", label: "\u98DE\u4E66", desc: "\u5B98\u65B9\u4E8B\u4EF6\u56DE\u8C03 API"
|
|
873
|
+
{ id: "wechat", label: "\u5FAE\u4FE1", desc: "iLink Bot API\uFF08ClawBot\uFF09" },
|
|
874
|
+
{ id: "qq", label: "QQ", desc: "NapCat / Mirai" },
|
|
875
|
+
{ id: "feishu", label: "\u98DE\u4E66", desc: "\u5B98\u65B9\u4E8B\u4EF6\u56DE\u8C03 API" }
|
|
863
876
|
];
|
|
864
877
|
tabContent = React.createElement(
|
|
865
878
|
"div",
|
|
866
879
|
null,
|
|
867
|
-
//
|
|
880
|
+
// 平台选择器(可点击切换)
|
|
868
881
|
React.createElement(
|
|
869
882
|
"div",
|
|
870
883
|
{
|
|
871
884
|
style: { display: "flex", gap: 10, marginBottom: 16, flexWrap: "wrap" }
|
|
872
885
|
},
|
|
873
|
-
IM_PLATFORMS.map(
|
|
874
|
-
|
|
886
|
+
IM_PLATFORMS.map(({ id, label, desc }) => {
|
|
887
|
+
const platformData = platforms?.[id];
|
|
888
|
+
const available = !!platformData;
|
|
889
|
+
const active = platformData?.status === "connected" || platformData?.status === "starting" || platformData?.status === "reconnecting";
|
|
890
|
+
return React.createElement(
|
|
875
891
|
"div",
|
|
876
892
|
{
|
|
877
893
|
key: id,
|
|
878
894
|
style: {
|
|
879
895
|
flex: "1 1 140px",
|
|
880
|
-
border: `1px solid ${active ? "var(--dsw-alias-state-success-primary,#10b981)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
|
|
896
|
+
border: `1px solid ${selectedPlatform === id ? "var(--dsw-alias-state-info-primary,#3b82f6)" : active ? "var(--dsw-alias-state-success-primary,#10b981)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
|
|
881
897
|
borderRadius: 10,
|
|
882
898
|
padding: "12px 14px",
|
|
883
899
|
opacity: available ? 1 : 0.45,
|
|
884
|
-
cursor: available ? "
|
|
885
|
-
background: active ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : available ? "var(--dsw-alias-bg-layer-1,transparent)" : "var(--dsw-alias-bg-layer-2,#f9fafb)"
|
|
886
|
-
|
|
900
|
+
cursor: available ? "pointer" : "not-allowed",
|
|
901
|
+
background: selectedPlatform === id ? "var(--dsw-alias-state-info-bg,#eff6ff)" : active ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : available ? "var(--dsw-alias-bg-layer-1,transparent)" : "var(--dsw-alias-bg-layer-2,#f9fafb)",
|
|
902
|
+
transition: "all 0.15s ease"
|
|
903
|
+
},
|
|
904
|
+
onClick: available ? () => setSelectedPlatform(id) : void 0
|
|
887
905
|
},
|
|
888
906
|
React.createElement(
|
|
889
907
|
"div",
|
|
@@ -900,11 +918,19 @@ function BridgePanel({ rpcCall }) {
|
|
|
900
918
|
}, "\u5373\u5C06\u652F\u6301")
|
|
901
919
|
),
|
|
902
920
|
React.createElement("div", { style: { ...s.muted, marginTop: 3, fontSize: 11 } }, desc)
|
|
903
|
-
)
|
|
904
|
-
)
|
|
921
|
+
);
|
|
922
|
+
})
|
|
905
923
|
),
|
|
906
|
-
//
|
|
907
|
-
React.createElement(
|
|
924
|
+
// 显示选中的平台卡片
|
|
925
|
+
selectedPlatform && platforms?.[selectedPlatform] && React.createElement(PlatformCard, {
|
|
926
|
+
platformId: selectedPlatform,
|
|
927
|
+
platformName: IM_PLATFORMS.find((p) => p.id === selectedPlatform)?.label ?? selectedPlatform,
|
|
928
|
+
platformDesc: IM_PLATFORMS.find((p) => p.id === selectedPlatform)?.desc ?? "",
|
|
929
|
+
rpcCall,
|
|
930
|
+
onStatusChange: () => {
|
|
931
|
+
}
|
|
932
|
+
// 状态变化已由 listPlatforms 轮询处理,不需要回调
|
|
933
|
+
})
|
|
908
934
|
);
|
|
909
935
|
}
|
|
910
936
|
return React.createElement(
|