@wenbin_wb/dsh-bridge 1.0.8 → 1.2.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 +53 -5
- package/README.zh-CN.md +53 -5
- package/client/client.js +501 -42
- package/client/index.js +458 -44
- package/docs/banner.jpg +0 -0
- package/docs/custom-tunnel.md +220 -0
- package/docs/wechat-usage.md +101 -0
- package/lib/bridge-rpc-constants.js +21 -0
- package/lib/bridge-rpc.js +89 -13
- package/lib/index.js +45 -2
- package/lib/wechat/gateway.js +807 -0
- package/lib/wechat/index.js +189 -0
- package/lib/wechat/media.js +275 -0
- package/lib/wechat/node.js +1040 -0
- package/package.json +9 -4
package/client/client.js
CHANGED
|
@@ -31,7 +31,7 @@ __export(index_exports, {
|
|
|
31
31
|
});
|
|
32
32
|
module.exports = __toCommonJS(index_exports);
|
|
33
33
|
|
|
34
|
-
// lib/bridge-rpc.js
|
|
34
|
+
// lib/bridge-rpc-constants.js
|
|
35
35
|
var BRIDGE_RPC_CHANNEL = "/dsh-bridge";
|
|
36
36
|
var BRIDGE_ENDPOINTS = {
|
|
37
37
|
getStatus: "getStatus",
|
|
@@ -41,7 +41,14 @@ var BRIDGE_ENDPOINTS = {
|
|
|
41
41
|
stopCloudflared: "stopCloudflared",
|
|
42
42
|
resetCloudflared: "resetCloudflared",
|
|
43
43
|
saveCustomTunnelConfig: "saveCustomTunnelConfig",
|
|
44
|
-
checkVersion: "checkVersion"
|
|
44
|
+
checkVersion: "checkVersion",
|
|
45
|
+
wechatGetStatus: "wechatGetStatus",
|
|
46
|
+
wechatLogin: "wechatLogin",
|
|
47
|
+
wechatSetAllowFrom: "wechatSetAllowFrom",
|
|
48
|
+
wechatSetConfig: "wechatSetConfig",
|
|
49
|
+
wechatStop: "wechatStop",
|
|
50
|
+
wechatStart: "wechatStart",
|
|
51
|
+
wechatUnbind: "wechatUnbind"
|
|
45
52
|
};
|
|
46
53
|
|
|
47
54
|
// client/index.js
|
|
@@ -243,6 +250,315 @@ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, o
|
|
|
243
250
|
)
|
|
244
251
|
);
|
|
245
252
|
});
|
|
253
|
+
function WechatCard({ rpcCall, onStatusChange }) {
|
|
254
|
+
const [wx, setWx] = React.useState(null);
|
|
255
|
+
const [err, setErr] = React.useState(null);
|
|
256
|
+
const [busy, setBusy] = React.useState(false);
|
|
257
|
+
const [showAdvanced, setShowAdvanced] = React.useState(false);
|
|
258
|
+
const [showHelp, setShowHelp] = React.useState(false);
|
|
259
|
+
const [cfgDraft, setCfgDraft] = React.useState(null);
|
|
260
|
+
React.useEffect(() => {
|
|
261
|
+
if (wx?.config && !cfgDraft) {
|
|
262
|
+
setCfgDraft({
|
|
263
|
+
digestIntervalSec: String(wx.config.digestIntervalSec ?? 300),
|
|
264
|
+
approvalTimeoutSec: String(wx.config.approvalTimeoutSec ?? 600),
|
|
265
|
+
maxMessageChars: String(wx.config.maxMessageChars ?? 2e3),
|
|
266
|
+
sendChunkDelayMs: String(wx.config.sendChunkDelayMs ?? 1500)
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}, [wx?.config]);
|
|
270
|
+
React.useEffect(() => {
|
|
271
|
+
const connected2 = wx?.status === "connected" || wx?.status === "starting" || wx?.status === "reconnecting";
|
|
272
|
+
onStatusChange?.(connected2);
|
|
273
|
+
}, [wx?.status, onStatusChange]);
|
|
274
|
+
const load = React.useCallback(async (quiet = false) => {
|
|
275
|
+
try {
|
|
276
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.wechatGetStatus, {});
|
|
277
|
+
if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
|
|
278
|
+
setWx(r.value);
|
|
279
|
+
if (!quiet) setErr(null);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
if (!quiet) setErr(e.message);
|
|
282
|
+
}
|
|
283
|
+
}, [rpcCall]);
|
|
284
|
+
React.useEffect(() => {
|
|
285
|
+
load();
|
|
286
|
+
const activeLogin = wx?.login && (wx.login.phase === "qr" || wx.login.phase === "scaned");
|
|
287
|
+
const interval = activeLogin ? 1500 : 3e3;
|
|
288
|
+
const t = setInterval(() => load(true), interval);
|
|
289
|
+
return () => clearInterval(t);
|
|
290
|
+
}, [load, wx?.login?.phase]);
|
|
291
|
+
const act = React.useCallback(async (endpoint, payload) => {
|
|
292
|
+
setBusy(true);
|
|
293
|
+
try {
|
|
294
|
+
const r = await rpcCall(endpoint, payload ?? {});
|
|
295
|
+
if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
|
|
296
|
+
setWx(r.value);
|
|
297
|
+
setErr(null);
|
|
298
|
+
await load(true);
|
|
299
|
+
} catch (e) {
|
|
300
|
+
setErr(e.message);
|
|
301
|
+
} finally {
|
|
302
|
+
setBusy(false);
|
|
303
|
+
}
|
|
304
|
+
}, [rpcCall, load]);
|
|
305
|
+
const onLogin = React.useCallback(() => act(BRIDGE_ENDPOINTS.wechatLogin, {}), [act]);
|
|
306
|
+
const onStop = React.useCallback(() => act(BRIDGE_ENDPOINTS.wechatStop, {}), [act]);
|
|
307
|
+
const [newId, setNewId] = React.useState("");
|
|
308
|
+
const addAllow = React.useCallback(async () => {
|
|
309
|
+
const id = newId.trim();
|
|
310
|
+
if (!id) return;
|
|
311
|
+
const list = [...wx?.allowFrom ?? [], id];
|
|
312
|
+
await act(BRIDGE_ENDPOINTS.wechatSetAllowFrom, { allowFrom: list });
|
|
313
|
+
setNewId("");
|
|
314
|
+
}, [act, newId, wx?.allowFrom]);
|
|
315
|
+
const removeAllow = React.useCallback(async (id) => {
|
|
316
|
+
const list = (wx?.allowFrom ?? []).filter((x) => x !== id);
|
|
317
|
+
await act(BRIDGE_ENDPOINTS.wechatSetAllowFrom, { allowFrom: list });
|
|
318
|
+
}, [act, wx?.allowFrom]);
|
|
319
|
+
const handleNewId = React.useCallback((e) => setNewId(e.target.value), []);
|
|
320
|
+
const saveConfig = React.useCallback(async () => {
|
|
321
|
+
if (!cfgDraft) return;
|
|
322
|
+
await act(BRIDGE_ENDPOINTS.wechatSetConfig, {
|
|
323
|
+
digestIntervalSec: Number(cfgDraft.digestIntervalSec),
|
|
324
|
+
approvalTimeoutSec: Number(cfgDraft.approvalTimeoutSec),
|
|
325
|
+
maxMessageChars: Number(cfgDraft.maxMessageChars),
|
|
326
|
+
sendChunkDelayMs: Number(cfgDraft.sendChunkDelayMs)
|
|
327
|
+
});
|
|
328
|
+
}, [act, cfgDraft]);
|
|
329
|
+
const cfgDirty = cfgDraft && wx?.config && (Number(cfgDraft.digestIntervalSec) !== wx.config.digestIntervalSec || Number(cfgDraft.approvalTimeoutSec) !== wx.config.approvalTimeoutSec || Number(cfgDraft.maxMessageChars) !== wx.config.maxMessageChars || Number(cfgDraft.sendChunkDelayMs) !== wx.config.sendChunkDelayMs);
|
|
330
|
+
if (!wx && !err) {
|
|
331
|
+
return React.createElement(
|
|
332
|
+
"div",
|
|
333
|
+
{ style: s.card },
|
|
334
|
+
React.createElement("div", { style: s.label }, "\u5FAE\u4FE1 Bot"),
|
|
335
|
+
React.createElement("div", { style: { ...s.muted, marginTop: 6 } }, "\u52A0\u8F7D\u4E2D\u2026")
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
const connected = wx?.status === "connected" || wx?.status === "starting";
|
|
339
|
+
const login = wx?.login ?? {};
|
|
340
|
+
const showQr = login.phase === "qr" || login.phase === "scaned";
|
|
341
|
+
const statusLabel = wx?.status === "connected" ? "\u5DF2\u8FDE\u63A5" : wx?.status === "starting" ? "\u8FDE\u63A5\u4E2D\u2026" : wx?.status === "reconnecting" ? "\u91CD\u8FDE\u4E2D\u2026" : wx?.status === "paused" ? "\u6682\u505C\uFF08\u4F1A\u8BDD\u8FC7\u671F\uFF09" : wx?.status === "error" ? "\u9519\u8BEF" : "\u672A\u8FDE\u63A5";
|
|
342
|
+
return React.createElement(
|
|
343
|
+
"div",
|
|
344
|
+
{ style: s.card },
|
|
345
|
+
React.createElement(
|
|
346
|
+
"div",
|
|
347
|
+
{ style: { display: "flex", justifyContent: "space-between", alignItems: "center" } },
|
|
348
|
+
React.createElement(
|
|
349
|
+
"div",
|
|
350
|
+
null,
|
|
351
|
+
React.createElement("div", { style: s.label }, "\u5FAE\u4FE1 Bot"),
|
|
352
|
+
React.createElement(
|
|
353
|
+
"div",
|
|
354
|
+
{ style: { ...s.muted, marginTop: 2 } },
|
|
355
|
+
"\u901A\u8FC7\u5FAE\u4FE1\u626B ClawBot \u4E8C\u7EF4\u7801\uFF0C\u5728\u5FAE\u4FE1\u91CC\u8FDC\u7A0B\u5BF9\u8BDD\u548C\u63A7\u5236 DSH agent"
|
|
356
|
+
)
|
|
357
|
+
),
|
|
358
|
+
React.createElement(StatusTag, { running: connected })
|
|
359
|
+
),
|
|
360
|
+
// 快捷入口:使用说明 / 命令
|
|
361
|
+
React.createElement(
|
|
362
|
+
"div",
|
|
363
|
+
{ style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap", alignItems: "center" } },
|
|
364
|
+
React.createElement("a", {
|
|
365
|
+
href: "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/wechat-usage.md",
|
|
366
|
+
target: "_blank",
|
|
367
|
+
rel: "noopener noreferrer",
|
|
368
|
+
style: s.btnGhost
|
|
369
|
+
}, "\u{1F4D6} \u4F7F\u7528\u8BF4\u660E"),
|
|
370
|
+
React.createElement("button", {
|
|
371
|
+
style: s.btnGhost,
|
|
372
|
+
onClick: () => setShowHelp((v) => !v)
|
|
373
|
+
}, showHelp ? "\u6536\u8D77\u547D\u4EE4" : "\u5FAE\u4FE1\u547D\u4EE4")
|
|
374
|
+
),
|
|
375
|
+
// 命令速查
|
|
376
|
+
showHelp && React.createElement(
|
|
377
|
+
"div",
|
|
378
|
+
{ style: { ...s.block, fontSize: 12, lineHeight: 1.8, fontFamily: "monospace" } },
|
|
379
|
+
React.createElement("div", null, "/new <\u63D0\u793A\u8BCD> \u2014 \u65B0\u5EFA\u4F1A\u8BDD\uFF08\u5F53\u524D\u5DE5\u4F5C\u533A\uFF09"),
|
|
380
|
+
React.createElement("div", null, "/new <\u63D0\u793A\u8BCD> @N \u2014 \u5728\u6307\u5B9A\u5DE5\u4F5C\u533A\u65B0\u5EFA"),
|
|
381
|
+
React.createElement("div", null, "/sessions \u2014 \u6309\u5DE5\u4F5C\u533A\u5206\u7EC4\u5217\u4F1A\u8BDD"),
|
|
382
|
+
React.createElement("div", null, "/use N \u2014 \u5207\u6362\u5230\u4F1A\u8BDD N"),
|
|
383
|
+
React.createElement("div", null, "/workspaces \u2014 \u5217\u51FA\u5DE5\u4F5C\u533A"),
|
|
384
|
+
React.createElement("div", null, "/stop \u2014 \u505C\u6B62\u4EFB\u52A1"),
|
|
385
|
+
React.createElement("div", null, "/status \u2014 \u67E5\u770B\u72B6\u6001"),
|
|
386
|
+
React.createElement("div", null, "/yes \u6216 /no \u2014 \u56DE\u5E94\u5BA1\u6279"),
|
|
387
|
+
React.createElement("div", null, "/help \u2014 \u5168\u90E8\u547D\u4EE4")
|
|
388
|
+
),
|
|
389
|
+
err && React.createElement("div", { style: { ...s.warn, marginTop: 10 } }, err),
|
|
390
|
+
// 已配置:状态详情 + 白名单
|
|
391
|
+
wx?.configured && React.createElement(
|
|
392
|
+
"div",
|
|
393
|
+
{ style: s.block },
|
|
394
|
+
React.createElement(
|
|
395
|
+
"div",
|
|
396
|
+
{ style: { fontSize: 12, lineHeight: 1.7 } },
|
|
397
|
+
React.createElement("div", null, `\u72B6\u6001: ${statusLabel}`),
|
|
398
|
+
wx.accountId && React.createElement("div", null, `\u8D26\u53F7: ${wx.accountId}`),
|
|
399
|
+
wx.sessionId && React.createElement("div", null, `\u5F53\u524D\u4F1A\u8BDD: ${wx.sessionId}`)
|
|
400
|
+
),
|
|
401
|
+
React.createElement(
|
|
402
|
+
"div",
|
|
403
|
+
{ style: { ...s.muted, fontSize: 12, marginTop: 8, lineHeight: 1.6 } },
|
|
404
|
+
"\u767D\u540D\u5355\uFF08\u4EC5\u8FD9\u4E9B\u5FAE\u4FE1\u7528\u6237\u53EF\u9A71\u52A8 agent\uFF09:"
|
|
405
|
+
),
|
|
406
|
+
React.createElement(
|
|
407
|
+
"div",
|
|
408
|
+
{ style: { display: "flex", flexWrap: "wrap", gap: 6, marginTop: 6 } },
|
|
409
|
+
wx.allowFrom?.length ? wx.allowFrom.map(
|
|
410
|
+
(id) => React.createElement(
|
|
411
|
+
"span",
|
|
412
|
+
{ key: id, style: { ...s.tag, background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", color: "var(--dsw-alias-label-primary,currentColor)", gap: 6 } },
|
|
413
|
+
React.createElement("span", { style: { fontSize: 12, wordBreak: "break-all" } }, id),
|
|
414
|
+
React.createElement("button", {
|
|
415
|
+
style: { cursor: "pointer", border: "none", background: "none", color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 12, padding: 0 },
|
|
416
|
+
onClick: () => removeAllow(id),
|
|
417
|
+
title: "\u79FB\u51FA\u767D\u540D\u5355"
|
|
418
|
+
}, "\xD7")
|
|
419
|
+
)
|
|
420
|
+
) : React.createElement("div", { style: { ...s.muted, fontSize: 12 } }, "(\u7A7A \u2014 \u626B\u7801\u540E\u9996\u4E2A\u53D1\u6D88\u606F\u7684\u5FAE\u4FE1\u7528\u6237\u5C06\u81EA\u52A8\u52A0\u5165)")
|
|
421
|
+
),
|
|
422
|
+
React.createElement(
|
|
423
|
+
"div",
|
|
424
|
+
{ style: { display: "flex", gap: 8, marginTop: 8, alignItems: "center" } },
|
|
425
|
+
React.createElement("input", {
|
|
426
|
+
style: { ...s.input, flex: 1 },
|
|
427
|
+
placeholder: "\u6DFB\u52A0\u5141\u8BB8\u7684\u5FAE\u4FE1 ID\uFF08\u5982 xxx@im.wechat\uFF09",
|
|
428
|
+
value: newId,
|
|
429
|
+
onChange: handleNewId
|
|
430
|
+
}),
|
|
431
|
+
React.createElement("button", {
|
|
432
|
+
style: { ...s.btnGhost, whiteSpace: "nowrap", opacity: newId.trim() && !busy ? 1 : 0.5 },
|
|
433
|
+
onClick: addAllow,
|
|
434
|
+
disabled: busy || !newId.trim()
|
|
435
|
+
}, "\u6DFB\u52A0")
|
|
436
|
+
),
|
|
437
|
+
React.createElement(
|
|
438
|
+
"div",
|
|
439
|
+
{ style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap", alignItems: "center" } },
|
|
440
|
+
wx.status !== "connected" && wx.status !== "starting" && React.createElement("button", { style: s.btnPri, onClick: onLogin, disabled: busy }, "\u91CD\u65B0\u626B\u7801"),
|
|
441
|
+
(wx.status === "connected" || wx.status === "starting") && React.createElement("button", { style: s.btnGhost, onClick: onStop, disabled: busy }, "\u65AD\u5F00"),
|
|
442
|
+
React.createElement("button", {
|
|
443
|
+
style: { ...s.btnGhost, color: "var(--dsw-alias-state-error-primary,#dc2626)", borderColor: "var(--dsw-alias-state-error-primary,#dc2626)", opacity: busy ? 0.5 : 1 },
|
|
444
|
+
disabled: busy,
|
|
445
|
+
onClick: () => {
|
|
446
|
+
if (window.confirm("\u786E\u8BA4\u89E3\u7ED1\uFF1F\u8FD9\u5C06\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u626B\u7801\u767B\u5F55\u3002")) act(BRIDGE_ENDPOINTS.wechatUnbind, {});
|
|
447
|
+
},
|
|
448
|
+
title: "\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u626B\u7801"
|
|
449
|
+
}, "\u89E3\u7ED1\u8D26\u53F7")
|
|
450
|
+
)
|
|
451
|
+
),
|
|
452
|
+
// 未配置 / 登录中:二维码
|
|
453
|
+
(!wx?.configured || showQr) && React.createElement(
|
|
454
|
+
"div",
|
|
455
|
+
{ style: s.block },
|
|
456
|
+
showQr && login.qr ? React.createElement(
|
|
457
|
+
"div",
|
|
458
|
+
null,
|
|
459
|
+
React.createElement("img", { src: login.qr, alt: "wechat QR", style: s.qr }),
|
|
460
|
+
React.createElement(
|
|
461
|
+
"div",
|
|
462
|
+
{ style: { ...s.muted, marginTop: 4 } },
|
|
463
|
+
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"
|
|
464
|
+
),
|
|
465
|
+
login.error && React.createElement("div", { style: { ...s.muted, marginTop: 4, color: "var(--dsw-alias-state-warn-primary,#92400e)" } }, login.error)
|
|
466
|
+
) : React.createElement(
|
|
467
|
+
"div",
|
|
468
|
+
{ style: { display: "flex", gap: 8, marginTop: 4, flexWrap: "wrap", alignItems: "center" } },
|
|
469
|
+
React.createElement("button", {
|
|
470
|
+
style: { ...s.btnPri, opacity: busy ? 0.5 : 1 },
|
|
471
|
+
onClick: onLogin,
|
|
472
|
+
disabled: busy
|
|
473
|
+
}, busy ? "\u5904\u7406\u4E2D\u2026" : "\u626B\u7801\u767B\u5F55"),
|
|
474
|
+
login.phase === "error" && React.createElement("div", { style: { ...s.muted, fontSize: 12 } }, login.error ?? "\u767B\u5F55\u5931\u8D25")
|
|
475
|
+
)
|
|
476
|
+
),
|
|
477
|
+
// 高级设置(可折叠)
|
|
478
|
+
cfgDraft && React.createElement(
|
|
479
|
+
"div",
|
|
480
|
+
{ style: s.block },
|
|
481
|
+
React.createElement("button", {
|
|
482
|
+
style: { ...s.btnLink, fontSize: 12, marginBottom: showAdvanced ? 10 : 0 },
|
|
483
|
+
onClick: () => setShowAdvanced((v) => !v)
|
|
484
|
+
}, showAdvanced ? "\u25BE \u9AD8\u7EA7\u8BBE\u7F6E" : "\u25B8 \u9AD8\u7EA7\u8BBE\u7F6E"),
|
|
485
|
+
showAdvanced && React.createElement(
|
|
486
|
+
"div",
|
|
487
|
+
{ style: { display: "flex", flexDirection: "column", gap: 10 } },
|
|
488
|
+
// 心跳间隔
|
|
489
|
+
React.createElement(
|
|
490
|
+
"div",
|
|
491
|
+
null,
|
|
492
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5FC3\u8DF3\u95F4\u9694\uFF08\u79D2\uFF09\u2014 \u957F\u4EFB\u52A1\u5904\u7406\u4E2D\u6BCF\u9694\u591A\u4E45\u53D1\u4E00\u6B21\u8FDB\u5EA6\u63D0\u793A"),
|
|
493
|
+
React.createElement("input", {
|
|
494
|
+
style: { ...s.input, width: 120 },
|
|
495
|
+
type: "number",
|
|
496
|
+
min: 30,
|
|
497
|
+
max: 3600,
|
|
498
|
+
value: cfgDraft.digestIntervalSec,
|
|
499
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, digestIntervalSec: e.target.value }))
|
|
500
|
+
})
|
|
501
|
+
),
|
|
502
|
+
// 审批超时
|
|
503
|
+
React.createElement(
|
|
504
|
+
"div",
|
|
505
|
+
null,
|
|
506
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5BA1\u6279\u8D85\u65F6\uFF08\u79D2\uFF09\u2014 \u5DE5\u5177\u8C03\u7528\u5BA1\u6279\u65E0\u54CD\u5E94\u540E\u81EA\u52A8\u62D2\u7EDD"),
|
|
507
|
+
React.createElement("input", {
|
|
508
|
+
style: { ...s.input, width: 120 },
|
|
509
|
+
type: "number",
|
|
510
|
+
min: 30,
|
|
511
|
+
max: 86400,
|
|
512
|
+
value: cfgDraft.approvalTimeoutSec,
|
|
513
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, approvalTimeoutSec: e.target.value }))
|
|
514
|
+
})
|
|
515
|
+
),
|
|
516
|
+
// 每气泡字数
|
|
517
|
+
React.createElement(
|
|
518
|
+
"div",
|
|
519
|
+
null,
|
|
520
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u6BCF\u6761\u6D88\u606F\u6700\u5927\u5B57\u6570 \u2014 \u8D85\u51FA\u65F6\u81EA\u52A8\u5206\u591A\u6761\u53D1\u9001"),
|
|
521
|
+
React.createElement("input", {
|
|
522
|
+
style: { ...s.input, width: 120 },
|
|
523
|
+
type: "number",
|
|
524
|
+
min: 100,
|
|
525
|
+
max: 1e4,
|
|
526
|
+
value: cfgDraft.maxMessageChars,
|
|
527
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, maxMessageChars: e.target.value }))
|
|
528
|
+
})
|
|
529
|
+
),
|
|
530
|
+
// 分块延迟
|
|
531
|
+
React.createElement(
|
|
532
|
+
"div",
|
|
533
|
+
null,
|
|
534
|
+
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5206\u5757\u53D1\u9001\u5EF6\u8FDF\uFF08\u6BEB\u79D2\uFF09\u2014 \u591A\u6761\u6D88\u606F\u4E4B\u95F4\u7684\u95F4\u9694"),
|
|
535
|
+
React.createElement("input", {
|
|
536
|
+
style: { ...s.input, width: 120 },
|
|
537
|
+
type: "number",
|
|
538
|
+
min: 0,
|
|
539
|
+
max: 1e4,
|
|
540
|
+
value: cfgDraft.sendChunkDelayMs,
|
|
541
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, sendChunkDelayMs: e.target.value }))
|
|
542
|
+
})
|
|
543
|
+
),
|
|
544
|
+
React.createElement("button", {
|
|
545
|
+
style: { ...s.btnPri, alignSelf: "flex-start", opacity: cfgDirty && !busy ? 1 : 0.5 },
|
|
546
|
+
disabled: !cfgDirty || busy,
|
|
547
|
+
onClick: saveConfig
|
|
548
|
+
}, busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u8BBE\u7F6E")
|
|
549
|
+
)
|
|
550
|
+
),
|
|
551
|
+
React.createElement(
|
|
552
|
+
"div",
|
|
553
|
+
{ style: s.block },
|
|
554
|
+
React.createElement(
|
|
555
|
+
"div",
|
|
556
|
+
{ style: { ...s.tip, fontSize: 12 } },
|
|
557
|
+
"\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"
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
);
|
|
561
|
+
}
|
|
246
562
|
function VersionBanner({ rpcCall }) {
|
|
247
563
|
const [info, setInfo] = React.useState(null);
|
|
248
564
|
const [loading, setLoading] = React.useState(false);
|
|
@@ -345,9 +661,67 @@ function VersionBanner({ rpcCall }) {
|
|
|
345
661
|
links
|
|
346
662
|
);
|
|
347
663
|
}
|
|
664
|
+
var TABS = [
|
|
665
|
+
{ id: "lan", label: "\u5C40\u57DF\u7F51" },
|
|
666
|
+
{ id: "tunnel", label: "\u516C\u7F51\u96A7\u9053" },
|
|
667
|
+
{ id: "im", label: "IM \u673A\u5668\u4EBA" }
|
|
668
|
+
];
|
|
669
|
+
function TabBar({ active, onChange, dots }) {
|
|
670
|
+
return React.createElement(
|
|
671
|
+
"div",
|
|
672
|
+
{
|
|
673
|
+
style: {
|
|
674
|
+
display: "flex",
|
|
675
|
+
gap: 0,
|
|
676
|
+
marginBottom: 20,
|
|
677
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2,#e5e7eb)"
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
TABS.map(({ id, label }) => {
|
|
681
|
+
const isActive = active === id;
|
|
682
|
+
const hasDot = dots?.[id];
|
|
683
|
+
return React.createElement(
|
|
684
|
+
"button",
|
|
685
|
+
{
|
|
686
|
+
key: id,
|
|
687
|
+
onClick: () => onChange(id),
|
|
688
|
+
style: {
|
|
689
|
+
font: "inherit",
|
|
690
|
+
cursor: "pointer",
|
|
691
|
+
border: "none",
|
|
692
|
+
background: "none",
|
|
693
|
+
padding: "8px 16px",
|
|
694
|
+
fontSize: 13,
|
|
695
|
+
fontWeight: isActive ? 600 : 400,
|
|
696
|
+
color: isActive ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-label-secondary,#6b7280)",
|
|
697
|
+
borderBottom: isActive ? "2px solid var(--dsw-alias-brand-primary,#4f6ef7)" : "2px solid transparent",
|
|
698
|
+
marginBottom: -1,
|
|
699
|
+
display: "inline-flex",
|
|
700
|
+
alignItems: "center",
|
|
701
|
+
gap: 6,
|
|
702
|
+
transition: "color .15s, border-color .15s",
|
|
703
|
+
whiteSpace: "nowrap"
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
label,
|
|
707
|
+
hasDot && React.createElement("span", {
|
|
708
|
+
style: {
|
|
709
|
+
width: 6,
|
|
710
|
+
height: 6,
|
|
711
|
+
borderRadius: "50%",
|
|
712
|
+
background: "var(--dsw-alias-state-success-primary,#10b981)",
|
|
713
|
+
flexShrink: 0
|
|
714
|
+
}
|
|
715
|
+
})
|
|
716
|
+
);
|
|
717
|
+
})
|
|
718
|
+
);
|
|
719
|
+
}
|
|
348
720
|
function BridgePanel({ rpcCall }) {
|
|
349
721
|
const [status, setStatus] = React.useState(null);
|
|
350
722
|
const [err, setErr] = React.useState(null);
|
|
723
|
+
const [activeTab, setActiveTab] = React.useState("lan");
|
|
724
|
+
const [wechatConnected, setWechatConnected] = React.useState(false);
|
|
351
725
|
const load = React.useCallback(async (quiet = false) => {
|
|
352
726
|
try {
|
|
353
727
|
const r = await rpcCall(BRIDGE_ENDPOINTS.getStatus, {});
|
|
@@ -358,6 +732,25 @@ function BridgePanel({ rpcCall }) {
|
|
|
358
732
|
setErr(e.message);
|
|
359
733
|
}
|
|
360
734
|
}, [rpcCall]);
|
|
735
|
+
React.useEffect(() => {
|
|
736
|
+
let alive = true;
|
|
737
|
+
const poll = async () => {
|
|
738
|
+
try {
|
|
739
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.wechatGetStatus, {});
|
|
740
|
+
if (alive && r?.ok) {
|
|
741
|
+
const s2 = r.value?.status;
|
|
742
|
+
setWechatConnected(s2 === "connected" || s2 === "starting" || s2 === "reconnecting");
|
|
743
|
+
}
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
poll();
|
|
748
|
+
const t = setInterval(poll, 4e3);
|
|
749
|
+
return () => {
|
|
750
|
+
alive = false;
|
|
751
|
+
clearInterval(t);
|
|
752
|
+
};
|
|
753
|
+
}, [rpcCall]);
|
|
361
754
|
React.useEffect(() => {
|
|
362
755
|
load();
|
|
363
756
|
const t = setInterval(() => load(true), 3e3);
|
|
@@ -391,6 +784,110 @@ function BridgePanel({ rpcCall }) {
|
|
|
391
784
|
}, "\u52A0\u8F7D\u4E2D\u2026");
|
|
392
785
|
}
|
|
393
786
|
const ct = status?.customTunnel;
|
|
787
|
+
const dots = {
|
|
788
|
+
lan: !!status?.proxy?.running,
|
|
789
|
+
tunnel: !!(status?.cloudflared?.running || ct?.running),
|
|
790
|
+
im: wechatConnected
|
|
791
|
+
};
|
|
792
|
+
let tabContent;
|
|
793
|
+
if (activeTab === "lan") {
|
|
794
|
+
tabContent = React.createElement(TunnelCard, {
|
|
795
|
+
title: "\u5C40\u57DF\u7F51\u8BBF\u95EE",
|
|
796
|
+
desc: "\u540C\u4E00 Wi-Fi \u4E0B\u7684\u8BBE\u5907\u53EF\u76F4\u63A5\u626B\u7801\u8BBF\u95EE",
|
|
797
|
+
data: { running: status?.proxy?.running, url: status?.lan?.url, qr: status?.lan?.qr }
|
|
798
|
+
});
|
|
799
|
+
} else if (activeTab === "tunnel") {
|
|
800
|
+
tabContent = React.createElement(
|
|
801
|
+
React.Fragment,
|
|
802
|
+
null,
|
|
803
|
+
React.createElement(TunnelCard, {
|
|
804
|
+
title: "Cloudflare \u96A7\u9053",
|
|
805
|
+
desc: "\u4E00\u952E\u83B7\u53D6\u516C\u7F51\u5730\u5740\uFF08\u91CD\u542F\u540E URL \u4F1A\u53D8\u5316\uFF09",
|
|
806
|
+
data: {
|
|
807
|
+
running: status?.cloudflared?.running,
|
|
808
|
+
url: status?.cloudflared?.url,
|
|
809
|
+
qr: status?.cloudflared?.qr,
|
|
810
|
+
state: status?.cloudflared?.state
|
|
811
|
+
},
|
|
812
|
+
onStart: onStartCloudflared,
|
|
813
|
+
onStop: onStopCloudflared,
|
|
814
|
+
onReset: status?.cloudflared?.running ? onResetCloudflared : null
|
|
815
|
+
}),
|
|
816
|
+
React.createElement(
|
|
817
|
+
TunnelCard,
|
|
818
|
+
{
|
|
819
|
+
title: "\u81EA\u5EFA\u96A7\u9053",
|
|
820
|
+
desc: "\u8FDE\u63A5\u81EA\u5DF1\u90E8\u7F72\u7684\u96A7\u9053\u670D\u52A1\u5668\uFF0C\u83B7\u5F97\u56FA\u5B9A\u57DF\u540D",
|
|
821
|
+
data: {
|
|
822
|
+
configured: ct?.configured,
|
|
823
|
+
running: ct?.running,
|
|
824
|
+
url: ct?.url,
|
|
825
|
+
qr: ct?.qr,
|
|
826
|
+
state: ct?.state
|
|
827
|
+
},
|
|
828
|
+
onStart: onStartCustom,
|
|
829
|
+
onStop: onStopCustom
|
|
830
|
+
},
|
|
831
|
+
React.createElement(CustomTunnelGuide),
|
|
832
|
+
React.createElement(CustomTunnelConfigForm, {
|
|
833
|
+
serverUrl: ct?.serverUrl ?? "",
|
|
834
|
+
accessToken: ct?.accessToken ?? "",
|
|
835
|
+
onSave: saveConfig
|
|
836
|
+
})
|
|
837
|
+
)
|
|
838
|
+
);
|
|
839
|
+
} else if (activeTab === "im") {
|
|
840
|
+
const IM_PLATFORMS = [
|
|
841
|
+
{ id: "wechat", label: "\u5FAE\u4FE1", desc: "iLink Bot API\uFF08ClawBot\uFF09", available: true, active: wechatConnected },
|
|
842
|
+
{ id: "qq", label: "QQ", desc: "NapCat / Mirai", available: false, active: false },
|
|
843
|
+
{ id: "feishu", label: "\u98DE\u4E66", desc: "\u5B98\u65B9\u4E8B\u4EF6\u56DE\u8C03 API", available: false, active: false }
|
|
844
|
+
];
|
|
845
|
+
tabContent = React.createElement(
|
|
846
|
+
"div",
|
|
847
|
+
null,
|
|
848
|
+
// 平台选择器
|
|
849
|
+
React.createElement(
|
|
850
|
+
"div",
|
|
851
|
+
{
|
|
852
|
+
style: { display: "flex", gap: 10, marginBottom: 16, flexWrap: "wrap" }
|
|
853
|
+
},
|
|
854
|
+
IM_PLATFORMS.map(
|
|
855
|
+
({ id, label, desc, available, active }) => React.createElement(
|
|
856
|
+
"div",
|
|
857
|
+
{
|
|
858
|
+
key: id,
|
|
859
|
+
style: {
|
|
860
|
+
flex: "1 1 140px",
|
|
861
|
+
border: `1px solid ${active ? "var(--dsw-alias-state-success-primary,#10b981)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
|
|
862
|
+
borderRadius: 10,
|
|
863
|
+
padding: "12px 14px",
|
|
864
|
+
opacity: available ? 1 : 0.45,
|
|
865
|
+
cursor: available ? "default" : "not-allowed",
|
|
866
|
+
background: active ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : available ? "var(--dsw-alias-bg-layer-1,transparent)" : "var(--dsw-alias-bg-layer-2,#f9fafb)"
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
React.createElement(
|
|
870
|
+
"div",
|
|
871
|
+
{ style: { ...s.label, fontSize: 13, display: "flex", alignItems: "center", gap: 6 } },
|
|
872
|
+
label,
|
|
873
|
+
active && React.createElement("span", {
|
|
874
|
+
style: { width: 6, height: 6, borderRadius: "50%", background: "var(--dsw-alias-state-success-primary,#10b981)", flexShrink: 0 }
|
|
875
|
+
}),
|
|
876
|
+
!active && available && React.createElement("span", {
|
|
877
|
+
style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary,#6b7280)", fontWeight: 400 }
|
|
878
|
+
}, "\u672A\u8FDE\u63A5"),
|
|
879
|
+
!available && React.createElement("span", {
|
|
880
|
+
style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary,#9ca3af)", fontWeight: 400 }
|
|
881
|
+
}, "\u5373\u5C06\u652F\u6301")
|
|
882
|
+
),
|
|
883
|
+
React.createElement("div", { style: { ...s.muted, marginTop: 3, fontSize: 11 } }, desc)
|
|
884
|
+
)
|
|
885
|
+
)
|
|
886
|
+
),
|
|
887
|
+
// 微信卡片(onStatusChange 向上报连接状态)
|
|
888
|
+
React.createElement(WechatCard, { rpcCall, onStatusChange: setWechatConnected })
|
|
889
|
+
);
|
|
890
|
+
}
|
|
394
891
|
return React.createElement(
|
|
395
892
|
"div",
|
|
396
893
|
{ style: { maxWidth: 560 } },
|
|
@@ -398,46 +895,8 @@ function BridgePanel({ rpcCall }) {
|
|
|
398
895
|
style: { ...s.card, background: "var(--dsw-alias-state-error-bg,#fef2f2)", color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 13, marginBottom: 16 }
|
|
399
896
|
}, err),
|
|
400
897
|
React.createElement(VersionBanner, { rpcCall }),
|
|
401
|
-
React.createElement(
|
|
402
|
-
|
|
403
|
-
desc: "\u540C\u4E00 Wi-Fi \u4E0B\u7684\u8BBE\u5907\u53EF\u76F4\u63A5\u626B\u7801\u8BBF\u95EE",
|
|
404
|
-
data: { running: status?.proxy?.running, url: status?.lan?.url, qr: status?.lan?.qr }
|
|
405
|
-
}),
|
|
406
|
-
React.createElement(TunnelCard, {
|
|
407
|
-
title: "Cloudflare \u96A7\u9053",
|
|
408
|
-
desc: "\u4E00\u952E\u83B7\u53D6\u516C\u7F51\u5730\u5740\uFF08\u91CD\u542F\u540E URL \u4F1A\u53D8\u5316\uFF09",
|
|
409
|
-
data: {
|
|
410
|
-
running: status?.cloudflared?.running,
|
|
411
|
-
url: status?.cloudflared?.url,
|
|
412
|
-
qr: status?.cloudflared?.qr,
|
|
413
|
-
state: status?.cloudflared?.state
|
|
414
|
-
},
|
|
415
|
-
onStart: onStartCloudflared,
|
|
416
|
-
onStop: onStopCloudflared,
|
|
417
|
-
onReset: status?.cloudflared?.running ? onResetCloudflared : null
|
|
418
|
-
}),
|
|
419
|
-
React.createElement(
|
|
420
|
-
TunnelCard,
|
|
421
|
-
{
|
|
422
|
-
title: "\u81EA\u5EFA\u96A7\u9053",
|
|
423
|
-
desc: "\u8FDE\u63A5\u81EA\u5DF1\u90E8\u7F72\u7684\u96A7\u9053\u670D\u52A1\u5668\uFF0C\u83B7\u5F97\u56FA\u5B9A\u57DF\u540D",
|
|
424
|
-
data: {
|
|
425
|
-
configured: ct?.configured,
|
|
426
|
-
running: ct?.running,
|
|
427
|
-
url: ct?.url,
|
|
428
|
-
qr: ct?.qr,
|
|
429
|
-
state: ct?.state
|
|
430
|
-
},
|
|
431
|
-
onStart: onStartCustom,
|
|
432
|
-
onStop: onStopCustom
|
|
433
|
-
},
|
|
434
|
-
React.createElement(CustomTunnelGuide),
|
|
435
|
-
React.createElement(CustomTunnelConfigForm, {
|
|
436
|
-
serverUrl: ct?.serverUrl ?? "",
|
|
437
|
-
accessToken: ct?.accessToken ?? "",
|
|
438
|
-
onSave: saveConfig
|
|
439
|
-
})
|
|
440
|
-
)
|
|
898
|
+
React.createElement(TabBar, { active: activeTab, onChange: setActiveTab, dots }),
|
|
899
|
+
tabContent
|
|
441
900
|
);
|
|
442
901
|
}
|
|
443
902
|
function apply(ctx) {
|