@wenbin_wb/dsh-bridge 2.2.4 → 2.2.6

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,21 +36,57 @@ 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
  };
53
53
 
54
+ // ---- 官方品牌与 UI SVG 图标 ----
55
+
56
+ const Icons = {
57
+ wechat: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'currentColor', ...props },
58
+ React.createElement('path', { d: 'M8.5 2C4.36 2 1 4.91 1 8.5c0 2.01 1.05 3.81 2.69 4.97l-.69 2.06 2.45-1.22c.94.43 1.98.69 3.05.69.21 0 .42-.01.62-.03-.23-.62-.37-1.28-.37-1.97 0-3.59 3.36-6.5 7.5-6.5.21 0 .41.01.62.03C15.87 4.54 12.44 2 8.5 2zM6 6.5a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5zm5 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5zm7.5 3.5c-3.59 0-6.5 2.46-6.5 5.5 0 1.66.86 3.14 2.21 4.1l-.56 1.69 2.01-1c.78.36 1.64.57 2.54.57 3.59 0 6.5-2.46 6.5-5.5s-2.91-5.5-6.5-5.5zm-2 4a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z' })
59
+ ),
60
+ qq: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'currentColor', ...props },
61
+ React.createElement('path', { d: 'M12 2C7.58 2 4 5.37 4 9.53c0 1.95.78 3.73 2.07 5.07-.37 1.15-.99 2.19-1.8 3.08-.18.2-.04.52.23.52 2.22 0 3.99-1.07 4.9-1.86.8.25 1.66.39 2.6.39 4.42 0 8-3.37 8-7.53S16.42 2 12 2zm-3 8a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5zm6 0a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5z' })
62
+ ),
63
+ feishu: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'currentColor', ...props },
64
+ React.createElement('path', { d: 'M12 2.5L3.5 11.2l6.8 1.8 2.2 6.5 1.8-4.7 5.2-1.4L12 2.5zm-.8 11.1l-4.1-1.1 6.5-6.6-4.2 8.3 1.8-.6z' })
65
+ ),
66
+ telegram: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'currentColor', ...props },
67
+ React.createElement('path', { d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 0 0-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z' })
68
+ ),
69
+ lan: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
70
+ React.createElement('path', { d: 'M12 3c-4.97 0-9 4.03-9 9 0 2.12.74 4.07 1.97 5.61L4.29 19.3a1 1 0 0 0 1.41 1.41l1.7-1.7C9.02 19.64 10.46 20 12 20c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 15c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zm0-9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z' })
71
+ ),
72
+ tunnel: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
73
+ React.createElement('path', { d: 'M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3z' })
74
+ ),
75
+ bot: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
76
+ React.createElement('path', { d: 'M20 9V7c0-1.1-.9-2-2-2h-3c0-1.66-1.34-3-3-3S9 3.34 9 5H6c-1.1 0-2 .9-2 2v2c-1.66 0-3 1.34-3 3s1.34 3 3 3v4c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-4c1.66 0 3-1.34 3-3s-1.34-3-3-3zm-2 10H6V7h12v12zm-9-6c-.83 0-1.5-.67-1.5-1.5S8.17 10 9 10s1.5.67 1.5 1.5S9.83 13 9 13zm6 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z' })
77
+ ),
78
+ github: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 13, height: 13, fill: 'currentColor', ...props },
79
+ React.createElement('path', { d: 'M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z' })
80
+ ),
81
+ refresh: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 12, height: 12, fill: 'none', stroke: 'currentColor', strokeWidth: 2.2, strokeLinecap: 'round', strokeLinejoin: 'round', ...props },
82
+ React.createElement('path', { d: 'M23 4v6h-6M1 20v-6h6' }),
83
+ React.createElement('path', { d: 'M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15' })
84
+ ),
85
+ check: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 12, height: 12, fill: 'none', stroke: 'currentColor', strokeWidth: 2.5, strokeLinecap: 'round', strokeLinejoin: 'round', ...props },
86
+ React.createElement('polyline', { points: '20 6 9 17 4 12' })
87
+ ),
88
+ };
89
+
54
90
  // ---- 子组件 ----
55
91
 
56
92
  // 通用复制 hook:返回 [copied, copy],copy(text) 复制文本并短暂显示成功状态
@@ -82,14 +118,40 @@ function useCopy() {
82
118
  return [copied, copy];
83
119
  }
84
120
 
85
- function StatusTag({ running }) {
121
+ function StatusTag({ running, status }) {
122
+ let bg = 'var(--dsw-alias-bg-layer-2,#f3f4f6)';
123
+ let color = 'var(--dsw-alias-label-secondary,#6b7280)';
124
+ let text = running ? '运行中' : '未启动';
125
+
126
+ if (status === 'connected') {
127
+ bg = 'var(--dsw-alias-state-success-bg,#ecfdf5)';
128
+ color = 'var(--dsw-alias-state-success-primary,#059669)';
129
+ text = '已连接';
130
+ } else if (status === 'starting') {
131
+ bg = 'var(--dsw-alias-state-info-bg,#eff6ff)';
132
+ color = 'var(--dsw-alias-state-info-primary,#3b82f6)';
133
+ text = '连接中…';
134
+ } else if (status === 'reconnecting') {
135
+ bg = 'var(--dsw-alias-state-warn-bg,#fffbeb)';
136
+ color = 'var(--dsw-alias-state-warn-primary,#d97706)';
137
+ text = '重连中…';
138
+ } else if (status === 'paused') {
139
+ bg = 'var(--dsw-alias-state-warn-bg,#fffbeb)';
140
+ color = 'var(--dsw-alias-state-warn-primary,#d97706)';
141
+ text = '暂停中';
142
+ } else if (status === 'error') {
143
+ bg = 'var(--dsw-alias-state-error-bg,#fef2f2)';
144
+ color = 'var(--dsw-alias-state-error-primary,#dc2626)';
145
+ text = '异常';
146
+ } else if (running) {
147
+ bg = 'var(--dsw-alias-state-success-bg,#ecfdf5)';
148
+ color = 'var(--dsw-alias-state-success-primary,#059669)';
149
+ text = '运行中';
150
+ }
151
+
86
152
  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 ? '运行中' : '未启动');
153
+ style: { ...s.tag, background: bg, color },
154
+ }, text);
93
155
  }
