@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/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-1,transparent)', border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', borderRadius: 12, padding: '16px 20px', marginBottom: 16 },
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-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' },
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-1,transparent)', color: 'var(--dsw-alias-label-primary,currentColor)', outline: 'none', boxSizing: 'border-box' },
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
- ...s.tag,
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
- try { await onSave(serverUrl, accessToken); }
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: { ...s.btnPri, alignSelf: 'flex-start', opacity: (!dirty || saving) ? 0.5 : 1 },
214
- disabled: !dirty || saving,
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 切换到会话 N'),
413
- React.createElement('div', null, '/workspaces — 列出工作区'),
414
- React.createElement('div', null, '/stop停止任务'),
415
- React.createElement('div', null, '/status查看状态'),
416
- React.createElement('div', null, '/yes /no 回应审批'),
417
- React.createElement('div', null, '/help 全部命令'),
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
- React.createElement('div', { style: { fontSize: 12, lineHeight: 1.7 } },
425
- React.createElement('div', null, `状态: ${statusLabel}`),
426
- platform.accountId && React.createElement('div', null, `账号: ${platform.accountId}`),
427
- platform.sessionId && React.createElement('div', null, `当前会话: ${platform.sessionId}`),
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
- '白名单(仅这些用户可驱动 agent):'
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' : '添加允许的用户 ID',
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('input', {
501
- style: { ...s.input, width: '100%' },
502
- placeholder: '请输入 ClientSecret',
503
- value: cfgDraft?.clientSecret ?? '',
504
- onChange: (e) => setCfgDraft(d => ({ ...d, clientSecret: e.target.value })),
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', null,
540
- React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '心跳间隔(秒)— 长任务处理中每隔多久发一次进度提示'),
541
- React.createElement('input', {
542
- style: { ...s.input, width: 120 },
543
- type: 'number', min: 30, max: 3600,
544
- value: cfgDraft.digestIntervalSec,
545
- onChange: (e) => setCfgDraft(d => ({ ...d, digestIntervalSec: e.target.value })),
546
- }),
547
- ),
548
- // 审批超时
549
- React.createElement('div', null,
550
- React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '审批超时(秒)— 工具调用审批无响应后自动拒绝'),
551
- React.createElement('input', {
552
- style: { ...s.input, width: 120 },
553
- type: 'number', min: 30, max: 86400,
554
- value: cfgDraft.approvalTimeoutSec,
555
- onChange: (e) => setCfgDraft(d => ({ ...d, approvalTimeoutSec: e.target.value })),
556
- }),
557
- ),
558
- // 每气泡字数
559
- React.createElement('div', null,
560
- React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '每条消息最大字数 — 超出时自动分多条发送'),
561
- React.createElement('input', {
562
- style: { ...s.input, width: 120 },
563
- type: 'number', min: 100, max: 10000,
564
- value: cfgDraft.maxMessageChars,
565
- onChange: (e) => setCfgDraft(d => ({ ...d, maxMessageChars: e.target.value })),
566
- }),
567
- ),
568
- // 分块延迟
569
- React.createElement('div', null,
570
- React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '分块发送延迟(毫秒)— 多条消息之间的间隔'),
571
- React.createElement('input', {
572
- style: { ...s.input, width: 120 },
573
- type: 'number', min: 0, max: 10000,
574
- value: cfgDraft.sendChunkDelayMs,
575
- onChange: (e) => setCfgDraft(d => ({ ...d, sendChunkDelayMs: e.target.value })),
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: 220 },
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('input', {
592
- style: { ...s.input, width: 220 },
593
- placeholder: '请输入 ClientSecret',
594
- value: cfgDraft.clientSecret,
595
- onChange: (e) => setCfgDraft(d => ({ ...d, clientSecret: e.target.value })),
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('button', {
600
- style: { ...s.btnPri, alignSelf: 'flex-start', opacity: (cfgDirty && !busy) ? 1 : 0.5 },
601
- disabled: !cfgDirty || busy,
602
- onClick: saveConfig,
603
- }, busy ? '保存中…' : '保存设置'),
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: 0, marginBottom: 20,
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: 'iLink Bot API(ClawBot)' },
918
- { id: 'qq', label: 'QQ', desc: 'QQ Bot OpenAPI v2(私聊/群聊/按钮)' },
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 140px',
936
- 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)'}`,
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.45,
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)' : available ? 'var(--dsw-alias-bg-layer-1,transparent)' : 'var(--dsw-alias-bg-layer-2,#f9fafb)',
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', gap: 6 } },
947
- label,
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: 3, fontSize: 11 } }, desc),
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: 560 } },
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),