@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/README.md +70 -6
- package/README.zh-CN.md +70 -6
- package/client/client.js +542 -183
- package/client/index.js +475 -165
- package/docs/qq-usage.md +385 -0
- package/lib/bridge-rpc-constants.js +1 -0
- package/lib/bridge-rpc.js +5 -0
- package/lib/index.js +88 -8
- 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,21 +36,57 @@ 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
|
};
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
214
|
-
|
|
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
|
|
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
|
|
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, '/
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
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
|
|
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('
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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',
|
|
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
|
-
|
|
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:
|
|
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('
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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('
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
}, '
|
|
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
|
-
}, '
|
|
887
|
+
}, '反馈 Issue'),
|
|
672
888
|
);
|
|
673
889
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
1003
|
+
|
|
1004
|
+
upgradeResult && React.createElement('div', {
|
|
704
1005
|
style: {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
710
|
-
|
|
1012
|
+
marginBottom: 8,
|
|
1013
|
+
lineHeight: 1.5,
|
|
711
1014
|
},
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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:
|
|
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:
|
|
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',
|
|
918
|
-
{ id: 'qq',
|
|
919
|
-
{ id: 'feishu',
|
|
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
|
|
936
|
-
border: `1px solid ${selectedPlatform === id ? 'var(--dsw-alias-
|
|
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.
|
|
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)' :
|
|
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',
|
|
947
|
-
|
|
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:
|
|
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:
|
|
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),
|