94
156
 
95
157
  function QrBlock({ url, qr, onReset }) {
@@ -172,6 +234,7 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
172
234
  const [serverUrl, setServerUrl] = React.useState(initUrl ?? '');
173
235
  const [accessToken, setAccessToken] = React.useState(initToken ?? '');
174
236
  const [saving, setSaving] = React.useState(false);
237
+ const [saveSuccess, setSaveSuccess] = React.useState(false);
175
238
 
176
239
  const syncedRef = React.useRef(false);
177
240
  React.useEffect(() => {
@@ -185,11 +248,16 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
185
248
  const dirty = serverUrl !== (initUrl ?? '') || accessToken !== (initToken ?? '');
186
249
  const handleSave = React.useCallback(async () => {
187
250
  setSaving(true);
188
- try { await onSave(serverUrl, accessToken); }
251
+ setSaveSuccess(false);
252
+ try {
253
+ await onSave(serverUrl, accessToken);
254
+ setSaveSuccess(true);
255
+ setTimeout(() => setSaveSuccess(false), 2500);
256
+ }
189
257
  finally { setSaving(false); }
190
258
  }, [onSave, serverUrl, accessToken]);
191
- const handleUrlChange = React.useCallback((e) => setServerUrl(e.target.value), []);
192
- const handleTokenChange = React.useCallback((e) => setAccessToken(e.target.value), []);
259
+ const handleUrlChange = React.useCallback((e) => { setServerUrl(e.target.value); setSaveSuccess(false); }, []);
260
+ const handleTokenChange = React.useCallback((e) => { setAccessToken(e.target.value); setSaveSuccess(false); }, []);
193
261
 
194
262
  return React.createElement('div', { style: s.block },
195
263
  React.createElement('div', { style: { ...s.muted, marginBottom: 8 } }, '服务器配置'),
@@ -199,6 +267,7 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
199
267
  placeholder: 'WebSocket 地址,例如 wss://tunnel.example.com/connect',
200
268
  value: serverUrl,
201
269
  onChange: handleUrlChange,
270
+ onKeyDown: (e) => { if (e.key === 'Enter' && dirty && !saving) handleSave(); },
202
271
  disabled: saving,
203
272
  }),
204
273
  React.createElement('input', {
@@ -207,13 +276,19 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
207
276
  placeholder: '访问令牌(Access Token)',
208
277
  value: accessToken,
209
278
  onChange: handleTokenChange,
279
+ onKeyDown: (e) => { if (e.key === 'Enter' && dirty && !saving) handleSave(); },
210
280
  disabled: saving,
211
281
  }),
212
282
  React.createElement('button', {
213
- style: { ...s.btnPri, alignSelf: 'flex-start', opacity: (!dirty || saving) ? 0.5 : 1 },
214
- disabled: !dirty || saving,
283
+ style: {
284
+ ...s.btnPri,
285
+ alignSelf: 'flex-start',
286
+ opacity: (!dirty || saving) ? (saveSuccess ? 1 : 0.5) : 1,
287
+ background: saveSuccess ? 'var(--dsw-alias-state-success-primary,#059669)' : undefined,
288
+ },
289
+ disabled: (!dirty && !saveSuccess) || saving,
215
290
  onClick: handleSave,
216
- }, saving ? '保存中…' : '保存配置'),
291
+ }, saving ? '保存中…' : saveSuccess ? '✓ 已保存' : '保存配置'),
217
292
  ),
218
293
  );
219
294
  });
@@ -338,6 +413,20 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
338
413
  }, [act, platform?.allowFrom]);
339
414
  const handleNewId = React.useCallback((e) => setNewId(e.target.value), []);
340
415
 
416
+ // 密码明文/密文切换
417
+ const [showSecret, setShowSecret] = React.useState(false);
418
+
419
+ // 恢复推荐默认值
420
+ const resetDefaults = React.useCallback(() => {
421
+ setCfgDraft((d) => ({
422
+ ...d,
423
+ digestIntervalSec: '300',
424
+ approvalTimeoutSec: '600',
425
+ maxMessageChars: '2000',
426
+ sendChunkDelayMs: '1500',
427
+ }));
428
+ }, []);
429
+
341
430
  // 高级设置保存
