@xmanrui/dsh-im 4.11.0 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmanrui/dsh-im",
3
- "version": "4.11.0",
3
+ "version": "4.12.0",
4
4
  "description": "把九种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
4
+ import { CollapsibleAccountSection } from '../shared/collapsible-account.js';
4
5
  import { h } from '../../i18n.js';
5
6
  import { WorkspaceEditor } from '../../workspace-editor.js';
6
7
  import { ContextEnhancementEditor } from '../../context-enhancement.js';
@@ -256,13 +257,20 @@ export function AccountCard({
256
257
  const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
257
258
  return h('article', { className: 'ddt-card dim-botCard', tabIndex: -1, 'data-bot-id': account.botId },
258
259
  h('div', { className: 'ddt-cardBody dim-botCardBody' },
259
- h('div', { className: 'ddt-accountTop dim-botCardTop' },
260
+ h(CollapsibleAccountSection, {
261
+ id: `ddt-settings-${account.botId.replace(/[^a-zA-Z0-9_-]/g, '-')}`,
262
+ header: h('div', { className: 'ddt-accountTop dim-botCardTop' },
260
263
  h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
261
264
  h('div', { className: 'ddt-avatar dim-botAvatar', 'aria-hidden': 'true' }, h(DingtalkIcon, { size: 29 })),
262
265
  h('div', { className: 'dim-botName' },
263
266
  h('h3', { title: account.bot.name }, account.bot.name),
264
267
  h('p', { title: account.bot.clientIdMasked }, account.bot.clientIdMasked))),
265
- h('div', { className: 'dim-botCardTools' },
268
+ h('div', {
269
+ className: 'dim-botCardTools',
270
+ // The header is the collapse toggle; keep inner controls clickable.
271
+ onClick: (event) => { event.stopPropagation(); },
272
+ onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') event.stopPropagation(); },
273
+ },
266
274
  h(BotStatusMeta, {
267
275
  className: 'ddt-health',
268
276
  dotClassName: 'ddt-dot',
@@ -277,8 +285,9 @@ export function AccountCard({
277
285
  botName: account.bot.name,
278
286
  connected: account.connected,
279
287
  accessPolicy: account.accessPolicy,
280
- }))),
281
- h(WorkspaceEditor, {
288
+ })))
289
+ },
290
+ h(WorkspaceEditor, {
282
291
  workspace: account.workspace,
283
292
  disabled: Boolean(busy),
284
293
  onSave: onWorkspaceSave,
@@ -314,7 +323,9 @@ export function AccountCard({
314
323
  feedback ? h('div', {
315
324
  className: 'ddt-summary dim-cardFeedback',
316
325
  role: 'status',
317
- }, feedback) : null))),
326
+ }, feedback) : null)),
327
+ ),
328
+ ),
318
329
  removing ? h(RemoveConfirmation, {
319
330
  account,
320
331
  busy: busy === 'delete',
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
 
3
3
  import { FeishuLogoGlyph } from "../../channel-logos.js";
4
4
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from "../../credential-binding.js";
5
+ import { CollapsibleAccountSection } from "../shared/collapsible-account.js";
5
6
  import { h } from "../../i18n.js";
6
7
  import {
7
8
  FEISHU_ENDPOINTS,
@@ -506,42 +507,49 @@ export function BotCard({
506
507
  ref: cardRef,
507
508
  },
508
509
  h("div", { className: "bxf-cardBody dim-botCardBody" },
509
- h("div", { className: "bxf-connectedTop dim-botCardTop" },
510
- h("div", { className: "bxf-botIdentity dim-botIdentity" },
511
- h("div", { className: "bxf-avatar dim-botAvatar", "aria-hidden": "true" },
512
- h(FeishuLogoGlyph, { size: 34 })),
513
- h("div", { className: "bxf-botName dim-botName" },
514
- h("h3", { id: titleId, title: bot.name }, bot.name),
515
- h("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "应用标识已安全保存")),
516
- ),
517
- h("div", { className: "dim-botCardTools" },
518
- h(BotStatusMeta, {
519
- className: "bxf-healthPill",
520
- dotClassName: "bxf-dot",
521
- tone,
522
- stateLabel: HEALTH_LABELS[stateForDisplay] ?? "状态未知",
523
- lastCheckedAt: health.lastCheckedAt,
524
- formatCheckedTime,
525
- healthState: stateForDisplay,
526
- }),
527
- h(BotSettingsButton, {
528
- channel: "feishu",
529
- botId: connection.botId,
530
- botName: bot.name,
531
- connected,
532
- accessPolicy: connection.accessPolicy,
533
- channelSettings: {
534
- groupResponseMode: connection.groupResponseMode,
535
- groupTopicReply: connection.groupTopicReply,
536
- groupMessagePermissionGranted: connection.groupMessagePermissionGranted,
537
- },
538
- })),
539
- ),
540
- h(WorkspaceEditor, {
541
- workspace: connection.workspace,
542
- disabled: Boolean(busy),
543
- onSave: onWorkspaceSave,
544
- }),
510
+ h(CollapsibleAccountSection, {
511
+ id: `bxf-settings-${connection.botId.replace(/[^a-zA-Z0-9_-]/g, "-")}`,
512
+ header: h("div", { className: "bxf-connectedTop dim-botCardTop" },
513
+ h("div", { className: "bxf-botIdentity dim-botIdentity" },
514
+ h("div", { className: "bxf-avatar dim-botAvatar", "aria-hidden": "true" },
515
+ h(FeishuLogoGlyph, { size: 34 })),
516
+ h("div", { className: "bxf-botName dim-botName" },
517
+ h("h3", { id: titleId, title: bot.name }, bot.name),
518
+ h("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "应用标识已安全保存")),
519
+ ),
520
+ h("div", {
521
+ className: "dim-botCardTools",
522
+ // The header is the collapse toggle; keep inner controls clickable.
523
+ onClick: (event) => { event.stopPropagation(); },
524
+ onKeyDown: (event) => { if (event.key === "Enter" || event.key === " ") event.stopPropagation(); },
525
+ },
526
+ h(BotStatusMeta, {
527
+ className: "bxf-healthPill",
528
+ dotClassName: "bxf-dot",
529
+ tone,
530
+ stateLabel: HEALTH_LABELS[stateForDisplay] ?? "状态未知",
531
+ lastCheckedAt: health.lastCheckedAt,
532
+ formatCheckedTime,
533
+ healthState: stateForDisplay,
534
+ }),
535
+ h(BotSettingsButton, {
536
+ channel: "feishu",
537
+ botId: connection.botId,
538
+ botName: bot.name,
539
+ connected,
540
+ accessPolicy: connection.accessPolicy,
541
+ channelSettings: {
542
+ groupResponseMode: connection.groupResponseMode,
543
+ groupTopicReply: connection.groupTopicReply,
544
+ groupMessagePermissionGranted: connection.groupMessagePermissionGranted,
545
+ },
546
+ }))),
547
+ },
548
+ h(WorkspaceEditor, {
549
+ workspace: connection.workspace,
550
+ disabled: Boolean(busy),
551
+ onSave: onWorkspaceSave,
552
+ }),
545
553
  h(ModelEditor, {
546
554
  model: connection.model,
547
555
  disabled: Boolean(busy),
@@ -605,6 +613,7 @@ export function BotCard({
605
613
  className: "bxf-healthSummary dim-cardFeedback",
606
614
  role: "status",
607
615
  }, testNotice) : null),
616
+ ),
608
617
  ),
609
618
  ),
610
619
  removing
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { QqLogoGlyph } from '../../channel-logos.js';
4
4
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
5
+ import { CollapsibleAccountSection } from '../shared/collapsible-account.js';
5
6
  import { h } from '../../i18n.js';
6
7
  import { WorkspaceEditor } from '../../workspace-editor.js';
7
8
  import { ContextEnhancementEditor } from '../../context-enhancement.js';
@@ -184,12 +185,19 @@ export function AccountCard({
184
185
  const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
185
186
  return h('article', { className: 'ddt-card dim-botCard', 'data-bot-id': account.botId },
186
187
  h('div', { className: 'ddt-cardBody dim-botCardBody' },
187
- h('div', { className: 'ddt-accountTop dim-botCardTop' },
188
+ h(CollapsibleAccountSection, {
189
+ id: `qq-settings-${account.botId.replace(/[^a-zA-Z0-9_-]/g, '-')}`,
190
+ header: h('div', { className: 'ddt-accountTop dim-botCardTop' },
188
191
  h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
189
192
  h('div', { className: 'ddt-avatar dim-botAvatar dqq-avatar', 'aria-hidden': 'true' }, h(QqLogoGlyph, { size: 29 })),
190
193
  h('div', { className: 'dim-botName' },
191
194
  h('h3', null, account.bot.name), h('p', null, account.bot.appIdMasked))),
192
- h('div', { className: 'dim-botCardTools' },
195
+ h('div', {
196
+ className: 'dim-botCardTools',
197
+ // The header is the collapse toggle; keep inner controls clickable.
198
+ onClick: (event) => { event.stopPropagation(); },
199
+ onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') event.stopPropagation(); },
200
+ },
193
201
  h(BotStatusMeta, {
194
202
  className: 'ddt-health',
195
203
  dotClassName: 'ddt-dot',
@@ -204,8 +212,9 @@ export function AccountCard({
204
212
  botName: account.bot.name,
205
213
  connected: account.connected,
206
214
  accessPolicy: account.accessPolicy,
207
- }))),
208
- h(WorkspaceEditor, {
215
+ })))
216
+ },
217
+ h(WorkspaceEditor, {
209
218
  workspace: account.workspace,
210
219
  disabled: Boolean(busy),
211
220
  onSave: onWorkspaceSave,
@@ -239,7 +248,9 @@ export function AccountCard({
239
248
  className: 'ddt-summary dim-cardFeedback',
240
249
  role: 'status',
241
250
  'aria-live': 'polite',
242
- }, feedback) : null))),
251
+ }, feedback) : null)),
252
+ ),
253
+ ),
243
254
  removing ? h(RemoveConfirmation, {
244
255
  account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove,
245
256
  }) : null);
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Collapsible per-account card, shared by every IM channel.
3
+ *
4
+ * When several accounts are connected, each account's workspace / preset /
5
+ * context-enhancement editors make the list extremely long. This component
6
+ * renders the account header as the toggle itself: collapsed, the card shows
7
+ * only the header line; clicking the header expands the settings details.
8
+ *
9
+ * Design notes:
10
+ * - The whole header is the toggle (click + keyboard). Buttons rendered inside
11
+ * the header must call stopPropagation so they keep their own behaviour.
12
+ * - A chevron sits at the end of the header line as the affordance.
13
+ * - Height animation uses grid-template-rows: 0fr -> 1fr (pure CSS).
14
+ * - Full a11y: the header is a button-like region with aria-expanded /
15
+ * aria-controls; the details region keeps native semantics.
16
+ * - Honors prefers-reduced-motion.
17
+ * - Styles are managed by the IM settings plugin, not individual accounts.
18
+ */
19
+ import * as React from 'react';
20
+ import { h } from '../../i18n.js';
21
+
22
+ /**
23
+ * Collapsible account card.
24
+ *
25
+ * `header` renders the always-visible header line; clicking it toggles.
26
+ * Any interactive control inside the header should stop the click event so it
27
+ * does not toggle the section (e.g. the bot settings button).
28
+ * `children` is the collapsed details region.
29
+ */
30
+ export function CollapsibleAccountSection({
31
+ header,
32
+ defaultOpen = false,
33
+ open: controlledOpen,
34
+ onToggle,
35
+ id,
36
+ className = '',
37
+ children,
38
+ }) {
39
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
40
+ const open = controlledOpen ?? uncontrolledOpen;
41
+ const contentId = id ? `${id}-content` : undefined;
42
+
43
+ const toggle = () => {
44
+ if (controlledOpen === undefined) setUncontrolledOpen((value) => !value);
45
+ onToggle?.(!open);
46
+ };
47
+
48
+ const onKeyDown = (event) => {
49
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
50
+ event.preventDefault();
51
+ toggle();
52
+ }
53
+ };
54
+
55
+ return h('div', {
56
+ className: `dim-collapsibleAccount ${open ? 'is-open' : ''} ${className}`.trim(),
57
+ 'data-open': open ? 'true' : 'false',
58
+ },
59
+ h('div', {
60
+ className: 'dim-collapsibleHead',
61
+ role: 'button',
62
+ tabIndex: 0,
63
+ onClick: toggle,
64
+ onKeyDown,
65
+ 'aria-expanded': open ? 'true' : 'false',
66
+ 'aria-controls': contentId,
67
+ 'aria-label': open ? '收起该账号的设置' : '展开该账号的设置',
68
+ },
69
+ h('div', { className: 'dim-collapsibleHeaderContent' }, header),
70
+ h('span', { className: 'dim-collapsibleChevron', 'aria-hidden': 'true' }),
71
+ ),
72
+ h('div', {
73
+ id: contentId,
74
+ className: 'dim-collapsibleBody',
75
+ role: 'region',
76
+ },
77
+ h('div', { className: 'dim-collapsibleBodyInner' }, children),
78
+ ),
79
+ );
80
+ }
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import { CredentialActionIcon, CredentialBindingPanel } from '../../credential-binding.js';
4
+ import { CollapsibleAccountSection } from './collapsible-account.js';
4
5
  import { h } from '../../i18n.js';
5
6
  import { installDingtalkStyles } from '../dingtalk/styles.js';
6
7
  import { WorkspaceEditor } from '../../workspace-editor.js';
@@ -88,33 +89,41 @@ export function createTokenChannelSettings(definition) {
88
89
  const identity = account.bot.username ? `@${account.bot.username}` : account.bot.idMasked;
89
90
  return h('article', { className: 'ddt-card dim-botCard', 'data-bot-id': account.botId },
90
91
  h('div', { className: 'ddt-cardBody dim-botCardBody' },
91
- h('div', { className: 'ddt-accountTop dim-botCardTop' },
92
- h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
93
- h('div', { className: `ddt-avatar dim-botAvatar ${avatarClass}`, 'aria-hidden': 'true' },
94
- h(LogoGlyph, { size: 29 })),
95
- h('div', { className: 'dim-botName' },
96
- h('h3', null, account.bot.name), h('p', null, identity))),
97
- h('div', { className: 'dim-botCardTools' },
98
- h(BotStatusMeta, {
99
- className: 'ddt-health',
100
- dotClassName: 'ddt-dot',
101
- tone,
102
- stateLabel,
103
- lastCheckedAt: account.health.lastCheckedAt,
104
- formatCheckedTime: checkedTime,
105
- }),
106
- h(BotSettingsButton, {
107
- channel: channel.toLowerCase(),
108
- botId: account.botId,
109
- botName: account.bot.name,
110
- connected: account.connected,
111
- accessPolicy: account.accessPolicy,
112
- }))),
113
- h(WorkspaceEditor, {
114
- workspace: account.workspace,
115
- disabled: Boolean(busy),
116
- onSave: onWorkspaceSave,
117
- }),
92
+ h(CollapsibleAccountSection, {
93
+ id: `tok-settings-${account.botId.replace(/[^a-zA-Z0-9_-]/g, '-')}`,
94
+ header: h('div', { className: 'ddt-accountTop dim-botCardTop' },
95
+ h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
96
+ h('div', { className: `ddt-avatar dim-botAvatar ${avatarClass}`, 'aria-hidden': 'true' },
97
+ h(LogoGlyph, { size: 29 })),
98
+ h('div', { className: 'dim-botName' },
99
+ h('h3', null, account.bot.name), h('p', null, identity))),
100
+ h('div', {
101
+ className: 'dim-botCardTools',
102
+ // The header is the collapse toggle; keep inner controls clickable.
103
+ onClick: (event) => { event.stopPropagation(); },
104
+ onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') event.stopPropagation(); },
105
+ },
106
+ h(BotStatusMeta, {
107
+ className: 'ddt-health',
108
+ dotClassName: 'ddt-dot',
109
+ tone,
110
+ stateLabel,
111
+ lastCheckedAt: account.health.lastCheckedAt,
112
+ formatCheckedTime: checkedTime,
113
+ }),
114
+ h(BotSettingsButton, {
115
+ channel: channel.toLowerCase(),
116
+ botId: account.botId,
117
+ botName: account.bot.name,
118
+ connected: account.connected,
119
+ accessPolicy: account.accessPolicy,
120
+ }))),
121
+ },
122
+ h(WorkspaceEditor, {
123
+ workspace: account.workspace,
124
+ disabled: Boolean(busy),
125
+ onSave: onWorkspaceSave,
126
+ }),
118
127
  h(ModelEditor, {
119
128
  model: account.model,
120
129
  disabled: Boolean(busy),
@@ -157,7 +166,9 @@ export function createTokenChannelSettings(definition) {
157
166
  testNotice ? h('div', {
158
167
  className: 'ddt-summary dim-cardFeedback',
159
168
  role: 'status',
160
- }, testNotice) : null))),
169
+ }, testNotice) : null)),
170
+ ),
171
+ ),
161
172
  removing ? h('div', { className: 'ddt-confirm dim-confirm', role: 'alertdialog' },
162
173
  h('strong', null, `从 DeepSeek Harness 移除“${account.bot.name}”?`),
163
174
  h('p', null, `这会停止消息连接,并删除本机保存的 ${credentialNoun}、机器人配置及会话映射。${platformLabel}中的机器人不会被自动删除。`),
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { WecomLogoGlyph } from '../../channel-logos.js';
4
4
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
5
+ import { CollapsibleAccountSection } from '../shared/collapsible-account.js';
5
6
  import { h } from '../../i18n.js';
6
7
  import { WorkspaceEditor } from '../../workspace-editor.js';
7
8
  import { ContextEnhancementEditor } from '../../context-enhancement.js';
@@ -183,12 +184,19 @@ export function AccountCard({
183
184
  const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
184
185
  return h('article', { className: 'ddt-card dim-botCard', 'data-bot-id': account.botId },
185
186
  h('div', { className: 'ddt-cardBody dim-botCardBody' },
186
- h('div', { className: 'ddt-accountTop dim-botCardTop' },
187
+ h(CollapsibleAccountSection, {
188
+ id: `wec-settings-${account.botId.replace(/[^a-zA-Z0-9_-]/g, '-')}`,
189
+ header: h('div', { className: 'ddt-accountTop dim-botCardTop' },
187
190
  h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
188
191
  h('div', { className: 'ddt-avatar dim-botAvatar dwecom-avatar', 'aria-hidden': 'true' }, h(WecomLogoGlyph, { size: 29 })),
189
192
  h('div', { className: 'dim-botName' },
190
193
  h('h3', null, account.bot.name), h('p', null, account.bot.appIdMasked))),
191
- h('div', { className: 'dim-botCardTools' },
194
+ h('div', {
195
+ className: 'dim-botCardTools',
196
+ // The header is the collapse toggle; keep inner controls clickable.
197
+ onClick: (event) => { event.stopPropagation(); },
198
+ onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') event.stopPropagation(); },
199
+ },
192
200
  h(BotStatusMeta, {
193
201
  className: 'ddt-health',
194
202
  dotClassName: 'ddt-dot',
@@ -203,8 +211,9 @@ export function AccountCard({
203
211
  botName: account.bot.name,
204
212
  connected: account.connected,
205
213
  accessPolicy: account.accessPolicy,
206
- }))),
207
- h(WorkspaceEditor, {
214
+ })))
215
+ },
216
+ h(WorkspaceEditor, {
208
217
  workspace: account.workspace,
209
218
  disabled: Boolean(busy),
210
219
  onSave: onWorkspaceSave,
@@ -238,7 +247,9 @@ export function AccountCard({
238
247
  className: 'ddt-summary dim-cardFeedback',
239
248
  role: 'status',
240
249
  'aria-live': 'polite',
241
- }, feedback) : null))),
250
+ }, feedback) : null)),
251
+ ),
252
+ ),
242
253
  removing ? h(RemoveConfirmation, {
243
254
  account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove,
244
255
  }) : null);
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { WeixinLogoGlyph } from '../../channel-logos.js';
4
4
  import { QrActionIcon } from '../../credential-binding.js';
5
+ import { CollapsibleAccountSection } from '../shared/collapsible-account.js';
5
6
  import { h } from '../../i18n.js';
6
7
  import {
7
8
  WEIXIN_ENDPOINTS,
@@ -225,11 +226,18 @@ export function AccountCard({
225
226
  const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
226
227
  return h('article', { className: 'dxw-card dim-botCard', tabIndex: -1, 'data-bot-id': account.botId },
227
228
  h('div', { className: 'dxw-cardBody dim-botCardBody' },
228
- h('div', { className: 'dxw-accountTop dim-botCardTop' },
229
+ h(CollapsibleAccountSection, {
230
+ id: `dxw-settings-${account.botId.replace(/[^a-zA-Z0-9_-]/g, '-')}`,
231
+ header: h('div', { className: 'dxw-accountTop dim-botCardTop' },
229
232
  h('div', { className: 'dxw-accountIdentity dim-botIdentity' },
230
233
  h('div', { className: 'dxw-avatar dim-botAvatar', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 27 })),
231
234
  h('div', { className: 'dim-botName' }, h('h3', null, account.bot.name), h('p', null, account.bot.accountIdMasked))),
232
- h('div', { className: 'dim-botCardTools' },
235
+ h('div', {
236
+ className: 'dim-botCardTools',
237
+ // The header is the collapse toggle; keep inner controls clickable.
238
+ onClick: (event) => { event.stopPropagation(); },
239
+ onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') event.stopPropagation(); },
240
+ },
233
241
  h(BotStatusMeta, {
234
242
  className: 'dxw-health',
235
243
  dotClassName: 'dxw-dot',
@@ -244,8 +252,9 @@ export function AccountCard({
244
252
  botName: account.bot.name,
245
253
  connected: account.connected,
246
254
  accessPolicy: account.accessPolicy,
247
- }))),
248
- h(WorkspaceEditor, {
255
+ })))
256
+ },
257
+ h(WorkspaceEditor, {
249
258
  workspace: account.workspace,
250
259
  disabled: Boolean(busy),
251
260
  onSave: onWorkspaceSave,
@@ -281,7 +290,9 @@ export function AccountCard({
281
290
  className: 'dxw-summary dim-cardFeedback',
282
291
  role: 'status',
283
292
  'aria-live': 'polite',
284
- }, feedback) : null))),
293
+ }, feedback) : null)),
294
+ ),
295
+ ),
285
296
  removing ? h('div', { className: 'dxw-confirm dim-confirm', role: 'alertdialog' },
286
297
  h('strong', null, '从此 Harness 移除这个微信账号?'),
287
298
  h('p', null, '这会停止消息连接,并删除本机保存的 bot_token、账号配置和会话映射。其他微信账号不受影响。'),
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { WhatsappLogoGlyph } from '../../channel-logos.js';
4
4
  import { QrActionIcon } from '../../credential-binding.js';
5
+ import { CollapsibleAccountSection } from '../shared/collapsible-account.js';
5
6
  import { h } from '../../i18n.js';
6
7
  import { WorkspaceEditor } from '../../workspace-editor.js';
7
8
  import { ContextEnhancementEditor } from '../../context-enhancement.js';
@@ -203,7 +204,9 @@ export function WhatsappAccountCard({
203
204
  const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
204
205
  return h('article', { className: 'ddt-card dim-botCard', 'data-bot-id': account.botId },
205
206
  h('div', { className: 'ddt-cardBody dim-botCardBody' },
206
- h('div', { className: 'ddt-accountTop dim-botCardTop' },
207
+ h(CollapsibleAccountSection, {
208
+ id: `wsp-settings-${account.botId.replace(/[^a-zA-Z0-9_-]/g, '-')}`,
209
+ header: h('div', { className: 'ddt-accountTop dim-botCardTop' },
207
210
  h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
208
211
  h('div', {
209
212
  className: 'ddt-avatar dim-botAvatar dwa-avatar',
@@ -211,7 +214,12 @@ export function WhatsappAccountCard({
211
214
  }, h(WhatsappLogoGlyph, { size: 29 })),
212
215
  h('div', { className: 'dim-botName' },
213
216
  h('h3', null, account.bot.name), h('p', null, account.bot.idMasked))),
214
- h('div', { className: 'dim-botCardTools' },
217
+ h('div', {
218
+ className: 'dim-botCardTools',
219
+ // The header is the collapse toggle; keep inner controls clickable.
220
+ onClick: (event) => { event.stopPropagation(); },
221
+ onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') event.stopPropagation(); },
222
+ },
215
223
  h(BotStatusMeta, {
216
224
  className: 'ddt-health',
217
225
  dotClassName: 'ddt-dot',
@@ -226,8 +234,9 @@ export function WhatsappAccountCard({
226
234
  botName: account.bot.name,
227
235
  connected: account.connected,
228
236
  accessPolicy: account.accessPolicy,
229
- }))),
230
- h(WorkspaceEditor, {
237
+ })))
238
+ },
239
+ h(WorkspaceEditor, {
231
240
  workspace: account.workspace,
232
241
  disabled: Boolean(busy),
233
242
  onSave: onWorkspaceSave,
@@ -264,7 +273,9 @@ export function WhatsappAccountCard({
264
273
  testNotice ? h('div', {
265
274
  className: 'ddt-summary dim-cardFeedback',
266
275
  role: 'status',
267
- }, testNotice) : null))),
276
+ }, testNotice) : null)),
277
+ ),
278
+ ),
268
279
  removing ? h(RemoveConfirmation, {
269
280
  account,
270
281
  busy: busy === 'delete',
@@ -227,6 +227,8 @@ const EN = Object.freeze({
227
227
  '手动接入': 'Manual setup',
228
228
  '收起凭据': 'Hide credentials',
229
229
  '收起接入': 'Hide setup',
230
+ '展开该账号的设置': "Expand this account's settings",
231
+ '收起该账号的设置': "Collapse this account's settings",
230
232
  '接入机器人': 'Connect bot',
231
233
  '开始接入': 'Start setup',
232
234
  '在线': 'online',
@@ -358,6 +358,17 @@ const CSS = String.raw`
358
358
  .dim-panel .dim-botCard { position: relative; min-width: 0; width: 100%; max-width: 100%; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
359
359
  .dim-panel .dim-botCard::before { display: none; }
360
360
  .dim-panel .dim-botCardBody { position: relative; min-width: 0; width: 100%; max-width: 100%; padding: 12px; }
361
+ .dim-collapsibleAccount { min-width: 0; display: flex; flex-direction: column; }
362
+ .dim-collapsibleHead { min-width: 0; display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; -webkit-user-select: none; }
363
+ .dim-collapsibleHead:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; border-radius: 8px; }
364
+ .dim-collapsibleHeaderContent { min-width: 0; flex: 1 1 auto; display: flex; align-items: center; }
365
+ .dim-collapsibleChevron { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 9px; height: 9px; border-right: 1.6px solid var(--dsw-alias-label-tertiary, #8f959e); border-bottom: 1.6px solid var(--dsw-alias-label-tertiary, #8f959e); transform: rotate(-45deg); transition: transform .22s cubic-bezier(.4, 0, .2, 1); transform-origin: 50% 50%; }
366
+ .dim-collapsibleAccount.is-open .dim-collapsibleChevron { transform: rotate(45deg); }
367
+ /* Animate height without measuring content; hide collapsed controls from focus and accessibility. */
368
+ .dim-collapsibleBody { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .22s cubic-bezier(.4, 0, .2, 1); }
369
+ .dim-collapsibleAccount.is-open > .dim-collapsibleBody { grid-template-rows: 1fr; }
370
+ .dim-collapsibleBodyInner { min-height: 0; overflow: hidden; }
371
+ .dim-collapsibleAccount:not(.is-open) .dim-collapsibleBodyInner { visibility: hidden; }
361
372
  .dim-panel .dim-botCardTop { min-width: 0; max-width: 100%; display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
362
373
  .dim-panel .dim-botIdentity { min-width: 0; flex: 1 1 0; display: flex; align-items: center; gap: 10px; }
363
374
  .dim-panel .dim-botAvatar { flex: none; width: 38px; height: 38px; display: grid; place-items: center; overflow: hidden; border-radius: 11px; box-shadow: none; }
@@ -634,6 +645,7 @@ const CSS = String.raw`
634
645
  @media (prefers-reduced-motion: reduce) {
635
646
  .dim-page * { transition-duration: .01ms !important; }
636
647
  .dim-directoryPickerSpinner { animation-duration: 1.8s; }
648
+ .dim-collapsibleBody, .dim-collapsibleChevron { transition: none !important; }
637
649
  }
638
650
  `;
639
651
 
@@ -1,5 +1,29 @@
1
1
  // English translations (wecom area). Keys are exact Chinese literals passed to t().
2
2
  export default {
3
+ '/menu 或 /m 打开可点击的功能菜单': '/menu or /m Open the interactive menu',
4
+ '保持当前': 'Keep current',
5
+ '会话': 'Session',
6
+ '模型': 'Model',
7
+ '预设': 'Preset',
8
+ '跟随默认模型': 'Default model',
9
+ '跟随 Host 默认': 'Follow Host default',
10
+ '新会话': 'New session',
11
+ '上一页': 'Previous',
12
+ '下一页': 'Next',
13
+ '返回': 'Back',
14
+ '更多选项…': 'More options…',
15
+ '选择会话、模型或预设后,点击应用设置。': 'Choose a session, model or preset, then apply settings.',
16
+ '应用设置': 'Apply settings',
17
+ '工作区与任务': 'Workspace & tasks',
18
+ '应用选择': 'Apply selection',
19
+ '请选择一个选项后再应用。': 'Select one option before applying.',
20
+ '设置未改变。': 'Settings unchanged.',
21
+ '已收到操作,请查看下方结果。': 'Action received. See the result below.',
22
+ '重新打开菜单': 'Reopen menu',
23
+ '暂无可用选项。': 'No options are available.',
24
+ '预设仅用于之后的新会话。': 'Presets apply to future new sessions.',
25
+ '部分模型暂不可用,可稍后重试。': 'Some models are unavailable. Try again later.',
26
+ '工作区已变化,请从新菜单重新选择。': 'The workspace changed. Please select again from the new menu.',
3
27
  // Help text (wecom-bridge.mjs)
4
28
  '企业微信机器人已连接 DeepSeek Harness。': 'The Enterprise WeChat bot is connected to DeepSeek Harness.',
5
29