@wenbin_wb/dsh-bridge 2.2.4 → 2.2.5
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 +70 -6
- package/README.zh-CN.md +70 -6
- package/client/client.js +310 -128
- package/client/index.js +265 -113
- package/docs/qq-usage.md +385 -0
- package/lib/index.js +39 -0
- package/lib/platform/conversation-bridge.js +5 -3
- package/lib/qq/index.js +2 -0
- package/lib/tunnel-client.mjs +0 -3
- package/lib/wechat/node.js +42 -16
- package/package.json +3 -2
package/client/index.js
CHANGED
|
@@ -36,17 +36,17 @@ function semverGt(a, b) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const s = {
|
|
39
|
-
card: { background: 'var(--dsw-alias-bg-layer-
|
|
39
|
+
card: { background: 'var(--dsw-alias-bg-layer-2,#f9fafb)', border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', borderRadius: 12, padding: '16px 20px', marginBottom: 16 },
|
|
40
40
|
block: { borderTop: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', marginTop: 12, paddingTop: 12 },
|
|
41
41
|
muted: { color: 'var(--dsw-alias-label-tertiary,#8b93a1)', fontSize: 12, lineHeight: 1.5 },
|
|
42
42
|
label: { color: 'var(--dsw-alias-label-primary,currentColor)', fontSize: 13, fontWeight: 500 },
|
|
43
43
|
code: { fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 12, wordBreak: 'break-all', color: 'var(--dsw-alias-label-primary,currentColor)' },
|
|
44
44
|
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 },
|
|
45
|
-
btnGhost: { font: 'inherit', cursor: 'pointer', border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-
|
|
45
|
+
btnGhost: { font: 'inherit', cursor: 'pointer', border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-2,#f9fafb)', 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' },
|
|
46
46
|
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' },
|
|
47
|
-
qr: { width: 200, height: 200, borderRadius: 10, border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', margin: '8px 0', display: 'block' },
|
|
47
|
+
qr: { width: 200, height: 200, borderRadius: 10, border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', margin: '8px 0', display: 'block', background: '#ffffff', padding: 6, boxSizing: 'border-box' },
|
|
48
48
|
tag: { display: 'inline-flex', alignItems: 'center', padding: '2px 8px', borderRadius: 999, fontSize: 12, fontWeight: 500 },
|
|
49
|
-
input: { width: '100%', font: 'inherit', fontSize: 13, padding: '7px 10px', borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-
|
|
49
|
+
input: { width: '100%', font: 'inherit', fontSize: 13, padding: '7px 10px', borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-2,#f9fafb)', color: 'var(--dsw-alias-label-primary,currentColor)', outline: 'none', boxSizing: 'border-box' },
|
|
50
50
|
warn: { background: 'var(--dsw-alias-state-warn-bg,#fffbeb)', border: '1px solid var(--dsw-alias-state-warn-border,#fde68a)', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: 'var(--dsw-alias-state-warn-primary,#92400e)', lineHeight: 1.6 },
|
|
51
51
|
tip: { background: 'var(--dsw-alias-bg-layer-2,#f9fafb)', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', lineHeight: 1.6 },
|
|
52
52
|
};
|
|
@@ -82,14 +82,40 @@ function useCopy() {
|
|
|
82
82
|
return [copied, copy];
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
function StatusTag({ running }) {
|
|
85
|
+
function StatusTag({ running, status }) {
|
|
86
|
+
let bg = 'var(--dsw-alias-bg-layer-2,#f3f4f6)';
|
|
87
|
+
let color = 'var(--dsw-alias-label-secondary,#6b7280)';
|
|
88
|
+
let text = running ? '运行中' : '未启动';
|
|
89
|
+
|
|
90
|
+
if (status === 'connected') {
|
|
91
|
+
bg = 'var(--dsw-alias-state-success-bg,#ecfdf5)';
|
|
92
|
+
color = 'var(--dsw-alias-state-success-primary,#059669)';
|
|
93
|
+
text = '已连接';
|
|
94
|
+
} else if (status === 'starting') {
|
|
95
|
+
bg = 'var(--dsw-alias-state-info-bg,#eff6ff)';
|
|
96
|
+
color = 'var(--dsw-alias-state-info-primary,#3b82f6)';
|
|
97
|
+
text = '连接中…';
|
|
98
|
+
} else if (status === 'reconnecting') {
|
|
99
|
+
bg = 'var(--dsw-alias-state-warn-bg,#fffbeb)';
|
|
100
|
+
color = 'var(--dsw-alias-state-warn-primary,#d97706)';
|
|
101
|
+
text = '重连中…';
|
|
102
|
+
} else if (status === 'paused') {
|
|
103
|
+
bg = 'var(--dsw-alias-state-warn-bg,#fffbeb)';
|
|
104
|
+
color = 'var(--dsw-alias-state-warn-primary,#d97706)';
|
|
105
|
+
text = '暂停中';
|
|
106
|
+
} else if (status === 'error') {
|
|
107
|
+
bg = 'var(--dsw-alias-state-error-bg,#fef2f2)';
|
|
108
|
+
color = 'var(--dsw-alias-state-error-primary,#dc2626)';
|
|
109
|
+
text = '异常';
|
|
110
|
+
} else if (running) {
|
|
111
|
+
bg = 'var(--dsw-alias-state-success-bg,#ecfdf5)';
|
|
112
|
+
color = 'var(--dsw-alias-state-success-primary,#059669)';
|
|
113
|
+
text = '运行中';
|
|
114
|
+
}
|
|
115
|
+
|
|
86
116
|
return React.createElement('span', {
|
|
87
|
-
style: {
|
|
88
|
-
|
|
89
|
-
background: running ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' : 'var(--dsw-alias-bg-layer-2,#f3f4f6)',
|
|
90
|
-
color: running ? 'var(--dsw-alias-state-success-primary,#059669)' : 'var(--dsw-alias-label-secondary,#6b7280)',
|
|
91
|
-
},
|
|
92
|
-
}, running ? '运行中' : '未启动');
|
|
117
|
+
style: { ...s.tag, background: bg, color },
|
|
118
|
+
}, text);
|
|
93
119
|
}
|
|
94
120
|
|
|
95
121
|
function QrBlock({ url, qr, onReset }) {
|
|
@@ -172,6 +198,7 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
|
|
|
172
198
|
const [serverUrl, setServerUrl] = React.useState(initUrl ?? '');
|
|
173
199
|
const [accessToken, setAccessToken] = React.useState(initToken ?? '');
|
|
174
200
|
const [saving, setSaving] = React.useState(false);
|
|
201
|
+
const [saveSuccess, setSaveSuccess] = React.useState(false);
|
|
175
202
|
|
|
176
203
|
const syncedRef = React.useRef(false);
|
|
177
204
|
React.useEffect(() => {
|
|
@@ -185,11 +212,16 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
|
|
|
185
212
|
const dirty = serverUrl !== (initUrl ?? '') || accessToken !== (initToken ?? '');
|
|
186
213
|
const handleSave = React.useCallback(async () => {
|
|
187
214
|
setSaving(true);
|
|
188
|
-
|
|
215
|
+
setSaveSuccess(false);
|
|
216
|
+
try {
|
|
217
|
+
await onSave(serverUrl, accessToken);
|
|
218
|
+
setSaveSuccess(true);
|
|
219
|
+
setTimeout(() => setSaveSuccess(false), 2500);
|
|
220
|
+
}
|
|
189
221
|
finally { setSaving(false); }
|
|
190
222
|
}, [onSave, serverUrl, accessToken]);
|
|
191
|
-
const handleUrlChange = React.useCallback((e) => setServerUrl(e.target.value), []);
|
|
192
|
-
const handleTokenChange = React.useCallback((e) => setAccessToken(e.target.value), []);
|
|
223
|
+
const handleUrlChange = React.useCallback((e) => { setServerUrl(e.target.value); setSaveSuccess(false); }, []);
|
|
224
|
+
const handleTokenChange = React.useCallback((e) => { setAccessToken(e.target.value); setSaveSuccess(false); }, []);
|
|
193
225
|
|
|
194
226
|
return React.createElement('div', { style: s.block },
|
|
195
227
|
React.createElement('div', { style: { ...s.muted, marginBottom: 8 } }, '服务器配置'),
|
|
@@ -199,6 +231,7 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
|
|
|
199
231
|
placeholder: 'WebSocket 地址,例如 wss://tunnel.example.com/connect',
|
|
200
232
|
value: serverUrl,
|
|
201
233
|
onChange: handleUrlChange,
|
|
234
|
+
onKeyDown: (e) => { if (e.key === 'Enter' && dirty && !saving) handleSave(); },
|
|
202
235
|
disabled: saving,
|
|
203
236
|
}),
|
|
204
237
|
React.createElement('input', {
|
|
@@ -207,13 +240,19 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
|
|
|
207
240
|
placeholder: '访问令牌(Access Token)',
|
|
208
241
|
value: accessToken,
|
|
209
242
|
onChange: handleTokenChange,
|
|
243
|
+
onKeyDown: (e) => { if (e.key === 'Enter' && dirty && !saving) handleSave(); },
|
|
210
244
|
disabled: saving,
|
|
211
245
|
}),
|
|
212
246
|
React.createElement('button', {
|
|
213
|
-
style: {
|
|
214
|
-
|
|
247
|
+
style: {
|
|
248
|
+
...s.btnPri,
|
|
249
|
+
alignSelf: 'flex-start',
|
|
250
|
+
opacity: (!dirty || saving) ? (saveSuccess ? 1 : 0.5) : 1,
|
|
251
|
+
background: saveSuccess ? 'var(--dsw-alias-state-success-primary,#059669)' : undefined,
|
|
252
|
+
},
|
|
253
|
+
disabled: (!dirty && !saveSuccess) || saving,
|
|
215
254
|
onClick: handleSave,
|
|
216
|
-
}, saving ? '保存中…' : '保存配置'),
|
|
255
|
+
}, saving ? '保存中…' : saveSuccess ? '✓ 已保存' : '保存配置'),
|
|
217
256
|
),
|
|
218
257
|
);
|
|
219
258
|
});
|
|
@@ -338,6 +377,20 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
338
377
|
}, [act, platform?.allowFrom]);
|
|
339
378
|
const handleNewId = React.useCallback((e) => setNewId(e.target.value), []);
|
|
340
379
|
|
|
380
|
+
// 密码明文/密文切换
|
|
381
|
+
const [showSecret, setShowSecret] = React.useState(false);
|
|
382
|
+
|
|
383
|
+
// 恢复推荐默认值
|
|
384
|
+
const resetDefaults = React.useCallback(() => {
|
|
385
|
+
setCfgDraft((d) => ({
|
|
386
|
+
...d,
|
|
387
|
+
digestIntervalSec: '300',
|
|
388
|
+
approvalTimeoutSec: '600',
|
|
389
|
+
maxMessageChars: '2000',
|
|
390
|
+
sendChunkDelayMs: '1500',
|
|
391
|
+
}));
|
|
392
|
+
}, []);
|
|
393
|
+
|
|
341
394
|
// 高级设置保存
|
|
342
395
|
const saveConfig = React.useCallback(async () => {
|
|
343
396
|
if (!cfgDraft) return;
|
|
@@ -388,16 +441,26 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
388
441
|
React.createElement('div', { style: s.label }, platformName),
|
|
389
442
|
React.createElement('div', { style: { ...s.muted, marginTop: 2 } }, platformDesc),
|
|
390
443
|
),
|
|
391
|
-
React.createElement(StatusTag, { running: connected }),
|
|
444
|
+
React.createElement(StatusTag, { status: platform?.status, running: connected }),
|
|
392
445
|
),
|
|
393
446
|
|
|
394
|
-
// 快捷入口:使用说明 /
|
|
447
|
+
// 快捷入口:使用说明 / 开放平台 / 命令速查
|
|
395
448
|
React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap', alignItems: 'center' } },
|
|
396
449
|
platformId === 'wechat' && React.createElement('a', {
|
|
397
450
|
href: 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/wechat-usage.md',
|
|
398
451
|
target: '_blank', rel: 'noopener noreferrer',
|
|
399
452
|
style: s.btnGhost,
|
|
400
|
-
}, '📖
|
|
453
|
+
}, '📖 微信使用说明'),
|
|
454
|
+
platformId === 'qq' && React.createElement('a', {
|
|
455
|
+
href: 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/qq-usage.md',
|
|
456
|
+
target: '_blank', rel: 'noopener noreferrer',
|
|
457
|
+
style: s.btnGhost,
|
|
458
|
+
}, '📖 QQ 使用说明'),
|
|
459
|
+
platformId === 'qq' && React.createElement('a', {
|
|
460
|
+
href: 'https://bot.q.qq.com/wiki/develop/api-v2/',
|
|
461
|
+
target: '_blank', rel: 'noopener noreferrer',
|
|
462
|
+
style: s.btnGhost,
|
|
463
|
+
}, '🌐 QQ 开放平台'),
|
|
401
464
|
React.createElement('button', {
|
|
402
465
|
style: s.btnGhost,
|
|
403
466
|
onClick: () => setShowHelp(v => !v),
|
|
@@ -406,28 +469,75 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
406
469
|
|
|
407
470
|
// 命令速查
|
|
408
471
|
showHelp && React.createElement('div', { style: { ...s.block, fontSize: 12, lineHeight: 1.8, fontFamily: 'monospace' } },
|
|
409
|
-
React.createElement('div', null, '/new <提示词> —
|
|
410
|
-
React.createElement('div', null, '/new <提示词> @N —
|
|
411
|
-
React.createElement('div', null, '/sessions
|
|
412
|
-
React.createElement('div', null, '/use N
|
|
413
|
-
React.createElement('div', null, '/workspaces —
|
|
414
|
-
React.createElement('div', null, '/
|
|
415
|
-
React.createElement('div', null, '/
|
|
416
|
-
React.createElement('div', null, '/
|
|
417
|
-
React.createElement('div', null, '/
|
|
472
|
+
React.createElement('div', null, '/new <提示词> — 新建会话并开始(当前工作区)'),
|
|
473
|
+
React.createElement('div', null, '/new <提示词> @N — 在指定工作区新建会话'),
|
|
474
|
+
React.createElement('div', null, '/sessions(或 /list)— 列出会话(按工作区分组,带标题)'),
|
|
475
|
+
React.createElement('div', null, '/use N(或 /resume N)— 切换到会话 N'),
|
|
476
|
+
React.createElement('div', null, '/workspaces — 列出所有可用工作区'),
|
|
477
|
+
React.createElement('div', null, '/end — 结束当前会话(回到无活动会话状态)'),
|
|
478
|
+
React.createElement('div', null, '/stop — 停止当前任务'),
|
|
479
|
+
React.createElement('div', null, '/status — 查看 Agent 状态与会话摘要'),
|
|
480
|
+
React.createElement('div', null, '/yes 或 /no(或 1/2)— 回应权限审批请求'),
|
|
481
|
+
React.createElement('div', null, '/help — 显示完整命令帮助'),
|
|
418
482
|
),
|
|
419
483
|
|
|
420
484
|
err && React.createElement('div', { style: { ...s.warn, marginTop: 10 } }, err),
|
|
421
485
|
|
|
422
|
-
//
|
|
486
|
+
// 已配置:结构化状态看板 + 白名单
|
|
423
487
|
platform?.configured && React.createElement('div', { style: s.block },
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
488
|
+
// 结构化状态卡片看板
|
|
489
|
+
React.createElement('div', {
|
|
490
|
+
style: {
|
|
491
|
+
display: 'grid',
|
|
492
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))',
|
|
493
|
+
gap: 8,
|
|
494
|
+
marginBottom: 12,
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
React.createElement('div', {
|
|
498
|
+
style: {
|
|
499
|
+
background: 'var(--dsw-alias-bg-layer-1,#fff)',
|
|
500
|
+
border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
501
|
+
borderRadius: 8,
|
|
502
|
+
padding: '8px 12px',
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11 } }, '连接状态'),
|
|
506
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, marginTop: 2, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
507
|
+
React.createElement('span', {
|
|
508
|
+
style: {
|
|
509
|
+
width: 6, height: 6, borderRadius: '50%',
|
|
510
|
+
background: connected ? 'var(--dsw-alias-state-success-primary,#10b981)' : 'var(--dsw-alias-label-tertiary,#9ca3af)',
|
|
511
|
+
},
|
|
512
|
+
}),
|
|
513
|
+
statusLabel,
|
|
514
|
+
),
|
|
515
|
+
),
|
|
516
|
+
platform.accountId && React.createElement('div', {
|
|
517
|
+
style: {
|
|
518
|
+
background: 'var(--dsw-alias-bg-layer-1,#fff)',
|
|
519
|
+
border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
520
|
+
borderRadius: 8,
|
|
521
|
+
padding: '8px 12px',
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11 } }, '登录账号'),
|
|
525
|
+
React.createElement('div', { style: { ...s.code, fontSize: 12, marginTop: 2, fontWeight: 500 } }, platform.accountId),
|
|
526
|
+
),
|
|
527
|
+
platform.sessionId && React.createElement('div', {
|
|
528
|
+
style: {
|
|
529
|
+
background: 'var(--dsw-alias-bg-layer-1,#fff)',
|
|
530
|
+
border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
531
|
+
borderRadius: 8,
|
|
532
|
+
padding: '8px 12px',
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11 } }, '活动会话'),
|
|
536
|
+
React.createElement('div', { style: { ...s.code, fontSize: 12, marginTop: 2 } }, platform.sessionId),
|
|
537
|
+
),
|
|
428
538
|
),
|
|
429
539
|
React.createElement('div', { style: { ...s.muted, fontSize: 12, marginTop: 8, lineHeight: 1.6 } },
|
|
430
|
-
|
|
540
|
+
`白名单 (已授权 ${platform.allowFrom?.length || 0} 个账号/群):`
|
|
431
541
|
),
|
|
432
542
|
React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 6 } },
|
|
433
543
|
(platform.allowFrom?.length
|
|
@@ -449,9 +559,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
449
559
|
React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 8, alignItems: 'center' } },
|
|
450
560
|
React.createElement('input', {
|
|
451
561
|
style: { ...s.input, flex: 1 },
|
|
452
|
-
placeholder: platformId === 'wechat' ? '添加允许的微信 ID(如 xxx@im.wechat
|
|
562
|
+
placeholder: platformId === 'wechat' ? '添加允许的微信 ID(如 xxx@im.wechat),按 Enter 添加' : '添加允许的用户/群 ID,按 Enter 添加',
|
|
453
563
|
value: newId,
|
|
454
564
|
onChange: handleNewId,
|
|
565
|
+
onKeyDown: (e) => { if (e.key === 'Enter' && newId.trim() && !busy) addAllow(); },
|
|
455
566
|
}),
|
|
456
567
|
React.createElement('button', {
|
|
457
568
|
style: { ...s.btnGhost, whiteSpace: 'nowrap', opacity: (newId.trim() && !busy) ? 1 : 0.5 },
|
|
@@ -493,16 +604,27 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
493
604
|
placeholder: '请输入 AppID',
|
|
494
605
|
value: cfgDraft?.appId ?? '',
|
|
495
606
|
onChange: (e) => setCfgDraft(d => ({ ...d, appId: e.target.value })),
|
|
607
|
+
onKeyDown: (e) => { if (e.key === 'Enter' && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig(); },
|
|
496
608
|
}),
|
|
497
609
|
),
|
|
498
610
|
React.createElement('div', null,
|
|
499
611
|
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'ClientSecret — QQ 开放平台机器人密钥'),
|
|
500
|
-
React.createElement('
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
612
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
|
|
613
|
+
React.createElement('input', {
|
|
614
|
+
style: { ...s.input, flex: 1 },
|
|
615
|
+
type: showSecret ? 'text' : 'password',
|
|
616
|
+
placeholder: '请输入 ClientSecret',
|
|
617
|
+
value: cfgDraft?.clientSecret ?? '',
|
|
618
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, clientSecret: e.target.value })),
|
|
619
|
+
onKeyDown: (e) => { if (e.key === 'Enter' && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig(); },
|
|
620
|
+
}),
|
|
621
|
+
React.createElement('button', {
|
|
622
|
+
style: { ...s.btnGhost, height: 32, padding: '0 10px', fontSize: 13, flexShrink: 0 },
|
|
623
|
+
onClick: () => setShowSecret(v => !v),
|
|
624
|
+
type: 'button',
|
|
625
|
+
title: showSecret ? '隐藏密钥' : '显示明文',
|
|
626
|
+
}, showSecret ? '🙈 隐藏' : '👁️ 显示'),
|
|
627
|
+
),
|
|
506
628
|
),
|
|
507
629
|
React.createElement('div', null,
|
|
508
630
|
React.createElement('a', {
|
|
@@ -528,79 +650,104 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
528
650
|
),
|
|
529
651
|
),
|
|
530
652
|
|
|
531
|
-
//
|
|
653
|
+
// 高级设置(紧凑 2x2 网格 + 恢复默认值)
|
|
532
654
|
cfgDraft && React.createElement('div', { style: s.block },
|
|
533
655
|
React.createElement('button', {
|
|
534
656
|
style: { ...s.btnLink, fontSize: 12, marginBottom: showAdvanced ? 10 : 0 },
|
|
535
657
|
onClick: () => setShowAdvanced(v => !v),
|
|
536
658
|
}, showAdvanced ? '▾ 高级设置' : '▸ 高级设置'),
|
|
537
659
|
showAdvanced && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10 } },
|
|
538
|
-
//
|
|
539
|
-
React.createElement('div',
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
660
|
+
// 2x2 参数网格
|
|
661
|
+
React.createElement('div', {
|
|
662
|
+
style: {
|
|
663
|
+
display: 'grid',
|
|
664
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
665
|
+
gap: 10,
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
// 心跳间隔
|
|
669
|
+
React.createElement('div', null,
|
|
670
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '心跳进度间隔 (秒)'),
|
|
671
|
+
React.createElement('input', {
|
|
672
|
+
style: { ...s.input, width: '100%' },
|
|
673
|
+
type: 'number', min: 30, max: 3600,
|
|
674
|
+
value: cfgDraft.digestIntervalSec,
|
|
675
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, digestIntervalSec: e.target.value })),
|
|
676
|
+
}),
|
|
677
|
+
),
|
|
678
|
+
// 审批超时
|
|
679
|
+
React.createElement('div', null,
|
|
680
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '审批超时自动拒绝 (秒)'),
|
|
681
|
+
React.createElement('input', {
|
|
682
|
+
style: { ...s.input, width: '100%' },
|
|
683
|
+
type: 'number', min: 30, max: 86400,
|
|
684
|
+
value: cfgDraft.approvalTimeoutSec,
|
|
685
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, approvalTimeoutSec: e.target.value })),
|
|
686
|
+
}),
|
|
687
|
+
),
|
|
688
|
+
// 每气泡字数
|
|
689
|
+
React.createElement('div', null,
|
|
690
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '单条最大字数限制 (字)'),
|
|
691
|
+
React.createElement('input', {
|
|
692
|
+
style: { ...s.input, width: '100%' },
|
|
693
|
+
type: 'number', min: 100, max: 10000,
|
|
694
|
+
value: cfgDraft.maxMessageChars,
|
|
695
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, maxMessageChars: e.target.value })),
|
|
696
|
+
}),
|
|
697
|
+
),
|
|
698
|
+
// 分块延迟
|
|
699
|
+
React.createElement('div', null,
|
|
700
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '分块发送延迟 (毫秒)'),
|
|
701
|
+
React.createElement('input', {
|
|
702
|
+
style: { ...s.input, width: '100%' },
|
|
703
|
+
type: 'number', min: 0, max: 10000,
|
|
704
|
+
value: cfgDraft.sendChunkDelayMs,
|
|
705
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, sendChunkDelayMs: e.target.value })),
|
|
706
|
+
}),
|
|
707
|
+
),
|
|
577
708
|
),
|
|
578
709
|
// QQ 平台凭证
|
|
579
|
-
platformId === 'qq' && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10 } },
|
|
710
|
+
platformId === 'qq' && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10, marginTop: 4 } },
|
|
580
711
|
React.createElement('div', null,
|
|
581
712
|
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'AppID — QQ 开放平台机器人应用 ID'),
|
|
582
713
|
React.createElement('input', {
|
|
583
|
-
style: { ...s.input, width:
|
|
714
|
+
style: { ...s.input, width: '100%' },
|
|
584
715
|
placeholder: '请输入 AppID',
|
|
585
716
|
value: cfgDraft.appId,
|
|
586
717
|
onChange: (e) => setCfgDraft(d => ({ ...d, appId: e.target.value })),
|
|
587
718
|
}),
|
|
588
719
|
),
|
|
589
720
|
React.createElement('div', null,
|
|
590
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'ClientSecret — QQ
|
|
591
|
-
React.createElement('
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
721
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'ClientSecret — QQ 开放平台机器人密钥(留空则保持已保存密钥)'),
|
|
722
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
|
|
723
|
+
React.createElement('input', {
|
|
724
|
+
style: { ...s.input, flex: 1 },
|
|
725
|
+
type: showSecret ? 'text' : 'password',
|
|
726
|
+
placeholder: '留空保持已保存密钥',
|
|
727
|
+
value: cfgDraft.clientSecret,
|
|
728
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, clientSecret: e.target.value })),
|
|
729
|
+
}),
|
|
730
|
+
React.createElement('button', {
|
|
731
|
+
style: { ...s.btnGhost, height: 32, padding: '0 10px', fontSize: 13, flexShrink: 0 },
|
|
732
|
+
onClick: () => setShowSecret(v => !v),
|
|
733
|
+
type: 'button',
|
|
734
|
+
title: showSecret ? '隐藏密钥' : '显示明文',
|
|
735
|
+
}, showSecret ? '🙈 隐藏' : '👁️ 显示'),
|
|
736
|
+
),
|
|
597
737
|
),
|
|
598
738
|
),
|
|
599
|
-
React.createElement('
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
739
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', marginTop: 4 } },
|
|
740
|
+
React.createElement('button', {
|
|
741
|
+
style: { ...s.btnPri, opacity: (cfgDirty && !busy) ? 1 : 0.5 },
|
|
742
|
+
disabled: !cfgDirty || busy,
|
|
743
|
+
onClick: saveConfig,
|
|
744
|
+
}, busy ? '保存中…' : '保存设置'),
|
|
745
|
+
React.createElement('button', {
|
|
746
|
+
style: { ...s.btnGhost, height: 32, fontSize: 12 },
|
|
747
|
+
onClick: resetDefaults,
|
|
748
|
+
title: '恢复推荐默认配置',
|
|
749
|
+
}, '↺ 恢复推荐默认'),
|
|
750
|
+
),
|
|
604
751
|
),
|
|
605
752
|
),
|
|
606
753
|
|
|
@@ -738,19 +885,19 @@ function VersionBanner({ rpcCall }) {
|
|
|
738
885
|
// ---- Tab Bar ----
|
|
739
886
|
|
|
740
887
|
const TABS = [
|
|
741
|
-
{ id: 'lan', label: '局域网' },
|
|
742
|
-
{ id: 'tunnel', label: '公网隧道' },
|
|
743
|
-
{ id: 'im', label: 'IM 机器人' },
|
|
888
|
+
{ id: 'lan', label: '局域网', icon: '📶' },
|
|
889
|
+
{ id: 'tunnel', label: '公网隧道', icon: '🌐' },
|
|
890
|
+
{ id: 'im', label: 'IM 机器人', icon: '🤖' },
|
|
744
891
|
];
|
|
745
892
|
|
|
746
893
|
function TabBar({ active, onChange, dots }) {
|
|
747
894
|
return React.createElement('div', {
|
|
748
895
|
style: {
|
|
749
|
-
display: 'flex', gap:
|
|
896
|
+
display: 'flex', gap: 4, marginBottom: 20,
|
|
750
897
|
borderBottom: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
751
898
|
},
|
|
752
899
|
},
|
|
753
|
-
TABS.map(({ id, label }) => {
|
|
900
|
+
TABS.map(({ id, label, icon }) => {
|
|
754
901
|
const isActive = active === id;
|
|
755
902
|
const hasDot = dots?.[id];
|
|
756
903
|
return React.createElement('button', {
|
|
@@ -771,6 +918,7 @@ function TabBar({ active, onChange, dots }) {
|
|
|
771
918
|
whiteSpace: 'nowrap',
|
|
772
919
|
},
|
|
773
920
|
},
|
|
921
|
+
React.createElement('span', { style: { fontSize: 14 } }, icon),
|
|
774
922
|
label,
|
|
775
923
|
hasDot && React.createElement('span', {
|
|
776
924
|
style: {
|
|
@@ -914,9 +1062,9 @@ function BridgePanel({ rpcCall }) {
|
|
|
914
1062
|
} else if (activeTab === 'im') {
|
|
915
1063
|
// 从 listPlatforms 动态生成平台列表
|
|
916
1064
|
const IM_PLATFORMS = [
|
|
917
|
-
{ id: 'wechat', label: '微信', desc: '
|
|
918
|
-
{ id: 'qq', label: 'QQ', desc: '
|
|
919
|
-
{ id: 'feishu', label: '飞书', desc: '官方事件回调 API' },
|
|
1065
|
+
{ id: 'wechat', label: '微信', icon: '💬', desc: 'ClawBot 扫码直连 · 无需公网' },
|
|
1066
|
+
{ id: 'qq', label: 'QQ', icon: '🐧', desc: '官方机器人 · 私聊/群聊/按钮' },
|
|
1067
|
+
{ id: 'feishu', label: '飞书', icon: '🕊️', desc: '官方事件回调 API' },
|
|
920
1068
|
];
|
|
921
1069
|
|
|
922
1070
|
tabContent = React.createElement('div', null,
|
|
@@ -924,7 +1072,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
924
1072
|
React.createElement('div', {
|
|
925
1073
|
style: { display: 'flex', gap: 10, marginBottom: 16, flexWrap: 'wrap' },
|
|
926
1074
|
},
|
|
927
|
-
IM_PLATFORMS.map(({ id, label, desc }) => {
|
|
1075
|
+
IM_PLATFORMS.map(({ id, label, icon, desc }) => {
|
|
928
1076
|
const platformData = platforms?.[id];
|
|
929
1077
|
const available = !!platformData;
|
|
930
1078
|
const active = platformData?.status === 'connected' || platformData?.status === 'starting' || platformData?.status === 'reconnecting';
|
|
@@ -932,19 +1080,23 @@ function BridgePanel({ rpcCall }) {
|
|
|
932
1080
|
return React.createElement('div', {
|
|
933
1081
|
key: id,
|
|
934
1082
|
style: {
|
|
935
|
-
flex: '1 1
|
|
936
|
-
border: `1px solid ${selectedPlatform === id ? 'var(--dsw-alias-
|
|
1083
|
+
flex: '1 1 150px',
|
|
1084
|
+
border: `1px solid ${selectedPlatform === id ? 'var(--dsw-alias-brand-primary,#4f6ef7)' : active ? 'var(--dsw-alias-state-success-primary,#10b981)' : 'var(--dsw-alias-border-l2,#e5e7eb)'}`,
|
|
937
1085
|
borderRadius: 10,
|
|
938
1086
|
padding: '12px 14px',
|
|
939
|
-
opacity: available ? 1 : 0.
|
|
1087
|
+
opacity: available ? 1 : 0.5,
|
|
940
1088
|
cursor: available ? 'pointer' : 'not-allowed',
|
|
941
|
-
background: selectedPlatform === id ? 'var(--dsw-alias-state-info-bg,#eff6ff)' : active ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' :
|
|
1089
|
+
background: selectedPlatform === id ? 'var(--dsw-alias-state-info-bg,#eff6ff)' : active ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' : 'var(--dsw-alias-bg-layer-2,#f9fafb)',
|
|
1090
|
+
boxShadow: selectedPlatform === id ? '0 0 0 1px var(--dsw-alias-brand-primary,#4f6ef7)' : 'none',
|
|
942
1091
|
transition: 'all 0.15s ease',
|
|
943
1092
|
},
|
|
944
1093
|
onClick: available ? () => setSelectedPlatform(id) : undefined,
|
|
945
1094
|
},
|
|
946
|
-
React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center',
|
|
947
|
-
|
|
1095
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
|
|
1096
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1097
|
+
React.createElement('span', { style: { fontSize: 14 } }, icon),
|
|
1098
|
+
label,
|
|
1099
|
+
),
|
|
948
1100
|
active && React.createElement('span', {
|
|
949
1101
|
style: { width: 6, height: 6, borderRadius: '50%', background: 'var(--dsw-alias-state-success-primary,#10b981)', flexShrink: 0 },
|
|
950
1102
|
}),
|
|
@@ -955,7 +1107,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
955
1107
|
style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary,#9ca3af)', fontWeight: 400 },
|
|
956
1108
|
}, '即将支持'),
|
|
957
1109
|
),
|
|
958
|
-
React.createElement('div', { style: { ...s.muted, marginTop:
|
|
1110
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 4, fontSize: 11 } }, desc),
|
|
959
1111
|
);
|
|
960
1112
|
}),
|
|
961
1113
|
),
|
|
@@ -970,7 +1122,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
970
1122
|
);
|
|
971
1123
|
}
|
|
972
1124
|
|
|
973
|
-
return React.createElement('div', { style: { maxWidth:
|
|
1125
|
+
return React.createElement('div', { style: { maxWidth: 620 } },
|
|
974
1126
|
err && React.createElement('div', {
|
|
975
1127
|
style: { ...s.card, background: 'var(--dsw-alias-state-error-bg,#fef2f2)', color: 'var(--dsw-alias-state-error-primary,#dc2626)', fontSize: 13, marginBottom: 16 },
|
|
976
1128
|
}, err),
|