342
431
  const saveConfig = React.useCallback(async () => {
343
432
  if (!cfgDraft) return;
@@ -385,19 +474,35 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
385
474
  return React.createElement('div', { style: s.card },
386
475
  React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
387
476
  React.createElement('div', null,
388
- React.createElement('div', { style: s.label }, platformName),
477
+ React.createElement('div', { style: { ...s.label, display: 'flex', alignItems: 'center', gap: 7 } },
478
+ platformId === 'wechat' && React.createElement(Icons.wechat, { style: { color: '#07C160', width: 20, height: 20 } }),
479
+ platformId === 'qq' && React.createElement(Icons.qq, { style: { color: '#12B7F5', width: 20, height: 20 } }),
480
+ platformId === 'feishu' && React.createElement(Icons.feishu, { style: { color: '#00D6B9', width: 20, height: 20 } }),
481
+ platformId === 'telegram' && React.createElement(Icons.telegram, { style: { color: '#24A1DE', width: 20, height: 20 } }),
482
+ platformName,
483
+ ),
389
484
  React.createElement('div', { style: { ...s.muted, marginTop: 2 } }, platformDesc),
390
485
  ),
391
- React.createElement(StatusTag, { running: connected }),
486
+ React.createElement(StatusTag, { status: platform?.status, running: connected }),
392
487
  ),
393
488
 
394
- // 快捷入口:使用说明 / 命令
489
+ // 快捷入口:使用说明 / 开放平台 / 命令速查
395
490
  React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap', alignItems: 'center' } },
396
491
  platformId === 'wechat' && React.createElement('a', {
397
492
  href: 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/wechat-usage.md',
398
493
  target: '_blank', rel: 'noopener noreferrer',
399
494
  style: s.btnGhost,
400
- }, '📖 使用说明'),
495
+ }, '📖 微信使用说明'),
496
+ platformId === 'qq' && React.createElement('a', {
497
+ href: 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/qq-usage.md',
498
+ target: '_blank', rel: 'noopener noreferrer',
499
+ style: s.btnGhost,
500
+ }, '📖 QQ 使用说明'),
501
+ platformId === 'qq' && React.createElement('a', {
502
+ href: 'https://bot.q.qq.com/wiki/develop/api-v2/',
503
+ target: '_blank', rel: 'noopener noreferrer',
504
+ style: s.btnGhost,
505
+ }, '🌐 QQ 开放平台'),
401
506
  React.createElement('button', {
402
507
  style: s.btnGhost,
403
508
  onClick: () => setShowHelp(v => !v),
@@ -406,28 +511,75 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
406
511
 
407
512
  // 命令速查
408
513
  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 全部命令'),
514
+ React.createElement('div', null, '/new <提示词> — 新建会话并开始(当前工作区)'),
515
+ React.createElement('div', null, '/new <提示词> @N — 在指定工作区新建会话'),
516
+ React.createElement('div', null, '/sessions(或 /list)— 列出会话(按工作区分组,带标题)'),
517
+ React.createElement('div', null, '/use N(或 /resume N)— 切换到会话 N'),
518
+ React.createElement('div', null, '/workspaces — 列出所有可用工作区'),
519
+ React.createElement('div', null, '/end结束当前会话(回到无活动会话状态)'),
520
+ React.createElement('div', null, '/stop停止当前任务'),
521
+ React.createElement('div', null, '/status 查看 Agent 状态与会话摘要'),
522
+ React.createElement('div', null, '/yes /no(或 1/2)— 回应权限审批请求'),
523
+ React.createElement('div', null, '/help — 显示完整命令帮助'),
418
524
  ),
419
525
 
420
526
  err && React.createElement('div', { style: { ...s.warn, marginTop: 10 } }, err),
421
527
 
422
- // 已配置:状态详情 + 白名单
528
+ // 已配置:结构化状态看板 + 白名单
423
529
  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}`),
530
+ // 结构化状态卡片看板
531
+ React.createElement('div', {
532
+ style: {
533
+ display: 'grid',
534
+ gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))',
535
+ gap: 8,
536
+ marginBottom: 12,
537
+ },
538
+ },
539
+ React.createElement('div', {
540
+ style: {
541
+ background: 'var(--dsw-alias-bg-layer-1,#fff)',
542
+ border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
543
+ borderRadius: 8,
544
+ padding: '8px 12px',
545
+ },
546
+ },
547
+ React.createElement('div', { style: { ...s.muted, fontSize: 11 } }, '连接状态'),
548
+ React.createElement('div', { style: { ...s.label, fontSize: 13, marginTop: 2, display: 'flex', alignItems: 'center', gap: 6 } },
549
+ React.createElement('span', {
550
+ style: {
551
+ width: 6, height: 6, borderRadius: '50%',
552
+ background: connected ? 'var(--dsw-alias-state-success-primary,#10b981)' : 'var(--dsw-alias-label-tertiary,#9ca3af)',
553
+ },
554
+ }),
555
+ statusLabel,
556
+ ),
557
+ ),
558
+ platform.accountId && React.createElement('div', {
559
+ style: {
560
+ background: 'var(--dsw-alias-bg-layer-1,#fff)',
561
+ border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
562
+ borderRadius: 8,
563
+ padding: '8px 12px',
564
+ },
565
+ },
566
+ React.createElement('div', { style: { ...s.muted, fontSize: 11 } }, '登录账号'),
567
+ React.createElement('div', { style: { ...s.code, fontSize: 12, marginTop: 2, fontWeight: 500 } }, platform.accountId),
568
+ ),
569
+ platform.sessionId && React.createElement('div', {
570
+ style: {
571
+ background: 'var(--dsw-alias-bg-layer-1,#fff)',
572
+ border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
573
+ borderRadius: 8,
574
+ padding: '8px 12px',
575
+ },
576
+ },
577
+ React.createElement('div', { style: { ...s.muted, fontSize: 11 } }, '活动会话'),
578
+ React.createElement('div', { style: { ...s.code, fontSize: 12, marginTop: 2 } }, platform.sessionId),
579
+ ),
428
580
  ),
429
581
  React.createElement('div', { style: { ...s.muted, fontSize: 12, marginTop: 8, lineHeight: 1.6 } },
430
- '白名单(仅这些用户可驱动 agent):'
582
+ `白名单 (已授权 ${platform.allowFrom?.length || 0} 个账号/群):`
431
583
  ),
432
584
  React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 6 } },
433
585
  (platform.allowFrom?.length
@@ -449,9 +601,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
449
601
  React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 8, alignItems: 'center' } },
450
602
  React.createElement('input', {
451
603
  style: { ...s.input, flex: 1 },
452
- placeholder: platformId === 'wechat' ? '添加允许的微信 ID(如 xxx@im.wechat' : '添加允许的用户 ID',
604
+ placeholder: platformId === 'wechat' ? '添加允许的微信 ID(如 xxx@im.wechat),按 Enter 添加' : '添加允许的用户/群 ID,按 Enter 添加',
453
605
  value: newId,
454
606
  onChange: handleNewId,
607
+ onKeyDown: (e) => { if (e.key === 'Enter' && newId.trim() && !busy) addAllow(); },
455
608
  }),
456
609
  React.createElement('button', {
457
610
  style: { ...s.btnGhost, whiteSpace: 'nowrap', opacity: (newId.trim() && !busy) ? 1 : 0.5 },
@@ -493,16 +646,27 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
493
646
  placeholder: '请输入 AppID',
494
647
  value: cfgDraft?.appId ?? '',
495
648
  onChange: (e) => setCfgDraft(d => ({ ...d, appId: e.target.value })),
649
+ onKeyDown: (e) => { if (e.key === 'Enter' && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig(); },
496
650
  }),
497
651
  ),
498
652
  React.createElement('div', null,
499
653
  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
- }),
654
+ React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
655
+ React.createElement('input', {
656
+ style: { ...s.input, flex: 1 },
657
+ type: showSecret ? 'text' : 'password',
658
+ placeholder: '请输入 ClientSecret',
659
+ value: cfgDraft?.clientSecret ?? '',
660
+ onChange: (e) => setCfgDraft(d => ({ ...d, clientSecret: e.target.value })),
661
+ onKeyDown: (e) => { if (e.key === 'Enter' && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig(); },
662
+ }),
663
+ React.createElement('button', {
664
+ style: { ...s.btnGhost, height: 32, padding: '0 10px', fontSize: 13, flexShrink: 0 },
665
+ onClick: () => setShowSecret(v => !v),
666
+ type: 'button',
667
+ title: showSecret ? '隐藏密钥' : '显示明文',
668
+ }, showSecret ? '🙈 隐藏' : '👁️ 显示'),
669
+ ),
506
670
  ),
507
671
  React.createElement('div', null,
508
672
  React.createElement('a', {
@@ -528,79 +692,104 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
528
692
  ),
529
693
  ),
530
694
 
531
- // 高级设置(可折叠)
695
+ // 高级设置(紧凑 2x2 网格 + 恢复默认值)
532
696
  cfgDraft && React.createElement('div', { style: s.block },
533
697
  React.createElement('button', {
534
698
  style: { ...s.btnLink, fontSize: 12, marginBottom: showAdvanced ? 10 : 0 },
535
699
  onClick: () => setShowAdvanced(v => !v),
536
700
  }, showAdvanced ? '▾ 高级设置' : '▸ 高级设置'),
537
701
  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
- }),
702
+ // 2x2 参数网格
703
+ React.createElement('div', {
704
+ style: {
705
+ display: 'grid',
706
+ gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
707
+ gap: 10,
708
+ },
709
+ },
710
+ // 心跳间隔
711
+ React.createElement('div', null,
712
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '心跳进度间隔 (秒)'),
713
+ React.createElement('input', {
714
+ style: { ...s.input, width: '100%' },
715
+ type: 'number', min: 30, max: 3600,
716
+ value: cfgDraft.digestIntervalSec,
717
+ onChange: (e) => setCfgDraft(d => ({ ...d, digestIntervalSec: e.target.value })),
718
+ }),
719
+ ),
720
+ // 审批超时
721
+ React.createElement('div', null,
722
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '审批超时自动拒绝 (秒)'),
723
+ React.createElement('input', {
724
+ style: { ...s.input, width: '100%' },
725
+ type: 'number', min: 30, max: 86400,
726
+ value: cfgDraft.approvalTimeoutSec,
727
+ onChange: (e) => setCfgDraft(d => ({ ...d, approvalTimeoutSec: e.target.value })),
728
+ }),
729
+ ),
730
+ // 每气泡字数
731
+ React.createElement('div', null,
732
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '单条最大字数限制 (字)'),
733
+ React.createElement('input', {
734
+ style: { ...s.input, width: '100%' },
735
+ type: 'number', min: 100, max: 10000,
736
+ value: cfgDraft.maxMessageChars,
737
+ onChange: (e) => setCfgDraft(d => ({ ...d, maxMessageChars: e.target.value })),
738
+ }),
739
+ ),
740
+ // 分块延迟
741
+ React.createElement('div', null,
742
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '分块发送延迟 (毫秒)'),
743
+ React.createElement('input', {
744
+ style: { ...s.input, width: '100%' },
745
+ type: 'number', min: 0, max: 10000,
746
+ value: cfgDraft.sendChunkDelayMs,
747
+ onChange: (e) => setCfgDraft(d => ({ ...d, sendChunkDelayMs: e.target.value })),
748
+ }),
749
+ ),
577
750
  ),
578
751
  // QQ 平台凭证
579
- platformId === 'qq' && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10 } },
752
+ platformId === 'qq' && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10, marginTop: 4 } },
580
753
  React.createElement('div', null,
581
754
  React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'AppID — QQ 开放平台机器人应用 ID'),
582
755
  React.createElement('input', {
583
- style: { ...s.input, width: 220 },
756
+ style: { ...s.input, width: '100%' },
584
757
  placeholder: '请输入 AppID',
585
758
  value: cfgDraft.appId,
586
759
  onChange: (e) => setCfgDraft(d => ({ ...d, appId: e.target.value })),
587
760
  }),
588
761
  ),
589
762
  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
- }),
763
+ React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'ClientSecret — QQ 开放平台机器人密钥(留空则保持已保存密钥)'),
764
+ React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
765
+ React.createElement('input', {
766
+ style: { ...s.input, flex: 1 },
767
+ type: showSecret ? 'text' : 'password',
768
+ placeholder: '留空保持已保存密钥',
769
+ value: cfgDraft.clientSecret,
770
+ onChange: (e) => setCfgDraft(d => ({ ...d, clientSecret: e.target.value })),
771
+ }),
772
+ React.createElement('button', {
773
+ style: { ...s.btnGhost, height: 32, padding: '0 10px', fontSize: 13, flexShrink: 0 },
774
+ onClick: () => setShowSecret(v => !v),
775
+ type: 'button',
776
+ title: showSecret ? '隐藏密钥' : '显示明文',
777
+ }, showSecret ? '🙈 隐藏' : '👁️ 显示'),
778
+ ),
597
779
  ),
598
780
  ),
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 ? '保存中…' : '保存设置'),
781
+ React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', marginTop: 4 } },
782
+ React.createElement('button', {
783
+ style: { ...s.btnPri, opacity: (cfgDirty && !busy) ? 1 : 0.5 },
784
+ disabled: !cfgDirty || busy,
785
+ onClick: saveConfig,
786
+ }, busy ? '保存中…' : '保存设置'),
787
+ React.createElement('button', {
788
+ style: { ...s.btnGhost, height: 32, fontSize: 12 },
789
+ onClick: resetDefaults,
790
+ title: '恢复推荐默认配置',
791
+ }, '↺ 恢复推荐默认'),
792
+ ),
604
793
  ),
605
794
  ),
606
795
 
@@ -628,6 +817,10 @@ function UpgradeCommandRow({ cmd }) {
628
817
  flex: 1,
629
818
  minWidth: 0,
630
819
  wordBreak: 'break-all',
820
+ background: 'var(--dsw-alias-bg-layer-1,#ffffff)',
821
+ padding: '4px 8px',
822
+ borderRadius: 6,
823
+ border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
631
824
  },
632
825
  }, cmd),
633
826
  React.createElement('button', {
@@ -638,10 +831,13 @@ function UpgradeCommandRow({ cmd }) {
638
831
  );
639
832
  }
640
833
 
641
- // 版本检查 + GitHub/反馈入口
834
+ // 版本检查 + 一键升级 + GitHub/反馈入口
642
835
  function VersionBanner({ rpcCall }) {
643
836
  const [info, setInfo] = React.useState(null);
644
837
  const [loading, setLoading] = React.useState(false);
838
+ const [upgrading, setUpgrading] = React.useState(false);
839
+ const [upgradeResult, setUpgradeResult] = React.useState(null);
840
+ const [showManual, setShowManual] = React.useState(false);
645
841
 
646
842
  const check = React.useCallback(async () => {
647
843
  setLoading(true);
@@ -655,102 +851,205 @@ function VersionBanner({ rpcCall }) {
655
851
 
656
852
  React.useEffect(() => { check(); }, [check]);
657
853
 
658
- // 只有 npm 上的版本严格大于当前版本才算有新版本
659
854
  const hasUpdate = info?.latest && info?.current && !info.error && semverGt(info.latest, info.current);
855
+ const isLatest = info?.latest && info?.current && !info.error && !semverGt(info.latest, info.current);
856
+
857
+ const handleUpgrade = React.useCallback(async () => {
858
+ if (!info?.latest || upgrading) return;
859
+ setUpgrading(true);
860
+ setUpgradeResult(null);
861
+ try {
862
+ const r = await rpcCall(BRIDGE_ENDPOINTS.upgradePlugin, { version: info.latest });
863
+ if (r?.ok && r.value?.ok) {
864
+ setUpgradeResult({ ok: true, message: `已成功升级到 v${info.latest}!请重启 DSH 服务使新版本生效。` });
865
+ setTimeout(() => check(), 3000);
866
+ } else {
867
+ setUpgradeResult({ ok: false, message: r?.value?.error || r?.error?.message || '升级失败' });
868
+ setShowManual(true);
869
+ }
870
+ } catch (e) {
871
+ setUpgradeResult({ ok: false, message: e.message || '升级请求失败' });
872
+ setShowManual(true);
873
+ } finally {
874
+ setUpgrading(false);
875
+ }
876
+ }, [info?.latest, upgrading, rpcCall, check]);
660
877
 
661
- // 链接按钮组:GitHub + 更新日志 + 反馈 Bug
662
878
  const links = React.createElement('div', { style: { display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' } },
663
879
  React.createElement('a', {
664
880
  href: GITHUB_URL, target: '_blank', rel: 'noreferrer', style: s.btnLink,
665
- }, 'GitHub'),
881
+ }, React.createElement(Icons.github), 'GitHub'),
666
882
  React.createElement('a', {
667
883
  href: RELEASES_URL, target: '_blank', rel: 'noreferrer', style: s.btnLink,
668
- }, '📋 更新日志'),
884
+ }, '更新日志'),
669
885
  React.createElement('a', {
670
886
  href: ISSUES_URL, target: '_blank', rel: 'noreferrer', style: s.btnLink,
671
- }, '🐛 反馈 Bug'),
887
+ }, '反馈 Issue'),
672
888
  );
673
889
 
674
- // 有新版本:信息卡片
675
- if (hasUpdate) {
676
- const cmds = upgradeCommands(info.latest);
677
- return React.createElement('div', { style: { marginBottom: 16 } },
678
- React.createElement('div', {
679
- style: {
680
- ...s.card,
681
- background: 'var(--dsw-alias-state-info-bg,#eff6ff)',
682
- border: '1px solid var(--dsw-alias-state-info-border,#bfdbfe)',
683
- padding: '12px 16px',
684
- marginBottom: 8,
890
+ return React.createElement('div', { style: { marginBottom: 16 } },
891
+ // 顶部状态行:版本徽标 + 刷新按钮 + 链接组
892
+ React.createElement('div', {
893
+ style: {
894
+ display: 'flex',
895
+ alignItems: 'center',
896
+ justifyContent: 'space-between',
897
+ flexWrap: 'wrap',
898
+ gap: 8,
899
+ },
900
+ },
901
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
902
+ // 版本状态徽标
903
+ React.createElement('span', {
904
+ style: {
905
+ ...s.tag,
906
+ background: hasUpdate
907
+ ? 'var(--dsw-alias-state-info-bg,#eff6ff)'
908
+ : isLatest
909
+ ? 'var(--dsw-alias-state-success-bg,#ecfdf5)'
910
+ : 'var(--dsw-alias-bg-layer-2,#f3f4f6)',
911
+ color: hasUpdate
912
+ ? 'var(--dsw-alias-state-info-primary,#2563eb)'
913
+ : isLatest
914
+ ? 'var(--dsw-alias-state-success-primary,#059669)'
915
+ : 'var(--dsw-alias-label-secondary,#6b7280)',
916
+ padding: '3px 10px',
917
+ fontSize: 12,
918
+ fontWeight: 500,
919
+ display: 'inline-flex',
920
+ alignItems: 'center',
921
+ gap: 5,
922
+ },
685
923
  },
924
+ loading
925
+ ? React.createElement('span', {
926
+ style: { display: 'inline-flex', alignItems: 'center' },
927
+ }, React.createElement(Icons.refresh))
928
+ : isLatest
929
+ ? React.createElement(Icons.check)
930
+ : null,
931
+ info ? `v${info.current}` : '版本检查中…',
932
+ isLatest && React.createElement('span', { style: { opacity: 0.85, fontSize: 11, fontWeight: 400 } }, '· 已是最新'),
933
+ hasUpdate && React.createElement('span', { style: { fontWeight: 600, fontSize: 11 } }, `➔ v${info.latest}`),
934
+ info?.error && React.createElement('span', { style: { color: 'var(--dsw-alias-state-warn-primary,#d97706)', fontSize: 11 } }, '(网络超时)'),
935
+ ),
936
+ // 刷新检查按钮
937
+ React.createElement('button', {
938
+ style: {
939
+ ...s.btnGhost,
940
+ height: 24,
941
+ padding: '0 8px',
942
+ fontSize: 11,
943
+ opacity: loading ? 0.5 : 1,
944
+ gap: 4,
945
+ },
946
+ onClick: check,
947
+ disabled: loading || upgrading,
948
+ title: '重新检查 npm 线上版本',
949
+ },
950
+ React.createElement(Icons.refresh),
951
+ loading ? '检查中…' : '检查更新',
952
+ ),
953
+ ),
954
+ links,
955
+ ),
956
+
957
+ // 发现新版本高亮卡片(支持一键直接升级)
958
+ hasUpdate && React.createElement('div', {
959
+ style: {
960
+ ...s.card,
961
+ background: 'var(--dsw-alias-state-info-bg,#eff6ff)',
962
+ border: '1px solid var(--dsw-alias-state-info-border,#bfdbfe)',
963
+ padding: '14px 16px',
964
+ marginTop: 10,
965
+ marginBottom: 0,
686
966
  },
687
- React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12 } },
688
- React.createElement('span', { style: { fontSize: 20 } }, '🎉'),
689
- React.createElement('div', { style: { flex: 1, minWidth: 0 } },
967
+ },
968
+ React.createElement('div', { style: { display: 'flex', alignItems: 'flex-start', gap: 12 } },
969
+ React.createElement('span', { style: { fontSize: 22 } }, '🚀'),
970
+ React.createElement('div', { style: { flex: 1, minWidth: 0 } },
971
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8, marginBottom: 8 } },
690
972
  React.createElement('div', {
691
973
  style: {
692
974
  fontSize: 13,
693
975
  fontWeight: 600,
694
976
  color: 'var(--dsw-alias-state-info-primary,#1e40af)',
695
- marginBottom: 8,
696
977
  },
697
978
  }, `发现新版本 v${info.latest}(当前 v${info.current})`),
698
- // 升级命令:dsh plugin 与 npx 两种,均带复制按钮
699
- React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
700
- cmds.map(({ id, cmd }) => React.createElement(UpgradeCommandRow, { key: id, cmd })),
979
+ React.createElement('button', {
980
+ style: {
981
+ ...s.btnPri,
982
+ height: 28,
983
+ fontSize: 12,
984
+ padding: '0 14px',
985
+ background: upgradeResult?.ok
986
+ ? 'var(--dsw-alias-state-success-primary,#059669)'
987
+ : 'var(--dsw-alias-brand-primary,#4f6ef7)',
988
+ opacity: upgrading ? 0.6 : 1,
989
+ },
990
+ onClick: handleUpgrade,
991
+ disabled: upgrading || upgradeResult?.ok,
992
+ },
993
+ upgrading
994
+ ? React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6 } },
995
+ React.createElement('span', { style: { animation: 'spin 1s linear infinite', display: 'inline-flex' } }, React.createElement(Icons.refresh)),
996
+ '正在自动升级…',
997
+ )
998
+ : upgradeResult?.ok
999
+ ? '✓ 已完成升级'
1000
+ : `一键升级到 v${info.latest}`
701
1001
  ),
702
1002
  ),
703
- React.createElement('button', {
1003
+
1004
+ upgradeResult && React.createElement('div', {
704
1005
  style: {
705
- ...s.btnGhost,
706
- height: 30,
707
- padding: '0 12px',
1006
+ background: upgradeResult.ok ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' : 'var(--dsw-alias-state-error-bg,#fef2f2)',
1007
+ border: `1px solid ${upgradeResult.ok ? 'var(--dsw-alias-state-success-border,#a7f3d0)' : 'var(--dsw-alias-state-error-border,#fecaca)'}`,
1008
+ color: upgradeResult.ok ? 'var(--dsw-alias-state-success-primary,#065f46)' : 'var(--dsw-alias-state-error-primary,#991b1b)',
1009
+ padding: '8px 12px',
1010
+ borderRadius: 6,
708
1011
  fontSize: 12,
709
- opacity: loading ? 0.5 : 1,
710
- flexShrink: 0,
1012
+ marginBottom: 8,
1013
+ lineHeight: 1.5,
711
1014
  },
712
- onClick: check,
713
- disabled: loading,
714
- }, loading ? '…' : '刷新'),
1015
+ }, upgradeResult.message),
1016
+
1017
+ React.createElement('div', { style: { marginTop: 4 } },
1018
+ React.createElement('button', {
1019
+ style: { ...s.btnLink, fontSize: 11, color: 'var(--dsw-alias-label-secondary,#6b7280)' },
1020
+ onClick: () => setShowManual(v => !v),
1021
+ }, showManual ? '▴ 折叠手动命令行' : '▾ 查看手动升级命令 (如需)'),
1022
+ ),
1023
+
1024
+ showManual && React.createElement('div', {
1025
+ style: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 },
1026
+ },
1027
+ upgradeCommands(info.latest).map(({ id, cmd }) =>
1028
+ React.createElement(UpgradeCommandRow, { key: id, cmd })
1029
+ ),
1030
+ ),
715
1031
  ),
716
1032
  ),
717
- links,
718
- );
719
- }
720
-
721
- // 正常状态:版本信息一行 + 链接
722
- return React.createElement('div', { style: { marginBottom: 14, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8 } },
723
- React.createElement('div', { style: { ...s.muted, display: 'flex', alignItems: 'center', gap: 8 } },
724
- loading && !info
725
- ? React.createElement('span', null, '检查更新中…')
726
- : info
727
- ? React.createElement('span', null, `v${info.current}${info.latest && !info.error ? ' · 已是最新' : info.error ? ' · 检查失败' : ''}`)
728
- : null,
729
- info && React.createElement('button', {
730
- style: { ...s.btnGhost, height: 22, padding: '0 8px', fontSize: 11, opacity: loading ? 0.5 : 1 },
731
- onClick: check, disabled: loading,
732
- }, loading ? '…' : '重新检查'),
733
1033
  ),
734
- links,
735
1034
  );
736
1035
  }
737
1036
 
738
1037
  // ---- Tab Bar ----
739
1038
 
740
1039
  const TABS = [
741
- { id: 'lan', label: '局域网' },
742
- { id: 'tunnel', label: '公网隧道' },
743
- { id: 'im', label: 'IM 机器人' },
1040
+ { id: 'lan', label: '局域网', icon: Icons.lan },
1041
+ { id: 'tunnel', label: '公网隧道', icon: Icons.tunnel },
1042
+ { id: 'im', label: 'IM 机器人', icon: Icons.bot },
744
1043
  ];
745
1044
 
746
1045
  function TabBar({ active, onChange, dots }) {
747
1046
  return React.createElement('div', {
748
1047
  style: {
749
- display: 'flex', gap: 0, marginBottom: 20,
1048
+ display: 'flex', gap: 4, marginBottom: 20,
750
1049
  borderBottom: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
751
1050
  },
752
1051
  },
753
- TABS.map(({ id, label }) => {
1052
+ TABS.map(({ id, label, icon: TabIcon }) => {
754
1053
  const isActive = active === id;
755
1054
  const hasDot = dots?.[id];
756
1055
  return React.createElement('button', {
@@ -766,11 +1065,17 @@ function TabBar({ active, onChange, dots }) {
766
1065
  ? '2px solid var(--dsw-alias-brand-primary,#4f6ef7)'
767
1066
  : '2px solid transparent',
768
1067
  marginBottom: -1,
769
- display: 'inline-flex', alignItems: 'center', gap: 6,
1068
+ display: 'inline-flex', alignItems: 'center', gap: 7,
770
1069
  transition: 'color .15s, border-color .15s',
771
1070
  whiteSpace: 'nowrap',
772
1071
  },
773
1072
  },
1073
+ TabIcon && React.createElement(TabIcon, {
1074
+ style: {
1075
+ color: isActive ? 'var(--dsw-alias-brand-primary,#4f6ef7)' : 'var(--dsw-alias-label-tertiary,#9ca3af)',
1076
+ width: 16, height: 16, flexShrink: 0,
1077
+ },
1078
+ }),
774
1079
  label,
775
1080
  hasDot && React.createElement('span', {
776
1081
  style: {
@@ -914,9 +1219,10 @@ function BridgePanel({ rpcCall }) {
914
1219
  } else if (activeTab === 'im') {
915
1220
  // 从 listPlatforms 动态生成平台列表
916
1221
  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' },
1222
+ { id: 'wechat', label: '微信', icon: Icons.wechat, brandColor: '#07C160', desc: 'ClawBot 扫码直连 · 无需公网' },
1223
+ { id: 'qq', label: 'QQ', icon: Icons.qq, brandColor: '#12B7F5', desc: '官方机器人 · 私聊/群聊/按钮' },
1224
+ { id: 'feishu', label: '飞书', icon: Icons.feishu, brandColor: '#00D6B9', desc: '官方事件回调 API' },
1225
+ { id: 'telegram', label: 'Telegram', icon: Icons.telegram, brandColor: '#24A1DE', desc: '官方 Bot API' },
920
1226
  ];
921
1227
 
922
1228
  tabContent = React.createElement('div', null,
@@ -924,7 +1230,7 @@ function BridgePanel({ rpcCall }) {
924
1230
  React.createElement('div', {
925
1231
  style: { display: 'flex', gap: 10, marginBottom: 16, flexWrap: 'wrap' },
926
1232
  },
927
- IM_PLATFORMS.map(({ id, label, desc }) => {
1233
+ IM_PLATFORMS.map(({ id, label, icon: IconComponent, brandColor, desc }) => {
928
1234
  const platformData = platforms?.[id];
929
1235
  const available = !!platformData;
930
1236
  const active = platformData?.status === 'connected' || platformData?.status === 'starting' || platformData?.status === 'reconnecting';
@@ -932,19 +1238,23 @@ function BridgePanel({ rpcCall }) {
932
1238
  return React.createElement('div', {
933
1239
  key: id,
934
1240
  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)'}`,
1241
+ flex: '1 1 135px',
1242
+ 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
1243
  borderRadius: 10,
938
1244
  padding: '12px 14px',
939
- opacity: available ? 1 : 0.45,
1245
+ opacity: available ? 1 : 0.5,
940
1246
  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)',
1247
+ 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)',
1248
+ boxShadow: selectedPlatform === id ? '0 0 0 1px var(--dsw-alias-brand-primary,#4f6ef7)' : 'none',
942
1249
  transition: 'all 0.15s ease',
943
1250
  },
944
1251
  onClick: available ? () => setSelectedPlatform(id) : undefined,
945
1252
  },
946
- React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', gap: 6 } },
947
- label,
1253
+ React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
1254
+ React.createElement('span', { style: { display: 'flex', alignItems: 'center', gap: 7 } },
1255
+ IconComponent && React.createElement(IconComponent, { style: { color: brandColor, width: 18, height: 18, flexShrink: 0 } }),
1256
+ label,
1257
+ ),
948
1258
  active && React.createElement('span', {
949
1259
  style: { width: 6, height: 6, borderRadius: '50%', background: 'var(--dsw-alias-state-success-primary,#10b981)', flexShrink: 0 },
950
1260
  }),
@@ -955,7 +1265,7 @@ function BridgePanel({ rpcCall }) {
955
1265
  style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary,#9ca3af)', fontWeight: 400 },
956
1266
  }, '即将支持'),
957
1267
  ),
958
- React.createElement('div', { style: { ...s.muted, marginTop: 3, fontSize: 11 } }, desc),
1268
+ React.createElement('div', { style: { ...s.muted, marginTop: 4, fontSize: 11 } }, desc),
959
1269
  );
960
1270
  }),
961
1271
  ),
@@ -970,7 +1280,7 @@ function BridgePanel({ rpcCall }) {
970
1280
  );
971
1281
  }
972
1282
 
973
- return React.createElement('div', { style: { maxWidth: 560 } },
1283
+ return React.createElement('div', { style: { maxWidth: 620 } },
974
1284
  err && React.createElement('div', {
975
1285
  style: { ...s.card, background: 'var(--dsw-alias-state-error-bg,#fef2f2)', color: 'var(--dsw-alias-state-error-primary,#dc2626)', fontSize: 13, marginBottom: 16 },
976
1286
  }, err),