hiwork-core 0.0.30 → 0.0.37

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.
Files changed (41) hide show
  1. package/cordis.patch.yml +15 -1
  2. package/lib/ai-router-client.js +394 -2
  3. package/lib/browser-login.js +42 -16
  4. package/lib/client-policy.js +2 -2
  5. package/lib/client.js +2496 -500
  6. package/lib/image-rpc.js +306 -0
  7. package/lib/imagegen.js +424 -0
  8. package/lib/index.js +18639 -7060
  9. package/lib/knowledge-config.js +91 -1
  10. package/lib/provision-session.js +10 -2
  11. package/lib/tool-names.js +10 -0
  12. package/lib/tools.js +205 -0
  13. package/lib/types/ai-router-client.d.ts +255 -2
  14. package/lib/types/browser-login.d.ts +10 -1
  15. package/lib/types/client/about-section.d.ts +27 -7
  16. package/lib/types/client/controller.d.ts +17 -9
  17. package/lib/types/client/daily-data.d.ts +31 -3
  18. package/lib/types/client/feature-center.d.ts +46 -1
  19. package/lib/types/client/feature-registry.d.ts +36 -3
  20. package/lib/types/client/image-cards.d.ts +218 -0
  21. package/lib/types/client/image-styles.d.ts +9 -0
  22. package/lib/types/client/images-data.d.ts +127 -0
  23. package/lib/types/client/images-page.d.ts +97 -0
  24. package/lib/types/client/index.d.ts +0 -1
  25. package/lib/types/client/login-gate.d.ts +2 -2
  26. package/lib/types/client/logo.d.ts +7 -2
  27. package/lib/types/client/menu-preferences.d.ts +163 -0
  28. package/lib/types/client/menu-section.d.ts +5 -0
  29. package/lib/types/client/menu-store.d.ts +101 -0
  30. package/lib/types/client/styles.d.ts +16 -0
  31. package/lib/types/client/types.d.ts +9 -0
  32. package/lib/types/image-rpc.d.ts +142 -0
  33. package/lib/types/imagegen.d.ts +241 -0
  34. package/lib/types/index.d.ts +89 -8
  35. package/lib/types/knowledge-config.d.ts +50 -2
  36. package/lib/types/tool-names.d.ts +11 -0
  37. package/lib/types/tools.d.ts +80 -0
  38. package/lib/types/user-agent.d.ts +31 -0
  39. package/lib/user-agent.js +54 -0
  40. package/lib/web-search.js +2 -2
  41. package/package.json +34 -24
package/lib/client.js CHANGED
@@ -65,6 +65,10 @@ var secondaryButton = {
65
65
  color: textSecondary,
66
66
  fontSize: 14
67
67
  };
68
+ var disabledButton = {
69
+ opacity: 0.45,
70
+ cursor: "not-allowed"
71
+ };
68
72
  var textInput = {
69
73
  padding: "9px 12px",
70
74
  borderRadius: 10,
@@ -110,7 +114,338 @@ var interactionCss = [
110
114
  // 账号行图标钮(用户名/设置/退出账号)hover:底变浅、图标提亮,同侧边栏 iconButton。
111
115
  `[data-hiwork-core-icon]:hover { background: ${interactiveHover} !important; color: ${textPrimary} !important; }`,
112
116
  // 品牌蓝按钮(登录门禁)hover:提亮而非换底色,保留渐变。
113
- `[data-hiwork-core-brand]:hover:not(:disabled) { filter: brightness(1.08) !important; }`
117
+ `[data-hiwork-core-brand]:hover:not(:disabled) { filter: brightness(1.08) !important; }`,
118
+ // 行内文字链接(设置「关于」的版本/官网/手册):hover 补下划线,走颜色之外的
119
+ // 唯一提示维度,不抢布局。
120
+ `[data-hiwork-core-linktext]:hover { text-decoration: underline; }`
121
+ ].join("\n");
122
+ var imageCardsCss = [
123
+ /* ---- 会话内卡片 ---- */
124
+ `.hiwork-image-card {`,
125
+ ` display: flex;`,
126
+ ` flex-direction: column;`,
127
+ ` gap: 8px;`,
128
+ ` margin: 6px 0;`,
129
+ ` padding: 10px 12px;`,
130
+ ` border: 1px solid ${borderL2};`,
131
+ ` border-radius: 10px;`,
132
+ ` background: ${bgLayer2};`,
133
+ `}`,
134
+ `.hiwork-image-card-head {`,
135
+ ` display: flex;`,
136
+ ` align-items: baseline;`,
137
+ ` gap: 8px;`,
138
+ `}`,
139
+ `.hiwork-image-card-title {`,
140
+ ` font-size: 13px;`,
141
+ ` font-weight: 600;`,
142
+ ` color: ${textPrimary};`,
143
+ `}`,
144
+ `.hiwork-image-card-summary {`,
145
+ ` margin-left: auto;`,
146
+ ` font-size: 12px;`,
147
+ ` color: ${textTertiary};`,
148
+ ` font-variant-numeric: tabular-nums;`,
149
+ `}`,
150
+ `.hiwork-image-card-prompt {`,
151
+ ` margin: 0;`,
152
+ ` font-size: 12px;`,
153
+ ` line-height: 18px;`,
154
+ ` color: ${textSecondary};`,
155
+ ` white-space: pre-wrap;`,
156
+ ` word-break: break-word;`,
157
+ `}`,
158
+ `.hiwork-image-card-figure {`,
159
+ ` display: flex;`,
160
+ ` flex-direction: column;`,
161
+ ` gap: 6px;`,
162
+ ` margin: 0;`,
163
+ `}`,
164
+ `.hiwork-image-card-img {`,
165
+ ` display: block;`,
166
+ ` max-width: 100%;`,
167
+ ` max-height: 320px;`,
168
+ ` border: 1px solid ${borderL2};`,
169
+ ` border-radius: 8px;`,
170
+ ` background: ${bgLayer1};`,
171
+ ` cursor: zoom-in;`,
172
+ `}`,
173
+ `.hiwork-image-card-path {`,
174
+ ` font-size: 11px;`,
175
+ ` line-height: 16px;`,
176
+ ` color: ${textCaption};`,
177
+ ` word-break: break-all;`,
178
+ `}`,
179
+ `.hiwork-image-card-meta {`,
180
+ ` font-size: 11px;`,
181
+ ` color: ${textTertiary};`,
182
+ ` font-variant-numeric: tabular-nums;`,
183
+ `}`,
184
+ `.hiwork-image-card-actions {`,
185
+ ` display: flex;`,
186
+ ` align-items: center;`,
187
+ ` gap: 8px;`,
188
+ ` flex-wrap: wrap;`,
189
+ `}`,
190
+ `.hiwork-image-card-action {`,
191
+ ` padding: 4px 10px;`,
192
+ ` border: 1px solid ${borderL2};`,
193
+ ` border-radius: 8px;`,
194
+ ` background: transparent;`,
195
+ ` color: ${textSecondary};`,
196
+ ` font: inherit;`,
197
+ ` font-size: 11px;`,
198
+ ` line-height: 16px;`,
199
+ ` cursor: pointer;`,
200
+ `}`,
201
+ `.hiwork-image-card-action:hover {`,
202
+ ` background: ${interactiveHover};`,
203
+ ` color: ${textPrimary};`,
204
+ `}`,
205
+ `.hiwork-image-card-copy-state {`,
206
+ ` font-size: 11px;`,
207
+ ` color: ${textTertiary};`,
208
+ `}`,
209
+ `.hiwork-image-card-error {`,
210
+ ` margin: 0;`,
211
+ ` font-size: 12px;`,
212
+ ` line-height: 18px;`,
213
+ ` color: ${errorText};`,
214
+ `}`,
215
+ `.hiwork-image-card-retry-hint {`,
216
+ ` margin-left: 6px;`,
217
+ ` font-size: 11px;`,
218
+ ` color: ${warnText};`,
219
+ `}`,
220
+ /* ---- 放大浮层(半透明黑是浮层自带的,不引用宿主内部变量)---- */
221
+ `.hiwork-image-zoom {`,
222
+ ` position: fixed;`,
223
+ ` inset: 0;`,
224
+ ` z-index: 2147483000;`,
225
+ ` display: flex;`,
226
+ ` align-items: center;`,
227
+ ` justify-content: center;`,
228
+ ` padding: 32px;`,
229
+ ` background: rgba(0, 0, 0, 0.72);`,
230
+ ` cursor: zoom-out;`,
231
+ `}`,
232
+ `.hiwork-image-zoom img {`,
233
+ ` max-width: 100%;`,
234
+ ` max-height: 100%;`,
235
+ ` border-radius: 8px;`,
236
+ ` background: ${bgLayer1};`,
237
+ `}`,
238
+ `.hiwork-image-zoom-close {`,
239
+ ` position: absolute;`,
240
+ ` top: 16px;`,
241
+ ` right: 20px;`,
242
+ ` padding: 4px 12px;`,
243
+ ` border: 1px solid rgba(255, 255, 255, 0.4);`,
244
+ ` border-radius: 8px;`,
245
+ ` background: transparent;`,
246
+ ` color: #ffffff;`,
247
+ ` font: inherit;`,
248
+ ` font-size: 12px;`,
249
+ ` cursor: pointer;`,
250
+ `}`
251
+ ].join("\n");
252
+ var imagesPageCss = [
253
+ `.hiwork-images {`,
254
+ ` display: flex;`,
255
+ ` flex-direction: column;`,
256
+ ` gap: 14px;`,
257
+ ` box-sizing: border-box;`,
258
+ ` height: 100%;`,
259
+ ` padding: 20px 24px;`,
260
+ ` color: ${textPrimary};`,
261
+ ` overflow: hidden;`,
262
+ `}`,
263
+ `.hiwork-images-head {`,
264
+ ` display: flex;`,
265
+ ` align-items: baseline;`,
266
+ ` gap: 10px;`,
267
+ ` flex-wrap: wrap;`,
268
+ `}`,
269
+ `.hiwork-images-title {`,
270
+ ` margin: 0;`,
271
+ ` font-size: 18px;`,
272
+ ` font-weight: 600;`,
273
+ ` color: ${textPrimary};`,
274
+ `}`,
275
+ `.hiwork-images-count {`,
276
+ ` font-size: 12px;`,
277
+ ` color: ${textTertiary};`,
278
+ ` font-variant-numeric: tabular-nums;`,
279
+ `}`,
280
+ `.hiwork-images-body {`,
281
+ ` display: grid;`,
282
+ ` grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);`,
283
+ ` gap: 16px;`,
284
+ ` flex: 1;`,
285
+ ` min-height: 0;`,
286
+ `}`,
287
+ `.hiwork-images-list {`,
288
+ ` display: flex;`,
289
+ ` flex-direction: column;`,
290
+ ` gap: 6px;`,
291
+ ` box-sizing: border-box;`,
292
+ ` margin: 0;`,
293
+ ` padding: 8px;`,
294
+ ` border: 1px solid ${borderL2};`,
295
+ ` border-radius: 10px;`,
296
+ ` list-style: none;`,
297
+ ` overflow-y: auto;`,
298
+ `}`,
299
+ `.hiwork-images-row {`,
300
+ ` display: flex;`,
301
+ ` flex-direction: column;`,
302
+ ` gap: 4px;`,
303
+ ` box-sizing: border-box;`,
304
+ ` width: 100%;`,
305
+ ` padding: 8px 10px;`,
306
+ ` border: 1px solid transparent;`,
307
+ ` border-radius: 8px;`,
308
+ ` background: transparent;`,
309
+ ` color: inherit;`,
310
+ ` font: inherit;`,
311
+ ` text-align: left;`,
312
+ ` cursor: pointer;`,
313
+ `}`,
314
+ `.hiwork-images-row:hover {`,
315
+ ` background: ${interactiveHover};`,
316
+ `}`,
317
+ `.hiwork-images-row[aria-pressed="true"] {`,
318
+ ` border-color: ${brandAccent};`,
319
+ ` background: var(--dsw-alias-interactive-bg-active, rgba(15, 17, 21, 0.1));`,
320
+ `}`,
321
+ `.hiwork-images-row-prompt {`,
322
+ ` font-size: 12px;`,
323
+ ` line-height: 18px;`,
324
+ ` color: ${textPrimary};`,
325
+ ` overflow: hidden;`,
326
+ ` text-overflow: ellipsis;`,
327
+ ` white-space: nowrap;`,
328
+ `}`,
329
+ `.hiwork-images-row-meta {`,
330
+ ` font-size: 11px;`,
331
+ ` color: ${textTertiary};`,
332
+ ` font-variant-numeric: tabular-nums;`,
333
+ `}`,
334
+ `.hiwork-images-preview {`,
335
+ ` display: flex;`,
336
+ ` flex-direction: column;`,
337
+ ` gap: 10px;`,
338
+ ` box-sizing: border-box;`,
339
+ ` min-width: 0;`,
340
+ ` min-height: 0;`,
341
+ ` padding: 14px;`,
342
+ ` border: 1px solid ${borderL2};`,
343
+ ` border-radius: 10px;`,
344
+ ` background: ${bgLayer2};`,
345
+ ` overflow: auto;`,
346
+ `}`,
347
+ `.hiwork-images-image {`,
348
+ ` display: block;`,
349
+ ` max-width: 100%;`,
350
+ ` max-height: 60vh;`,
351
+ ` margin: 0 auto;`,
352
+ ` border-radius: 8px;`,
353
+ ` background: ${bgLayer1};`,
354
+ `}`,
355
+ `.hiwork-images-preview-body {`,
356
+ ` display: flex;`,
357
+ ` flex-direction: column;`,
358
+ ` gap: 10px;`,
359
+ ` min-height: 0;`,
360
+ `}`,
361
+ `.hiwork-images-empty {`,
362
+ ` display: flex;`,
363
+ ` flex-direction: column;`,
364
+ ` gap: 6px;`,
365
+ ` align-items: flex-start;`,
366
+ ` padding: 24px 4px;`,
367
+ `}`,
368
+ `.hiwork-images-preview-prompt {`,
369
+ ` margin: 0;`,
370
+ ` font-size: 12px;`,
371
+ ` line-height: 18px;`,
372
+ ` color: ${textSecondary};`,
373
+ ` white-space: pre-wrap;`,
374
+ ` word-break: break-word;`,
375
+ `}`,
376
+ `.hiwork-images-note {`,
377
+ ` margin: 0;`,
378
+ ` font-size: 12px;`,
379
+ ` line-height: 18px;`,
380
+ ` color: ${textTertiary};`,
381
+ `}`,
382
+ `.hiwork-images-empty-title {`,
383
+ ` margin: 0;`,
384
+ ` font-size: 14px;`,
385
+ ` font-weight: 600;`,
386
+ ` color: ${textSecondary};`,
387
+ `}`,
388
+ `.hiwork-images-error {`,
389
+ ` margin: 0;`,
390
+ ` font-size: 12px;`,
391
+ ` line-height: 18px;`,
392
+ ` color: ${errorText};`,
393
+ `}`,
394
+ `.hiwork-images-banner {`,
395
+ ` display: flex;`,
396
+ ` align-items: center;`,
397
+ ` gap: 8px;`,
398
+ ` padding: 8px 10px;`,
399
+ ` border-radius: 8px;`,
400
+ ` background: ${warnFill};`,
401
+ ` color: ${warnText};`,
402
+ ` font-size: 12px;`,
403
+ ` line-height: 18px;`,
404
+ `}`,
405
+ `.hiwork-images-saved {`,
406
+ ` font-size: 11px;`,
407
+ ` line-height: 16px;`,
408
+ ` color: ${textSecondary};`,
409
+ ` word-break: break-all;`,
410
+ `}`,
411
+ `.hiwork-images-actions {`,
412
+ ` display: flex;`,
413
+ ` align-items: center;`,
414
+ ` gap: 8px;`,
415
+ ` flex-wrap: wrap;`,
416
+ `}`,
417
+ `.hiwork-images-action {`,
418
+ ` padding: 5px 12px;`,
419
+ ` border: 1px solid ${borderL2};`,
420
+ ` border-radius: 8px;`,
421
+ ` background: transparent;`,
422
+ ` color: ${textSecondary};`,
423
+ ` font: inherit;`,
424
+ ` font-size: 12px;`,
425
+ ` line-height: 18px;`,
426
+ ` cursor: pointer;`,
427
+ `}`,
428
+ `.hiwork-images-action:hover:not(:disabled) {`,
429
+ ` background: ${interactiveHover};`,
430
+ ` color: ${textPrimary};`,
431
+ `}`,
432
+ `.hiwork-images-action:disabled {`,
433
+ ` opacity: 0.45;`,
434
+ ` cursor: not-allowed;`,
435
+ `}`,
436
+ `.hiwork-images-more {`,
437
+ ` padding: 6px 12px;`,
438
+ ` border: 1px solid ${borderL2};`,
439
+ ` border-radius: 8px;`,
440
+ ` background: transparent;`,
441
+ ` color: ${textSecondary};`,
442
+ ` font: inherit;`,
443
+ ` font-size: 12px;`,
444
+ ` cursor: pointer;`,
445
+ `}`,
446
+ `.hiwork-images-more:hover:not(:disabled) {`,
447
+ ` background: ${interactiveHover};`,
448
+ `}`
114
449
  ].join("\n");
115
450
  var CORE_STYLE_OWNER = "hiwork-core";
116
451
  function ownStyleTag(style, cssTagId) {
@@ -386,12 +721,12 @@ function openExternalUrl(url) {
386
721
  function openAuthorizeUrl(url) {
387
722
  openExternalUrl(url);
388
723
  }
389
- async function requestBrowserLogin(scope) {
390
- await scope.set("browserLoginRequest", { at: (/* @__PURE__ */ new Date()).toISOString() });
724
+ async function requestBrowserLogin(form) {
725
+ await form.set("browserLoginRequest", { at: (/* @__PURE__ */ new Date()).toISOString() });
391
726
  }
392
- async function clearBrowserLogin(scope) {
393
- await scope.unset("browserLoginRequest");
394
- await scope.unset("browserLogin");
727
+ async function clearBrowserLogin(form) {
728
+ await form.unset("browserLoginRequest");
729
+ await form.unset("browserLogin");
395
730
  }
396
731
  function project(value) {
397
732
  const base = {
@@ -419,8 +754,8 @@ function project(value) {
419
754
  return base;
420
755
  }
421
756
  var AiRouterCardController = class {
422
- constructor(scope, storeCredential) {
423
- this.scope = scope;
757
+ constructor(form, storeCredential) {
758
+ this.form = form;
424
759
  this.storeCredential = storeCredential;
425
760
  this.store = (0, import_dsh_client_store.createSnapshotStore)({
426
761
  status: "ready",
@@ -439,10 +774,10 @@ var AiRouterCardController = class {
439
774
  browserLoginPending: false
440
775
  });
441
776
  const publish = () => {
442
- const snapshot = this.scope.getSnapshot();
777
+ const snapshot = this.form.getSnapshot();
443
778
  if (snapshot.status === "ready") this.store.set(project(snapshot.value));
444
779
  };
445
- this.scope.subscribe(() => publish());
780
+ this.form.subscribe(() => publish());
446
781
  publish();
447
782
  }
448
783
  store;
@@ -462,24 +797,24 @@ var AiRouterCardController = class {
462
797
  }
463
798
  async submitLogin(username, password) {
464
799
  await this.storeCredential(PENDING_PASSWORD_REF, password);
465
- await this.scope.set("pendingLogin", { username, passwordRef: PENDING_PASSWORD_REF });
800
+ await this.form.set("pendingLogin", { username, passwordRef: PENDING_PASSWORD_REF });
466
801
  }
467
802
  /** 切换 Key:先落 keyId 配置,再触发一次刷新(Host 以新 keyId 重拉额度与模型)。 */
468
803
  async selectKey(keyId) {
469
- await this.scope.set("keyId", keyId);
470
- await this.scope.set("refreshRequest", { at: (/* @__PURE__ */ new Date()).toISOString() });
804
+ await this.form.set("keyId", keyId);
805
+ await this.form.set("refreshRequest", { at: (/* @__PURE__ */ new Date()).toISOString() });
471
806
  }
472
807
  async refresh() {
473
- await this.scope.set("refreshRequest", { at: (/* @__PURE__ */ new Date()).toISOString() });
808
+ await this.form.set("refreshRequest", { at: (/* @__PURE__ */ new Date()).toISOString() });
474
809
  }
475
810
  async logout() {
476
- await this.scope.set("logoutRequest", { at: (/* @__PURE__ */ new Date()).toISOString() });
811
+ await this.form.set("logoutRequest", { at: (/* @__PURE__ */ new Date()).toISOString() });
477
812
  }
478
813
  async startBrowserLogin() {
479
- await requestBrowserLogin(this.scope);
814
+ await requestBrowserLogin(this.form);
480
815
  }
481
816
  async cancelBrowserLogin() {
482
- await clearBrowserLogin(this.scope);
817
+ await clearBrowserLogin(this.form);
483
818
  }
484
819
  /** 重新打开授权页:URL 取自最近一次投影,缺省 no-op(登录门禁另有自动打开)。 */
485
820
  reopenBrowserLogin() {
@@ -1198,7 +1533,7 @@ function UserBadge(props) {
1198
1533
  title: "\u8BBE\u7F6E",
1199
1534
  "aria-label": "\u8BBE\u7F6E",
1200
1535
  onClick: openGatewaySettings,
1201
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives.IconSettingsOutline16, { size: 16 })
1536
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives.IconSettingsOutlineMedium, { size: 16 })
1202
1537
  }
1203
1538
  ),
1204
1539
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -1235,7 +1570,7 @@ var logo_with_text_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACSYA
1235
1570
  var HIWORK_LOGO = logo_default;
1236
1571
  var HIWORK_LOGO_TEXT = logo_text_default;
1237
1572
  var HIWORK_LOGO_WITH_TEXT = logo_with_text_default;
1238
- var HIWORK_VERSION = "1.0.4";
1573
+ var HIWORK_VERSION = "1.0.7";
1239
1574
 
1240
1575
  // src/client/brand.tsx
1241
1576
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -1321,13 +1656,123 @@ function createAppVersion(rpc) {
1321
1656
  };
1322
1657
  }
1323
1658
 
1659
+ // src/client/about-section.tsx
1660
+ var import_react4 = require("react");
1661
+
1662
+ // src/client/manual-link.ts
1663
+ var MANUAL_URL = "https://hiworkapp.hivery.cn/manual.html";
1664
+ var MANUAL_STYLE_ID = "hiwork-core-manual-style";
1665
+ var MANUAL_STYLE = `
1666
+ [data-hiwork-core-manual-entry]{box-sizing:border-box;flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin-left:auto;padding:0;border:0;border-radius:999px;background:transparent;color:var(--dsw-alias-label-tertiary,#71717a);font-family:inherit;font-size:14px;line-height:1;font-weight:600;cursor:pointer;user-select:none;transition:background .16s ease,color .16s ease}
1667
+ [data-hiwork-core-manual-entry]:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(24,24,27,.06));color:var(--dsw-alias-label-secondary,#3f3f46)}
1668
+ [data-hiwork-core-manual-entry]:focus-visible{outline:2px solid var(--dsw-alias-button-info-fill,#4c8dff);outline-offset:2px}
1669
+ body[data-ds-dark-theme] [data-hiwork-core-manual-entry]{color:var(--dsw-alias-label-tertiary,#9ba3b4)}
1670
+ body[data-ds-dark-theme] [data-hiwork-core-manual-entry]:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.08));color:var(--dsw-alias-label-secondary,#a6adb8)}
1671
+ @media (prefers-reduced-motion: reduce){[data-hiwork-core-manual-entry]{transition:none}}
1672
+ `;
1673
+ function ensureManualStyle(doc) {
1674
+ if (doc.getElementById(MANUAL_STYLE_ID) !== null) return;
1675
+ if (doc.head === null) return;
1676
+ const style = doc.createElement("style");
1677
+ style.id = MANUAL_STYLE_ID;
1678
+ ownStyleTag(style, "hiwork-core/manual-link.css");
1679
+ style.textContent = MANUAL_STYLE;
1680
+ doc.head.append(style);
1681
+ }
1682
+ function openManual() {
1683
+ openExternalUrl(MANUAL_URL);
1684
+ }
1685
+ function findManualMount(doc) {
1686
+ const hero = doc.querySelector('[data-phase="hero"]');
1687
+ if (hero === null) return null;
1688
+ return hero.querySelector('[class*="_heroWorkspaceRow"]');
1689
+ }
1690
+ function buildButton(doc) {
1691
+ const button = doc.createElement("button");
1692
+ button.dataset.hiworkCoreManualEntry = "";
1693
+ button.type = "button";
1694
+ button.title = "\u4F7F\u7528\u624B\u518C";
1695
+ button.setAttribute("aria-label", "\u4F7F\u7528\u624B\u518C");
1696
+ button.append(doc.createTextNode("?"));
1697
+ button.addEventListener("click", (event) => {
1698
+ event.preventDefault();
1699
+ event.stopPropagation();
1700
+ openManual();
1701
+ });
1702
+ return button;
1703
+ }
1704
+ function removeStaleManualEntry(doc) {
1705
+ for (const stale of Array.from(doc.querySelectorAll("[data-hiwork-core-manual-entry]"))) {
1706
+ stale.remove();
1707
+ }
1708
+ }
1709
+ function decorateManualEntry(doc) {
1710
+ const row = findManualMount(doc);
1711
+ if (row === null) {
1712
+ removeStaleManualEntry(doc);
1713
+ return;
1714
+ }
1715
+ const existing = row.querySelector(":scope > [data-hiwork-core-manual-entry]");
1716
+ if (existing !== null) return;
1717
+ removeStaleManualEntry(doc);
1718
+ row.append(buildButton(doc));
1719
+ }
1720
+
1324
1721
  // src/client/about-section.tsx
1325
1722
  var import_jsx_runtime4 = require("react/jsx-runtime");
1326
1723
  var KEYWORDS = ["\u667A\u80FD\u4F1A\u8BDD", "\u5C97\u4F4D\u667A\u80FD\u4F53", "\u6280\u80FD\u4E2D\u5FC3", "\u4F01\u4E1A IM \u96C6\u6210", "\u4F1A\u8BDD\u5F52\u6863", "\u5B9A\u65F6\u81EA\u52A8\u5316"];
1724
+ var WEBSITE_ORIGIN = new URL(MANUAL_URL).origin;
1725
+ var WEBSITE_HOST = new URL(MANUAL_URL).host;
1726
+ function versionClipboardText(version) {
1727
+ return `HiWork v${version}`;
1728
+ }
1729
+ async function copyVersionText(text6) {
1730
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
1731
+ if (clipboard === void 0 || typeof clipboard.writeText !== "function") return false;
1732
+ try {
1733
+ await clipboard.writeText(text6);
1734
+ return true;
1735
+ } catch {
1736
+ return false;
1737
+ }
1738
+ }
1739
+ var COPY_HINTS = { idle: "\u70B9\u51FB\u590D\u5236", copied: "\u5DF2\u590D\u5236", failed: "\u590D\u5236\u5931\u8D25" };
1740
+ var COPY_HINT_REVERT_MS = 1800;
1741
+ function ExternalLinkButton({ url, text: text6 }) {
1742
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1743
+ "button",
1744
+ {
1745
+ type: "button",
1746
+ "data-hiwork-core-linktext": true,
1747
+ style: linkValueStyle,
1748
+ title: `\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\uFF1A${url}`,
1749
+ onClick: () => {
1750
+ openExternalUrl(url);
1751
+ },
1752
+ children: [
1753
+ text6,
1754
+ " \u2197"
1755
+ ]
1756
+ }
1757
+ );
1758
+ }
1327
1759
  var sectionStyle2 = { color: "var(--dsw-alias-label-primary)" };
1328
1760
  var brandStyle = { display: "block", height: 32, width: "auto" };
1329
1761
  var taglineStyle = { margin: "12px 0 0", fontSize: 15, fontWeight: 600, lineHeight: "22px" };
1330
1762
  var subnoteStyle = { margin: "4px 0 0", color: "var(--dsw-alias-label-secondary)", fontSize: 12, lineHeight: "18px" };
1763
+ var versionRowStyle = { display: "flex", alignItems: "baseline", gap: 8, margin: "10px 0 0" };
1764
+ var versionValueStyle = {
1765
+ padding: 0,
1766
+ border: 0,
1767
+ background: "transparent",
1768
+ cursor: "pointer",
1769
+ font: "inherit",
1770
+ fontSize: 13,
1771
+ fontWeight: 600,
1772
+ lineHeight: "18px",
1773
+ color: textPrimary
1774
+ };
1775
+ var versionHintStyle = { color: textTertiary, fontSize: 11, lineHeight: "18px" };
1331
1776
  var dividerStyle = { margin: "24px 0 16px", border: 0, borderTop: "1px solid var(--dsw-alias-border-l2)" };
1332
1777
  var keywordsStyle = { display: "flex", flexWrap: "wrap", gap: 8, margin: 0, padding: 0, listStyle: "none" };
1333
1778
  var keywordStyle = {
@@ -1339,24 +1784,100 @@ var keywordStyle = {
1339
1784
  lineHeight: "16px"
1340
1785
  };
1341
1786
  var securityStyle = { margin: "20px 0 0", color: "var(--dsw-alias-label-tertiary)", fontSize: 12, lineHeight: "18px" };
1787
+ var metaListStyle = { display: "flex", flexDirection: "column", gap: 10, margin: 0 };
1788
+ var metaRowStyle = { display: "flex", alignItems: "baseline", gap: 12 };
1789
+ var metaLabelStyle = { flex: "none", width: "4em", color: textTertiary, fontSize: 12, lineHeight: "18px" };
1790
+ var metaValueStyle = { margin: 0, color: textSecondary, fontSize: 12, lineHeight: "18px", minWidth: 0 };
1791
+ var linkValueStyle = {
1792
+ padding: 0,
1793
+ border: 0,
1794
+ background: "transparent",
1795
+ cursor: "pointer",
1796
+ textAlign: "left",
1797
+ font: "inherit",
1798
+ fontSize: 12,
1799
+ lineHeight: "18px",
1800
+ color: brandAccent
1801
+ };
1342
1802
  var copyrightStyle = { margin: "8px 0 0", color: "var(--dsw-alias-label-tertiary)", fontSize: 12, lineHeight: "18px" };
1343
- function AboutSection(_props) {
1803
+ function AboutSection({ appVersion }) {
1804
+ const [version, setVersion] = (0, import_react4.useState)(void 0);
1805
+ const [copyHint, setCopyHint] = (0, import_react4.useState)("idle");
1806
+ const revertTimer = (0, import_react4.useRef)(void 0);
1807
+ (0, import_react4.useEffect)(() => {
1808
+ let alive = true;
1809
+ void appVersion.version().then(
1810
+ (value) => {
1811
+ if (alive) setVersion(value ?? null);
1812
+ },
1813
+ () => {
1814
+ if (alive) setVersion(null);
1815
+ }
1816
+ );
1817
+ return () => {
1818
+ alive = false;
1819
+ };
1820
+ }, [appVersion]);
1821
+ (0, import_react4.useEffect)(() => () => {
1822
+ if (revertTimer.current !== void 0) window.clearTimeout(revertTimer.current);
1823
+ }, []);
1824
+ const onCopyVersion = () => {
1825
+ if (version === void 0) return;
1826
+ void copyVersionText(versionClipboardText(version ?? HIWORK_VERSION)).then((ok) => {
1827
+ setCopyHint(ok ? "copied" : "failed");
1828
+ if (revertTimer.current !== void 0) window.clearTimeout(revertTimer.current);
1829
+ revertTimer.current = window.setTimeout(() => {
1830
+ setCopyHint("idle");
1831
+ }, COPY_HINT_REVERT_MS);
1832
+ });
1833
+ };
1344
1834
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { "aria-label": "\u5173\u4E8E", style: sectionStyle2, children: [
1345
1835
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { children: [
1346
1836
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: HIWORK_LOGO_WITH_TEXT, alt: "HiWork", className: "hiwork-brand-wordmark", style: brandStyle }),
1347
1837
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: taglineStyle, children: "\u667A\u8054\u4E07\u7269\uFF0C\u4ECE\u667A\u8054\u5DE5\u4F5C\u5F00\u59CB\u3002" }),
1348
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: subnoteStyle, children: "\u6E90\u81EA\u6D77\u666E HiCap \u7684\u4F01\u4E1A\u7EA7 AI \u5DE5\u4F5C\u53F0" })
1838
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: subnoteStyle, children: "\u6E90\u81EA\u6D77\u666E HiCap \u7684\u4F01\u4E1A\u7EA7 AI \u5DE5\u4F5C\u53F0" }),
1839
+ version === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: versionRowStyle, children: [
1840
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1841
+ "button",
1842
+ {
1843
+ type: "button",
1844
+ "data-hiwork-core-linktext": true,
1845
+ style: versionValueStyle,
1846
+ title: "\u70B9\u51FB\u590D\u5236\u7248\u672C\u4FE1\u606F",
1847
+ "aria-label": `\u590D\u5236\u7248\u672C\u4FE1\u606F v${version ?? HIWORK_VERSION}`,
1848
+ onClick: onCopyVersion,
1849
+ children: [
1850
+ "v",
1851
+ version ?? HIWORK_VERSION
1852
+ ]
1853
+ }
1854
+ ),
1855
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: versionHintStyle, children: COPY_HINTS[copyHint] })
1856
+ ] })
1349
1857
  ] }),
1350
1858
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("hr", { style: dividerStyle }),
1351
1859
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { style: keywordsStyle, children: KEYWORDS.map((keyword) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { style: keywordStyle, children: keyword }, keyword)) }),
1352
1860
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: securityStyle, children: "\u6570\u636E\u4E0D\u51FA\u57DF \xB7 \u4E0D\u7528\u4E8E\u8BAD\u7EC3 \xB7 \u5168\u94FE\u8DEF\u52A0\u5BC6" }),
1353
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: copyrightStyle, children: "\xA9 2026 \u6D77\u666E\u667A\u8054\u79D1\u6280\u80A1\u4EFD\u6709\u9650\u516C\u53F8 \xB7 \u4EC5\u9650\u5185\u90E8\u5458\u5DE5\u4F7F\u7528" })
1354
- ] });
1355
- }
1861
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("hr", { style: dividerStyle }),
1862
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dl", { style: metaListStyle, children: [
1863
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: metaRowStyle, children: [
1864
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { style: metaLabelStyle, children: "\u5B98\u65B9\u7F51\u7AD9" }),
1865
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { style: metaValueStyle, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExternalLinkButton, { url: WEBSITE_ORIGIN, text: WEBSITE_HOST }) })
1866
+ ] }),
1867
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: metaRowStyle, children: [
1868
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { style: metaLabelStyle, children: "\u4F7F\u7528\u624B\u518C" }),
1869
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { style: metaValueStyle, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExternalLinkButton, { url: MANUAL_URL, text: "\u5728\u7EBF\u67E5\u770B" }) })
1870
+ ] })
1871
+ ] }),
1872
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: copyrightStyle, children: "\xA9 2026 \u6D77\u666E\u667A\u8054\u79D1\u6280\u80A1\u4EFD\u6709\u9650\u516C\u53F8 \xB7 \u4EC5\u9650\u5185\u90E8\u5458\u5DE5\u4F7F\u7528" })
1873
+ ] });
1874
+ }
1356
1875
 
1357
1876
  // src/client/daily-data.ts
1358
1877
  var DAILY_CHANNEL = "/hiwork-daily";
1359
1878
  var DAILY_FAILURE_TTL_MS = 5 * 6e4;
1879
+ var DAILY_LOCAL_RETRY_MS = 2e3;
1880
+ var DAILY_LOCAL_FAILURE_CODES = ["SESSION_EXPIRED", "GATEWAY_UNREACHABLE"];
1360
1881
  var DailyRpcError = class extends Error {
1361
1882
  constructor(code, message) {
1362
1883
  super(message);
@@ -1428,27 +1949,38 @@ var DailyDataStore = class {
1428
1949
  this.now = options.now ?? (() => Date.now());
1429
1950
  this.localDay = options.localDay ?? (() => localDayIndex());
1430
1951
  this.failureTtlMs = options.failureTtlMs ?? DAILY_FAILURE_TTL_MS;
1952
+ this.localRetryMs = options.localRetryMs ?? DAILY_LOCAL_RETRY_MS;
1431
1953
  }
1432
1954
  entry;
1433
1955
  failureAt;
1956
+ /** 上一次失败是否为宿主本地前置失败({@link DAILY_LOCAL_FAILURE_CODES})——决定节流用哪个 TTL。 */
1957
+ localFailure = false;
1434
1958
  pending;
1435
1959
  now;
1436
1960
  localDay;
1437
1961
  failureTtlMs;
1962
+ localRetryMs;
1438
1963
  /** 清空缓存与防抖状态(登出 / 换账号时调用)。 */
1439
1964
  clear() {
1440
1965
  this.entry = void 0;
1441
1966
  this.failureAt = void 0;
1967
+ this.localFailure = false;
1442
1968
  this.pending = void 0;
1443
1969
  }
1444
1970
  /**
1445
- * 当日条目:同一本地日内命中缓存直接返回(不发 RPC);失败防抖期内返回 null 不
1446
- * 重复打网关;并发调用合并为同一次 RPC。返回 null 即「暂无可展示内容」。
1971
+ * 当日条目:同一本地日内命中缓存直接返回(不发 RPC);节流期内返回 null 不重复打网关;
1972
+ * 并发调用合并为同一次 RPC。返回 null 即「暂无可展示内容」。
1973
+ *
1974
+ * 节流时长分两档:宿主本地前置失败(未登录 / 网关未配置)只等 {@link DAILY_LOCAL_RETRY_MS},
1975
+ * 其余失败等 {@link DAILY_FAILURE_TTL_MS}——理由见两个常量的说明(首次安装必先启动后登录)。
1447
1976
  */
1448
1977
  async getToday() {
1449
1978
  const day = this.localDay();
1450
1979
  if (this.entry !== void 0 && this.entry.day === day) return this.entry.view;
1451
- if (this.failureAt !== void 0 && this.now() - this.failureAt < this.failureTtlMs) return null;
1980
+ if (this.failureAt !== void 0) {
1981
+ const ttl = this.localFailure ? this.localRetryMs : this.failureTtlMs;
1982
+ if (this.now() - this.failureAt < ttl) return null;
1983
+ }
1452
1984
  if (this.pending === void 0) {
1453
1985
  this.pending = this.fetchToday(day).finally(() => {
1454
1986
  this.pending = void 0;
@@ -1470,17 +2002,25 @@ var DailyDataStore = class {
1470
2002
  return null;
1471
2003
  }
1472
2004
  }
1473
- /** 单次 today 拉取:成功(含池空)写缓存;任何失败进入 5 分钟防抖并返回 null。 */
2005
+ /**
2006
+ * 单次 today 拉取:成功(含池空)写缓存;任何失败进入节流并返回 null。
2007
+ *
2008
+ * 失败分两档记:{@link DAILY_LOCAL_FAILURE_CODES} 是宿主在打网之前判定的(无网关流量、登录后必然变化),
2009
+ * 只记短节流;其余(网关透传失败、形状非法、未知错误)记满 {@link DAILY_FAILURE_TTL_MS}。
2010
+ */
1474
2011
  async fetchToday(day) {
1475
2012
  try {
1476
2013
  const raw = await this.rpc.call(DAILY_CHANNEL, "today", {});
1477
2014
  const response = parseTodayResponse(unwrapDailyResult(raw));
1478
2015
  if (response === void 0) throw new DailyRpcError("internal", "\u6BCF\u65E5\u4E00\u53E5\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
1479
2016
  this.failureAt = void 0;
2017
+ this.localFailure = false;
1480
2018
  const view = response.item === null ? null : { ...response.item, date: response.date };
1481
2019
  this.entry = { view, day };
1482
2020
  return view;
1483
- } catch {
2021
+ } catch (error) {
2022
+ const code = error instanceof DailyRpcError ? error.code : "";
2023
+ this.localFailure = DAILY_LOCAL_FAILURE_CODES.includes(code);
1484
2024
  this.failureAt = this.now();
1485
2025
  return null;
1486
2026
  }
@@ -1551,65 +2091,6 @@ function removeStaleHeroGreeting(doc) {
1551
2091
  for (const stale of Array.from(doc.querySelectorAll("[data-hiwork-hero-greeting]"))) stale.remove();
1552
2092
  }
1553
2093
 
1554
- // src/client/manual-link.ts
1555
- var MANUAL_URL = "https://hiworkapp.hivery.cn/manual.html";
1556
- var MANUAL_STYLE_ID = "hiwork-core-manual-style";
1557
- var MANUAL_STYLE = `
1558
- [data-hiwork-core-manual-entry]{box-sizing:border-box;flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin-left:auto;padding:0;border:0;border-radius:999px;background:transparent;color:var(--dsw-alias-label-tertiary,#71717a);font-family:inherit;font-size:14px;line-height:1;font-weight:600;cursor:pointer;user-select:none;transition:background .16s ease,color .16s ease}
1559
- [data-hiwork-core-manual-entry]:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(24,24,27,.06));color:var(--dsw-alias-label-secondary,#3f3f46)}
1560
- [data-hiwork-core-manual-entry]:focus-visible{outline:2px solid var(--dsw-alias-button-info-fill,#4c8dff);outline-offset:2px}
1561
- body[data-ds-dark-theme] [data-hiwork-core-manual-entry]{color:var(--dsw-alias-label-tertiary,#9ba3b4)}
1562
- body[data-ds-dark-theme] [data-hiwork-core-manual-entry]:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.08));color:var(--dsw-alias-label-secondary,#a6adb8)}
1563
- @media (prefers-reduced-motion: reduce){[data-hiwork-core-manual-entry]{transition:none}}
1564
- `;
1565
- function ensureManualStyle(doc) {
1566
- if (doc.getElementById(MANUAL_STYLE_ID) !== null) return;
1567
- if (doc.head === null) return;
1568
- const style = doc.createElement("style");
1569
- style.id = MANUAL_STYLE_ID;
1570
- ownStyleTag(style, "hiwork-core/manual-link.css");
1571
- style.textContent = MANUAL_STYLE;
1572
- doc.head.append(style);
1573
- }
1574
- function openManual() {
1575
- openExternalUrl(MANUAL_URL);
1576
- }
1577
- function findManualMount(doc) {
1578
- const hero = doc.querySelector('[data-phase="hero"]');
1579
- if (hero === null) return null;
1580
- return hero.querySelector('[class*="_heroWorkspaceRow"]');
1581
- }
1582
- function buildButton(doc) {
1583
- const button = doc.createElement("button");
1584
- button.dataset.hiworkCoreManualEntry = "";
1585
- button.type = "button";
1586
- button.title = "\u4F7F\u7528\u624B\u518C";
1587
- button.setAttribute("aria-label", "\u4F7F\u7528\u624B\u518C");
1588
- button.append(doc.createTextNode("?"));
1589
- button.addEventListener("click", (event) => {
1590
- event.preventDefault();
1591
- event.stopPropagation();
1592
- openManual();
1593
- });
1594
- return button;
1595
- }
1596
- function removeStaleManualEntry(doc) {
1597
- for (const stale of Array.from(doc.querySelectorAll("[data-hiwork-core-manual-entry]"))) {
1598
- stale.remove();
1599
- }
1600
- }
1601
- function decorateManualEntry(doc) {
1602
- const row = findManualMount(doc);
1603
- if (row === null) {
1604
- removeStaleManualEntry(doc);
1605
- return;
1606
- }
1607
- const existing = row.querySelector(":scope > [data-hiwork-core-manual-entry]");
1608
- if (existing !== null) return;
1609
- removeStaleManualEntry(doc);
1610
- row.append(buildButton(doc));
1611
- }
1612
-
1613
2094
  // src/client/daily-card.ts
1614
2095
  var DAILY_CARD_STYLE_ID = "dcu-daily-card-style";
1615
2096
  var DAILY_MAIN_FONT_SIZE_MAX = 64;
@@ -1991,7 +2472,7 @@ function observeAboutNavIcon(doc = document) {
1991
2472
  }
1992
2473
 
1993
2474
  // src/client/login-gate.tsx
1994
- var import_react4 = require("react");
2475
+ var import_react5 = require("react");
1995
2476
  var import_client = require("react-dom/client");
1996
2477
  var import_jsx_runtime5 = require("react/jsx-runtime");
1997
2478
  var INK = "#111b2f";
@@ -2105,12 +2586,12 @@ function loginGateVisible(snapshot) {
2105
2586
  return snapshot.value?.lastLogin === void 0 || errorCode === "SESSION_EXPIRED";
2106
2587
  }
2107
2588
  function LoginGateModal(props) {
2108
- const [username, setUsername] = (0, import_react4.useState)(props.initialUsername ?? "");
2109
- const [password, setPassword] = (0, import_react4.useState)("");
2110
- const [rememberPassword, setRememberPassword] = (0, import_react4.useState)(props.initialRememberPassword === true);
2111
- const [submitError, setSubmitError] = (0, import_react4.useState)();
2112
- const [formOpen, setFormOpen] = (0, import_react4.useState)(false);
2113
- (0, import_react4.useEffect)(() => {
2589
+ const [username, setUsername] = (0, import_react5.useState)(props.initialUsername ?? "");
2590
+ const [password, setPassword] = (0, import_react5.useState)("");
2591
+ const [rememberPassword, setRememberPassword] = (0, import_react5.useState)(props.initialRememberPassword === true);
2592
+ const [submitError, setSubmitError] = (0, import_react5.useState)();
2593
+ const [formOpen, setFormOpen] = (0, import_react5.useState)(false);
2594
+ (0, import_react5.useEffect)(() => {
2114
2595
  openAuthorizeUrl(props.browserLoginUrl);
2115
2596
  }, [props.browserLoginUrl]);
2116
2597
  const showBrowserLogin = props.loginMode !== "form";
@@ -2123,7 +2604,7 @@ function LoginGateModal(props) {
2123
2604
  const submit = () => {
2124
2605
  if (loginDisabled) return;
2125
2606
  setSubmitError(void 0);
2126
- void props.storeCredential(PENDING_PASSWORD_REF, password).then(() => props.scope.set("rememberPassword", rememberPassword)).then(() => props.scope.set("pendingLogin", { username, passwordRef: PENDING_PASSWORD_REF })).catch(() => setSubmitError("\u767B\u5F55\u8BF7\u6C42\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"));
2607
+ void props.storeCredential(PENDING_PASSWORD_REF, password).then(() => props.form.set("rememberPassword", rememberPassword)).then(() => props.form.set("pendingLogin", { username, passwordRef: PENDING_PASSWORD_REF })).catch(() => setSubmitError("\u767B\u5F55\u8BF7\u6C42\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"));
2127
2608
  };
2128
2609
  const onSubmit = (event) => {
2129
2610
  event.preventDefault();
@@ -2134,9 +2615,9 @@ function LoginGateModal(props) {
2134
2615
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: browserHintStyle, children: "\u5DF2\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u6388\u6743\u9875\uFF0C\u5B8C\u6210\u767B\u5F55\u540E\u672C\u7A97\u53E3\u4F1A\u81EA\u52A8\u7EE7\u7EED\u3002" }),
2135
2616
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: browserActionsStyle, children: [
2136
2617
  props.browserLoginUrl === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, "data-hiwork-core-browser-reopen": true, type: "button", style: linkButton, onClick: () => openAuthorizeUrl(props.browserLoginUrl), children: "\u91CD\u65B0\u6253\u5F00\u6388\u6743\u9875" }),
2137
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, "data-hiwork-core-browser-cancel": true, type: "button", style: linkButton, onClick: () => void clearBrowserLogin(props.scope), children: "\u53D6\u6D88" })
2618
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, "data-hiwork-core-browser-cancel": true, type: "button", style: linkButton, onClick: () => void clearBrowserLogin(props.form), children: "\u53D6\u6D88" })
2138
2619
  ] })
2139
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-brand": true, "data-hiwork-core-browser-login": true, type: "button", style: props.loggingIn ? loginButtonDisabled : loginButton, disabled: props.loggingIn, onClick: () => void requestBrowserLogin(props.scope), children: "\u4F7F\u7528\u6D4F\u89C8\u5668\u767B\u5F55" }) : null;
2620
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-brand": true, "data-hiwork-core-browser-login": true, type: "button", style: props.loggingIn ? loginButtonDisabled : loginButton, disabled: props.loggingIn, onClick: () => void requestBrowserLogin(props.form), children: "\u4F7F\u7528\u6D4F\u89C8\u5668\u767B\u5F55" }) : null;
2140
2621
  const form = formVisible ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit, style: { display: "flex", flexDirection: "column", gap: 14 }, children: [
2141
2622
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: fieldStyle, children: [
2142
2623
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { htmlFor: "hiwork-core-username", style: labelStyle, children: "\u7528\u6237\u540D" }),
@@ -2151,7 +2632,7 @@ function LoginGateModal(props) {
2151
2632
  "\u8BB0\u4F4F\u5BC6\u7801\uFF0C\u4E0B\u6B21\u81EA\u52A8\u767B\u5F55"
2152
2633
  ] }),
2153
2634
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-brand": true, type: "submit", style: loginDisabled ? loginButtonDisabled : loginButton, disabled: loginDisabled, children: props.loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B \u5F55" }),
2154
- noKey ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, type: "button", style: linkButton, onClick: () => void props.scope.set("refreshRequest", { at: (/* @__PURE__ */ new Date()).toISOString() }), children: "\u6211\u5DF2\u5206\u914D KEY\uFF0C\u5237\u65B0" }) : null
2635
+ noKey ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, type: "button", style: linkButton, onClick: () => void props.form.set("refreshRequest", { at: (/* @__PURE__ */ new Date()).toISOString() }), children: "\u6211\u5DF2\u5206\u914D KEY\uFF0C\u5237\u65B0" }) : null
2155
2636
  ] }) : !passwordFormOffered ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, "data-hiwork-core-form-toggle": true, type: "button", style: toggleStyle, onClick: () => setFormOpen(true), children: props.loginMode === "browser" ? "\u6D4F\u89C8\u5668\u767B\u5F55\u4E0D\u53EF\u7528\uFF1F\u4F7F\u7528\u8D26\u53F7\u5BC6\u7801\u767B\u5F55" : "\u4F7F\u7528\u8D26\u53F7\u5BC6\u7801\u767B\u5F55" });
2156
2637
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: overlayStyle, children: [
2157
2638
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OceanWaves, {}),
@@ -2192,7 +2673,7 @@ function LoginGateModal(props) {
2192
2673
  ] })
2193
2674
  ] });
2194
2675
  }
2195
- function mountLoginGate(scope, storeCredential) {
2676
+ function mountLoginGate(form, storeCredential) {
2196
2677
  let root;
2197
2678
  let gateContainer;
2198
2679
  const appRoot = typeof document === "undefined" ? null : document.getElementById("root");
@@ -2208,7 +2689,7 @@ function mountLoginGate(scope, storeCredential) {
2208
2689
  markReady();
2209
2690
  };
2210
2691
  const render = () => {
2211
- const snapshot = scope.getSnapshot();
2692
+ const snapshot = form.getSnapshot();
2212
2693
  if (!loginGateVisible(snapshot)) {
2213
2694
  unmount();
2214
2695
  return;
@@ -2222,10 +2703,10 @@ function mountLoginGate(scope, storeCredential) {
2222
2703
  root = (0, import_client.createRoot)(gateContainer);
2223
2704
  if (appRoot !== null) appRoot.inert = true;
2224
2705
  }
2225
- root.render(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LoginGateModal, { scope, storeCredential, lastError: value?.lastError, loggingIn: value?.pendingLogin !== void 0, initialUsername: value?.username, initialRememberPassword: value?.rememberPassword === true, passwordLogin: passwordLoginOf(value), ...browserLoginViewOf(value) }));
2706
+ root.render(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LoginGateModal, { form, storeCredential, lastError: value?.lastError, loggingIn: value?.pendingLogin !== void 0, initialUsername: value?.username, initialRememberPassword: value?.rememberPassword === true, passwordLogin: passwordLoginOf(value), ...browserLoginViewOf(value) }));
2226
2707
  };
2227
2708
  render();
2228
- const dispose = scope.subscribe(() => render());
2709
+ const dispose = form.subscribe(() => render());
2229
2710
  return () => {
2230
2711
  dispose();
2231
2712
  unmount();
@@ -2233,7 +2714,7 @@ function mountLoginGate(scope, storeCredential) {
2233
2714
  }
2234
2715
 
2235
2716
  // src/client/usage-section.tsx
2236
- var import_react5 = require("react");
2717
+ var import_react6 = require("react");
2237
2718
 
2238
2719
  // src/client/usage-data.ts
2239
2720
  var USAGE_CHANNEL = "/hiwork-usage";
@@ -2565,16 +3046,16 @@ function RatioRow({ name, value, pct }) {
2565
3046
  }
2566
3047
  function UsageSection(props) {
2567
3048
  const card = props.useAiRouterCard((snapshot) => snapshot);
2568
- const [selectedKeyId, setSelectedKeyId] = (0, import_react5.useState)(void 0);
2569
- const [result, setResult] = (0, import_react5.useState)(void 0);
2570
- const [loading, setLoading] = (0, import_react5.useState)(true);
2571
- const alive = (0, import_react5.useRef)(true);
2572
- const ticket = (0, import_react5.useRef)(0);
3049
+ const [selectedKeyId, setSelectedKeyId] = (0, import_react6.useState)(void 0);
3050
+ const [result, setResult] = (0, import_react6.useState)(void 0);
3051
+ const [loading, setLoading] = (0, import_react6.useState)(true);
3052
+ const alive = (0, import_react6.useRef)(true);
3053
+ const ticket = (0, import_react6.useRef)(0);
2573
3054
  const settingsKeyId = card.activeKeyId;
2574
3055
  const requestKeyId = selectedKeyId ?? settingsKeyId;
2575
3056
  const displayKeyId = requestKeyId ?? result?.data.keys?.[0]?.id;
2576
3057
  const loader = props.usage;
2577
- const load = (0, import_react5.useCallback)(async (force) => {
3058
+ const load = (0, import_react6.useCallback)(async (force) => {
2578
3059
  const current = ticket.current + 1;
2579
3060
  ticket.current = current;
2580
3061
  setLoading(true);
@@ -2587,13 +3068,13 @@ function UsageSection(props) {
2587
3068
  setResult(next);
2588
3069
  setLoading(false);
2589
3070
  }, [loader, requestKeyId]);
2590
- (0, import_react5.useEffect)(() => {
3071
+ (0, import_react6.useEffect)(() => {
2591
3072
  alive.current = true;
2592
3073
  return () => {
2593
3074
  alive.current = false;
2594
3075
  };
2595
3076
  }, []);
2596
- (0, import_react5.useEffect)(() => {
3077
+ (0, import_react6.useEffect)(() => {
2597
3078
  void load(false);
2598
3079
  }, [load]);
2599
3080
  const data = result?.data;
@@ -2732,247 +3213,229 @@ function UsageSection(props) {
2732
3213
  ] });
2733
3214
  }
2734
3215
 
2735
- // src/client/session-cost.tsx
2736
- var import_react6 = require("react");
2737
- var import_jsx_runtime7 = require("react/jsx-runtime");
2738
- var SESSION_COST_ENDPOINT = "estimate";
2739
- var SESSION_COST_CACHE_TTL_MS = 5 * 6e4;
2740
- var SESSION_COST_HINT = "\u6309\u7F51\u5173\u4EF7\u683C\u4F30\u7B97\uFF0C\u4EC5\u4F9B\u53C2\u8003\uFF1B\u4EE5\u300C\u7528\u91CF\u4E0E\u6210\u672C\u300D\u4E2D\u7684\u6743\u5A01\u8D26\u5355\u4E3A\u51C6";
2741
- var SESSION_COST_ESTIMATED_SUFFIX = "\uFF08\u4F30\u7B97\uFF09";
2742
- function nonNegativeInteger(value) {
2743
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
3216
+ // src/client/menu-section.tsx
3217
+ var import_react8 = require("react");
3218
+
3219
+ // src/client/feature-center.ts
3220
+ var import_react7 = require("react");
3221
+ var import_client2 = require("react-dom/client");
3222
+
3223
+ // src/client/feature-registry.ts
3224
+ var DEFAULT_FEATURE_ORDER = 1e3;
3225
+ var DEFAULT_FEATURE_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.84457 5.06199C11.6605 4.93876 14.7962 6.14848 14.8484 7.76397C14.8875 8.97461 13.1838 10.0696 10.7215 10.5942" stroke="currentColor" stroke-width="1.2"></path><path d="M5.12742 8.07731C5.00419 4.26138 6.21391 1.12568 7.8294 1.07351C9.04004 1.03441 10.135 2.73808 10.6596 5.20037" stroke="currentColor" stroke-width="1.2"></path><path d="M8.02457 10.6802C4.20865 10.8034 1.07294 9.5937 1.02077 7.97821C0.981678 6.76758 2.68535 5.67262 5.14763 5.14798" stroke="currentColor" stroke-width="1.2"></path><path d="M10.7476 7.89535C10.8708 11.7113 9.66109 14.847 8.0456 14.8991C6.83496 14.9382 5.74 13.2346 5.21536 10.7723" stroke="currentColor" stroke-width="1.2"></path></svg>';
3226
+ var NATIVE_PLUGINS_MENU_ID = "__native_plugins__";
3227
+ var NATIVE_PLUGINS_MENU_LABEL = "\u63D2\u4EF6";
3228
+ var NATIVE_PLUGINS_MENU_ORDER = 70;
3229
+ function nativePluginsMenuItem() {
3230
+ return {
3231
+ id: NATIVE_PLUGINS_MENU_ID,
3232
+ label: () => NATIVE_PLUGINS_MENU_LABEL,
3233
+ icon: DEFAULT_FEATURE_ICON,
3234
+ order: NATIVE_PLUGINS_MENU_ORDER
3235
+ };
2744
3236
  }
2745
- function sessionCostMatchOf(data) {
2746
- if (typeof data !== "object" || data === null) return null;
2747
- const turn = nonNegativeInteger(data.turn);
2748
- if (turn === void 0) return null;
2749
- const usage = data.tokenUsage;
2750
- if (typeof usage !== "object" || usage === null) return null;
2751
- const input = nonNegativeInteger(usage.uncachedInputTokens);
2752
- const output = nonNegativeInteger(usage.outputTokens);
2753
- if (input === void 0 || output === void 0) return null;
2754
- const routes = usage.routes;
2755
- if (!Array.isArray(routes) || routes.length === 0) return null;
2756
- const pairs = [];
3237
+ function compareFeatures(left, right) {
3238
+ const leftOrder = left.order ?? DEFAULT_FEATURE_ORDER;
3239
+ const rightOrder = right.order ?? DEFAULT_FEATURE_ORDER;
3240
+ if (leftOrder !== rightOrder) return leftOrder - rightOrder;
3241
+ if (left.id === right.id) return 0;
3242
+ return left.id < right.id ? -1 : 1;
3243
+ }
3244
+ function createFeatureRegistry() {
3245
+ const features = /* @__PURE__ */ new Map();
3246
+ const listeners = /* @__PURE__ */ new Set();
3247
+ let activeId = null;
3248
+ const notify = () => {
3249
+ for (const listener of [...listeners]) {
3250
+ try {
3251
+ listener();
3252
+ } catch (error) {
3253
+ console.error("hiwork feature center subscriber failed:", error);
3254
+ }
3255
+ }
3256
+ };
3257
+ return {
3258
+ register(feature) {
3259
+ const id = feature.id.trim();
3260
+ if (id === "") throw new Error("hiwork feature id must not be blank");
3261
+ if (features.has(id)) throw new Error(`hiwork feature "${id}" is already registered`);
3262
+ const entry = { ...feature, id };
3263
+ features.set(id, entry);
3264
+ notify();
3265
+ let disposed = false;
3266
+ return () => {
3267
+ if (disposed) return;
3268
+ disposed = true;
3269
+ if (features.get(id) !== entry) return;
3270
+ features.delete(id);
3271
+ if (activeId === id) activeId = null;
3272
+ notify();
3273
+ };
3274
+ },
3275
+ list() {
3276
+ return [...features.values()].sort(compareFeatures);
3277
+ },
3278
+ activeId() {
3279
+ return activeId;
3280
+ },
3281
+ open(id) {
3282
+ if (!features.has(id)) return false;
3283
+ if (activeId === id) return true;
3284
+ activeId = id;
3285
+ notify();
3286
+ return true;
3287
+ },
3288
+ openNative() {
3289
+ if (activeId === null) return;
3290
+ activeId = null;
3291
+ notify();
3292
+ },
3293
+ subscribe(listener) {
3294
+ listeners.add(listener);
3295
+ return () => {
3296
+ listeners.delete(listener);
3297
+ };
3298
+ }
3299
+ };
3300
+ }
3301
+
3302
+ // src/client/menu-preferences.ts
3303
+ var HIWORK_MENU_NS = "hiwork-menu";
3304
+ var DEFAULT_SIDEBAR_LIMIT = 5;
3305
+ var MORE_MENU_ITEM_ID = "__hiwork_more__";
3306
+ var MORE_MENU_LABEL = "\u66F4\u591A";
3307
+ var MENU_SECTION_LABEL = "\u4FA7\u680F\u83DC\u5355";
3308
+ var MENU_PLACEMENTS = ["sidebar", "more", "hidden"];
3309
+ var EMPTY_MENU_PREFERENCES = { order: [], sidebar: [], more: [], hidden: [] };
3310
+ function uniqueIds(value) {
2757
3311
  const seen = /* @__PURE__ */ new Set();
2758
- for (const entry of routes) {
2759
- if (typeof entry !== "object" || entry === null) return null;
2760
- const { provider, model } = entry;
2761
- if (typeof provider !== "string" || provider === "" || typeof model !== "string" || model === "") return null;
2762
- const key = `${provider}\0${model}`;
2763
- if (seen.has(key)) continue;
2764
- seen.add(key);
2765
- pairs.push({ route: provider, model });
2766
- }
2767
- if (pairs.length === 0) return null;
2768
- const cacheRead = nonNegativeInteger(usage.cacheReadTokens);
2769
- const cacheWrite = nonNegativeInteger(usage.cacheWriteTokens);
2770
- if (input === 0 && output === 0 && (cacheRead ?? 0) === 0 && (cacheWrite ?? 0) === 0) return null;
2771
- const first = pairs[0];
2772
- if (first === void 0) return null;
2773
- const at = rfc3339Of(data.time);
2774
- const item = {
2775
- route: first.route,
2776
- model: first.model,
2777
- input_tokens: input,
2778
- output_tokens: output,
2779
- // 桶缺失表示「并非每次尝试都上报了该桶」——原样省略,绝不猜 0。
2780
- ...cacheRead === void 0 ? {} : { cache_read_tokens: cacheRead },
2781
- ...cacheWrite === void 0 ? {} : { cache_write_tokens: cacheWrite },
2782
- ...at === void 0 ? {} : { at }
3312
+ const result = [];
3313
+ for (const item of value) {
3314
+ if (typeof item !== "string") continue;
3315
+ const id = item.trim();
3316
+ if (id === "" || seen.has(id)) continue;
3317
+ seen.add(id);
3318
+ result.push(id);
3319
+ }
3320
+ return result;
3321
+ }
3322
+ function idList(value) {
3323
+ return Array.isArray(value) ? uniqueIds(value.filter((item) => typeof item === "string")) : [];
3324
+ }
3325
+ function normalizeMenuPreferences(raw) {
3326
+ if (raw === null || typeof raw !== "object") return EMPTY_MENU_PREFERENCES;
3327
+ const value = raw;
3328
+ return {
3329
+ order: idList(value.order),
3330
+ sidebar: idList(value.sidebar),
3331
+ more: idList(value.more),
3332
+ hidden: idList(value.hidden)
2783
3333
  };
2784
- const ambiguous = pairs.length > 1 || cacheRead === void 0 || cacheWrite === void 0;
2785
- return { items: [item], turn, ambiguous };
2786
3334
  }
2787
- function rfc3339Of(time) {
2788
- if (typeof time !== "number" || !Number.isFinite(time) || time <= 0) return void 0;
2789
- const date = new Date(time);
2790
- return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
3335
+ function placementOf(preferences, id) {
3336
+ if (preferences.hidden.includes(id)) return "hidden";
3337
+ if (preferences.more.includes(id)) return "more";
3338
+ if (preferences.sidebar.includes(id)) return "sidebar";
3339
+ return void 0;
2791
3340
  }
2792
- function closingTurnTailData(snapshot, messageId) {
2793
- for (const key of snapshot.order) {
2794
- const node = snapshot.nodes.get(key);
2795
- if (node === void 0 || node.kind !== "turn-tail") continue;
2796
- const data = node.data;
2797
- if (data === void 0) continue;
2798
- if (data.closing?.finalNode?.messageId === messageId) return data;
3341
+ function compareFeatures2(left, right) {
3342
+ const leftOrder = left.order ?? DEFAULT_FEATURE_ORDER;
3343
+ const rightOrder = right.order ?? DEFAULT_FEATURE_ORDER;
3344
+ if (leftOrder !== rightOrder) return leftOrder - rightOrder;
3345
+ if (left.id === right.id) return 0;
3346
+ return left.id < right.id ? -1 : 1;
3347
+ }
3348
+ function orderedFeatureIds(features, preferences) {
3349
+ const known = new Set(features.map((feature) => feature.id));
3350
+ const result = [];
3351
+ const seen = /* @__PURE__ */ new Set();
3352
+ for (const id of preferences.order) {
3353
+ if (!known.has(id) || seen.has(id)) continue;
3354
+ seen.add(id);
3355
+ result.push(id);
3356
+ }
3357
+ for (const feature of [...features].sort(compareFeatures2)) {
3358
+ if (seen.has(feature.id)) continue;
3359
+ seen.add(feature.id);
3360
+ result.push(feature.id);
3361
+ }
3362
+ return result;
3363
+ }
3364
+ function menuLayout(features, preferences) {
3365
+ const hidden = new Set(preferences.hidden);
3366
+ const explicitMore = new Set(preferences.more.filter((id) => !hidden.has(id)));
3367
+ const explicitSidebar = new Set(preferences.sidebar.filter((id) => !hidden.has(id) && !explicitMore.has(id)));
3368
+ const byId = new Map(features.map((feature) => [feature.id, feature]));
3369
+ const markedMore = [...explicitMore].filter((id) => byId.has(id)).length;
3370
+ const budget = Math.max(0, DEFAULT_SIDEBAR_LIMIT - markedMore);
3371
+ const sidebar = [];
3372
+ const more = [];
3373
+ for (const id of orderedFeatureIds(features, preferences)) {
3374
+ const feature = byId.get(id);
3375
+ if (feature === void 0 || hidden.has(id)) continue;
3376
+ if (explicitSidebar.has(id)) {
3377
+ sidebar.push(feature);
3378
+ continue;
3379
+ }
3380
+ if (explicitMore.has(id) || sidebar.length >= budget) {
3381
+ more.push(feature);
3382
+ continue;
3383
+ }
3384
+ sidebar.push(feature);
2799
3385
  }
2800
- return void 0;
3386
+ return { sidebar, more };
2801
3387
  }
2802
- function sessionCostKey(sessionId, match) {
2803
- return `${sessionId}\0${match.turn}\0${JSON.stringify(match.items)}`;
3388
+ function withoutPlacement(preferences, id) {
3389
+ return {
3390
+ order: preferences.order,
3391
+ sidebar: preferences.sidebar.filter((item) => item !== id),
3392
+ more: preferences.more.filter((item) => item !== id),
3393
+ hidden: preferences.hidden.filter((item) => item !== id)
3394
+ };
2804
3395
  }
2805
- function normalizeSessionCost(value) {
2806
- if (typeof value !== "object" || value === null) return void 0;
2807
- const record = value;
2808
- if (record.priced !== true) return void 0;
2809
- const cost = record.cost;
2810
- if (typeof cost !== "number" || !Number.isFinite(cost) || cost < 0) return void 0;
2811
- return { cost, channelResolved: record.channelResolved === true };
2812
- }
2813
- var SessionCostStore = class {
2814
- constructor(rpc, options = {}) {
2815
- this.rpc = rpc;
2816
- this.now = options.now ?? (() => Date.now());
2817
- this.ttlMs = options.ttlMs ?? SESSION_COST_CACHE_TTL_MS;
2818
- }
2819
- entries = /* @__PURE__ */ new Map();
2820
- pending = /* @__PURE__ */ new Map();
2821
- now;
2822
- ttlMs;
2823
- /** 当前缓存条目数(测试断言用)。 */
2824
- get size() {
2825
- return this.entries.size;
2826
- }
2827
- /** 清空缓存(登出 / 切换账号)。 */
2828
- clear() {
2829
- this.entries.clear();
2830
- }
2831
- async estimate(input) {
2832
- const key = sessionCostKey(input.sessionId, { turn: input.turn, items: [...input.items] });
2833
- const cached = this.entries.get(key);
2834
- if (cached !== void 0 && this.now() - cached.fetchedAt < this.ttlMs) return cached.result;
2835
- const inflight = this.pending.get(key);
2836
- if (inflight !== void 0) return await inflight;
2837
- const task = this.request(key, input);
2838
- this.pending.set(key, task);
2839
- try {
2840
- return await task;
2841
- } finally {
2842
- this.pending.delete(key);
2843
- }
2844
- }
2845
- async request(key, input) {
2846
- try {
2847
- const raw = await this.rpc.call(USAGE_CHANNEL, SESSION_COST_ENDPOINT, { items: input.items }, input.signal);
2848
- const result = normalizeSessionCost(unwrapUsageResult(raw));
2849
- if (result !== void 0) this.entries.set(key, { result, fetchedAt: this.now() });
2850
- return result;
2851
- } catch {
2852
- return void 0;
2853
- }
2854
- }
2855
- };
2856
- var costStyle = {
2857
- // 字号/颜色与同一行的「用量」「用时」胶囊同源变量,保证在动作行里不显得突兀。
2858
- color: textTertiary,
2859
- fontSize: "var(--dsh-content-font-size-secondary, 13px)",
2860
- lineHeight: "18px",
2861
- whiteSpace: "nowrap",
2862
- cursor: "default"
2863
- };
2864
- function SessionCost({ messageId, sessionId, useChat, store }) {
2865
- const data = useChat((snapshot) => closingTurnTailData(snapshot, messageId));
2866
- const match = sessionCostMatchOf(data);
2867
- const key = match === null ? null : sessionCostKey(sessionId, match);
2868
- const [shown, setShown] = (0, import_react6.useState)(void 0);
2869
- (0, import_react6.useEffect)(() => {
2870
- if (match === null || key === null) return;
2871
- let disposed = false;
2872
- const controller = new AbortController();
2873
- void store.estimate({ sessionId, turn: match.turn, items: match.items, signal: controller.signal }).then((value) => {
2874
- if (!disposed) setShown({ key, result: value });
2875
- }).catch(() => {
2876
- if (!disposed) setShown({ key, result: void 0 });
2877
- });
2878
- return () => {
2879
- disposed = true;
2880
- controller.abort();
2881
- };
2882
- }, [store, key]);
2883
- if (match === null || key === null || shown === void 0 || shown.key !== key || shown.result === void 0) return null;
2884
- const result = shown.result;
2885
- const estimated = !result.channelResolved || match.ambiguous;
2886
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: costStyle, "data-hiwork-session-cost": estimated ? "estimated" : "exact", title: SESSION_COST_HINT, children: `\u2248 ${formatYuan(result.cost)}${estimated ? SESSION_COST_ESTIMATED_SUFFIX : ""}` });
2887
- }
2888
-
2889
- // src/client/account-cache.ts
2890
- function accountIdentityOf(state) {
2891
- return `${state.username ?? ""}\0${state.activeKeyId ?? ""}`;
2892
- }
2893
- function observeAccountChange(source, onChange) {
2894
- let identity = accountIdentityOf(source.getSnapshot());
2895
- const unsubscribe = source.subscribe(() => {
2896
- const next = accountIdentityOf(source.getSnapshot());
2897
- if (next === identity) return;
2898
- identity = next;
2899
- onChange();
2900
- });
2901
- return () => {
2902
- unsubscribe();
2903
- };
2904
- }
2905
-
2906
- // src/client/feature-registry.ts
2907
- var DEFAULT_FEATURE_ORDER = 1e3;
2908
- function compareFeatures(left, right) {
2909
- const leftOrder = left.order ?? DEFAULT_FEATURE_ORDER;
2910
- const rightOrder = right.order ?? DEFAULT_FEATURE_ORDER;
2911
- if (leftOrder !== rightOrder) return leftOrder - rightOrder;
2912
- if (left.id === right.id) return 0;
2913
- return left.id < right.id ? -1 : 1;
2914
- }
2915
- function createFeatureRegistry() {
2916
- const features = /* @__PURE__ */ new Map();
2917
- const listeners = /* @__PURE__ */ new Set();
2918
- let activeId = null;
2919
- const notify = () => {
2920
- for (const listener of [...listeners]) {
2921
- try {
2922
- listener();
2923
- } catch (error) {
2924
- console.error("hiwork feature center subscriber failed:", error);
2925
- }
2926
- }
2927
- };
2928
- return {
2929
- register(feature) {
2930
- const id = feature.id.trim();
2931
- if (id === "") throw new Error("hiwork feature id must not be blank");
2932
- if (features.has(id)) throw new Error(`hiwork feature "${id}" is already registered`);
2933
- const entry = { ...feature, id };
2934
- features.set(id, entry);
2935
- notify();
2936
- let disposed = false;
2937
- return () => {
2938
- if (disposed) return;
2939
- disposed = true;
2940
- if (features.get(id) !== entry) return;
2941
- features.delete(id);
2942
- if (activeId === id) activeId = null;
2943
- notify();
2944
- };
2945
- },
2946
- list() {
2947
- return [...features.values()].sort(compareFeatures);
2948
- },
2949
- activeId() {
2950
- return activeId;
2951
- },
2952
- open(id) {
2953
- if (!features.has(id)) return false;
2954
- if (activeId === id) return true;
2955
- activeId = id;
2956
- notify();
2957
- return true;
2958
- },
2959
- openNative() {
2960
- if (activeId === null) return;
2961
- activeId = null;
2962
- notify();
2963
- },
2964
- subscribe(listener) {
2965
- listeners.add(listener);
2966
- return () => {
2967
- listeners.delete(listener);
2968
- };
2969
- }
2970
- };
3396
+ function setMenuPlacement(preferences, id, placement) {
3397
+ const current = placementOf(preferences, id);
3398
+ if (current === placement) return preferences;
3399
+ const base = withoutPlacement(preferences, id);
3400
+ if (placement === "sidebar") return { ...base, sidebar: [...base.sidebar, id] };
3401
+ if (placement === "more") return { ...base, more: [...base.more, id] };
3402
+ return { ...base, hidden: [...base.hidden, id] };
3403
+ }
3404
+ function moveMenuEntry(preferences, features, id, targetIndex) {
3405
+ const ids = orderedFeatureIds(features, preferences);
3406
+ const from = ids.indexOf(id);
3407
+ if (from < 0) return preferences;
3408
+ const to = Math.max(0, Math.min(ids.length - 1, Math.trunc(targetIndex)));
3409
+ if (to === from) return preferences;
3410
+ const next = [...ids];
3411
+ next.splice(from, 1);
3412
+ next.splice(to, 0, id);
3413
+ return { ...preferences, order: next };
3414
+ }
3415
+ function sameIds(left, right) {
3416
+ return left.length === right.length && left.every((item, index) => item === right[index]);
3417
+ }
3418
+ function moveMenuEntryNear(preferences, features, id, targetId, position) {
3419
+ const ids = orderedFeatureIds(features, preferences);
3420
+ const from = ids.indexOf(id);
3421
+ if (from < 0 || id === targetId || !ids.includes(targetId)) return preferences;
3422
+ const rest = ids.filter((item) => item !== id);
3423
+ const anchor = rest.indexOf(targetId);
3424
+ const next = [...rest];
3425
+ next.splice(position === "before" ? anchor : anchor + 1, 0, id);
3426
+ if (sameIds(next, ids)) return preferences;
3427
+ return { ...preferences, order: next };
3428
+ }
3429
+ function menuPreferenceOps(current, next) {
3430
+ const ops = [];
3431
+ for (const field of ["order", "sidebar", "more", "hidden"]) {
3432
+ if (sameIds(current[field], next[field])) continue;
3433
+ ops.push({ op: "set", path: [field], value: [...next[field]] });
3434
+ }
3435
+ return ops;
2971
3436
  }
2972
3437
 
2973
3438
  // src/client/feature-center.ts
2974
- var import_react7 = require("react");
2975
- var import_client2 = require("react-dom/client");
2976
3439
  var SIDEBAR_SELECTOR = '[data-pane="sidebar"], [class*="sidebarCol"]';
2977
3440
  var CONVERSATION_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]';
2978
3441
  var REGION_SELECTOR = '[class*="regionArea"]';
@@ -2983,11 +3446,16 @@ var NEW_SESSION_SELECTOR = [
2983
3446
  ].join(", ");
2984
3447
  var MENU_ATTRIBUTE = "data-hiwork-feature-menu";
2985
3448
  var MENU_ITEM_ATTRIBUTE = "data-hiwork-feature-menu-item";
3449
+ var MENU_BUTTON_ATTRIBUTE = "data-hiwork-feature-menu-button";
3450
+ var MORE_TOGGLE_ATTRIBUTE = "data-hiwork-feature-more-toggle";
3451
+ var MORE_PANEL_ATTRIBUTE = "data-hiwork-feature-more-panel";
3452
+ var MORE_PANEL_ITEM_ATTRIBUTE = "data-hiwork-feature-more-item";
2986
3453
  var MENU_ICON_ATTRIBUTE = "data-hiwork-feature-menu-icon";
2987
3454
  var MENU_LABEL_ATTRIBUTE = "data-hiwork-feature-menu-label";
2988
3455
  var TOOLTIP_ATTRIBUTE = "data-hiwork-feature-tooltip";
2989
3456
  var TOOLTIP_VISIBLE_ATTRIBUTE = "data-hiwork-feature-tooltip-visible";
2990
3457
  var NATIVE_HIDDEN_ATTRIBUTE = "data-hiwork-native-new-session";
3458
+ var NATIVE_PANEL_LIST_ATTRIBUTE = "data-hiwork-native-panel-list";
2991
3459
  var FEATURE_ROOT_ATTRIBUTE = "data-hiwork-feature-root";
2992
3460
  var OWNER_ATTRIBUTE = "data-hiwork-owner";
2993
3461
  var ACTIVE_PAGE_ATTRIBUTE = "data-hiwork-active-page";
@@ -2997,7 +3465,7 @@ var STYLE_ELEMENT_ID = "hiwork-feature-center-styles";
2997
3465
  var TOOLTIP_DELAY_MS = 500;
2998
3466
  var TOOLTIP_GAP_PX = 8;
2999
3467
  var DEFAULT_NEW_SESSION_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.00003 0.3237C3.76075 0.3237 0.32373 3.76072 0.32373 8C0.32373 9.17603 0.589121 10.2922 1.0632 11.2901L1.35291 11.8989L2.5705 11.3205L2.28079 10.7117C1.89079 9.89074 1.67301 8.97167 1.67301 8C1.67301 4.50546 4.50549 1.67298 8.00003 1.67298C11.4946 1.67298 14.3271 4.50546 14.3271 8C14.3271 11.4945 11.4946 14.327 8.00003 14.327C7.28473 14.327 6.76077 14.277 6.29621 14.1487C5.83857 14.0224 5.40441 13.8109 4.88514 13.4488C4.12569 12.919 3.03778 12.7316 2.141 13.2978L2.12682 13.307L2.11264 13.3171L1.34886 13.854L1.79659 15.188L2.86122 14.4384C3.19068 14.2305 3.68325 14.2542 4.11326 14.5539C4.72789 14.9826 5.30042 15.2724 5.93762 15.4484C6.56803 15.6224 7.22776 15.6763 8.00003 15.6763C12.2393 15.6763 15.6763 12.2393 15.6763 8C15.6763 3.76072 12.2393 0.3237 8.00003 0.3237ZM7.32033 4.82535V7.32536H4.82538V8.67464H7.32033V11.1747H8.6696V8.67464H11.1747V7.32536H8.6696V4.82535H7.32033Z" fill="currentColor"></path></svg>';
3000
- var DEFAULT_FEATURE_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.84457 5.06199C11.6605 4.93876 14.7962 6.14848 14.8484 7.76397C14.8875 8.97461 13.1838 10.0696 10.7215 10.5942" stroke="currentColor" stroke-width="1.2"></path><path d="M5.12742 8.07731C5.00419 4.26138 6.21391 1.12568 7.8294 1.07351C9.04004 1.03441 10.135 2.73808 10.6596 5.20037" stroke="currentColor" stroke-width="1.2"></path><path d="M8.02457 10.6802C4.20865 10.8034 1.07294 9.5937 1.02077 7.97821C0.981678 6.76758 2.68535 5.67262 5.14763 5.14798" stroke="currentColor" stroke-width="1.2"></path><path d="M10.7476 7.89535C10.8708 11.7113 9.66109 14.847 8.0456 14.8991C6.83496 14.9382 5.74 13.2346 5.21536 10.7723" stroke="currentColor" stroke-width="1.2"></path></svg>';
3468
+ var MORE_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="3.2" cy="8" r="1.15" fill="currentColor"></circle><circle cx="8" cy="8" r="1.15" fill="currentColor"></circle><circle cx="12.8" cy="8" r="1.15" fill="currentColor"></circle></svg>';
3001
3469
  var SESSION_ROW_SELECTOR = [
3002
3470
  '[class*="sessionRow"]',
3003
3471
  '[class*="projectRow"]',
@@ -3007,18 +3475,22 @@ var SESSION_ROW_SELECTOR = [
3007
3475
  ].join(", ");
3008
3476
  var featureCenterCss = [
3009
3477
  `[${NATIVE_HIDDEN_ATTRIBUTE}="hidden"] { display: none !important; }`,
3478
+ `[${NATIVE_PANEL_LIST_ATTRIBUTE}="hidden"] { display: none !important; }`,
3010
3479
  `[${MENU_ATTRIBUTE}] { display: flex; flex-direction: column; gap: 2px; margin: 0 2px 8px; }`,
3011
- `[${MENU_ITEM_ATTRIBUTE}] { display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box; padding: 8px 10px; border: 0; border-radius: 10px; background: transparent; color: var(--dsw-alias-label-primary, #0f1115); font-size: 14px; line-height: 22px; text-align: left; cursor: pointer; }`,
3012
- `[${MENU_ITEM_ATTRIBUTE}]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 17, 21, 0.06)); }`,
3013
- `[${MENU_ITEM_ATTRIBUTE}][data-hiwork-feature-active="true"] { background: var(--dsw-alias-interactive-bg-active, rgba(15, 17, 21, 0.1)); }`,
3480
+ `[${MENU_BUTTON_ATTRIBUTE}] { display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box; padding: 8px 10px; border: 0; border-radius: 10px; background: transparent; color: var(--dsw-alias-label-primary, #0f1115); font-size: 14px; line-height: 22px; text-align: left; cursor: pointer; }`,
3481
+ `[${MENU_BUTTON_ATTRIBUTE}]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 17, 21, 0.06)); }`,
3482
+ `[${MENU_BUTTON_ATTRIBUTE}][data-hiwork-feature-active="true"] { background: var(--dsw-alias-interactive-bg-active, rgba(15, 17, 21, 0.1)); }`,
3014
3483
  `[${MENU_ICON_ATTRIBUTE}] { display: inline-flex; flex: none; width: 16px; height: 16px; }`,
3015
3484
  `[${MENU_ICON_ATTRIBUTE}] > svg { display: block; width: 16px; height: 16px; }`,
3016
3485
  // 侧栏折叠:宿主在侧栏根元素上加 `*_collapsed`(rc.1 为 hHd-Xa_collapsed),
3017
3486
  // 原生控件此时只显示图标;注入菜单必须同形,否则文字会在 56px 宽栏里竖排。
3018
3487
  `[class*="sidebarCol"] [class*="collapsed"] [${MENU_ATTRIBUTE}] { margin: 0 4px 8px; }`,
3019
3488
  `[class*="sidebarCol"] [class*="collapsed"] [${MENU_LABEL_ATTRIBUTE}] { display: none; }`,
3020
- `[class*="sidebarCol"] [class*="collapsed"] [${MENU_ITEM_ATTRIBUTE}] { justify-content: center; padding: 8px 0; }`,
3489
+ `[class*="sidebarCol"] [class*="collapsed"] [${MENU_BUTTON_ATTRIBUTE}] { justify-content: center; padding: 8px 0; }`,
3021
3490
  `[class*="sidebarCol"] [class*="collapsed"] [${MENU_ICON_ATTRIBUTE}], [class*="sidebarCol"] [class*="collapsed"] [${MENU_ICON_ATTRIBUTE}] > svg { width: 18px; height: 18px; }`,
3491
+ // 「更多」浮层:与框架浮层(Tooltip/Popover)同色同圆角,固定定位挂 body(不受侧栏 overflow 裁剪)。
3492
+ `[${MORE_PANEL_ATTRIBUTE}] { position: fixed; z-index: 100; display: flex; flex-direction: column; gap: 2px; box-sizing: border-box; min-width: 168px; max-width: 260px; max-height: 60vh; overflow-y: auto; padding: 4px; border: 1px solid var(--dsw-alias-border-l2, rgba(15, 17, 21, 0.1)); border-radius: 12px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 8px 24px rgba(15, 17, 21, 0.16); }`,
3493
+ `[${MORE_PANEL_ATTRIBUTE}] [${MENU_BUTTON_ATTRIBUTE}] { white-space: nowrap; }`,
3022
3494
  // 折叠态提示气泡:几何与配色逐项对齐框架 `ui-primitives` 的 `Tooltip.module.css`
3023
3495
  // (黑底白字、3px 7px 内边距、8px 圆角、13/20 字号、150ms 淡入、最大半屏宽)。
3024
3496
  `[${TOOLTIP_ATTRIBUTE}] { position: fixed; z-index: 100; display: none; width: max-content; max-width: 50vw; padding: 3px 7px; border-radius: 8px; background: var(--dsw-alias-tooltip-bg, #2c2c2e); color: var(--dsw-static-neutral-bluish-00, #fff); font-size: 13px; line-height: 20px; white-space: pre-line; overflow-wrap: break-word; pointer-events: none; transform: translateY(-50%); }`,
@@ -3061,6 +3533,7 @@ function nativeNewSessionButton(root) {
3061
3533
  function conversationContainer() {
3062
3534
  return document.querySelector(CONVERSATION_SELECTOR) ?? void 0;
3063
3535
  }
3536
+ var PANEL_LIST_SELECTOR = 'nav[class*="panelList"]';
3064
3537
  function isCollapsedRail(item) {
3065
3538
  const host = item.closest('[class*="collapsed"]');
3066
3539
  if (host === null) return false;
@@ -3077,12 +3550,15 @@ function mountFeatureCenter(options) {
3077
3550
  return { dispose: () => void 0, openNativeHome: () => void 0 };
3078
3551
  }
3079
3552
  const isActiveOwner = () => activeOwner === owner;
3080
- for (const stale of Array.from(document.querySelectorAll(`[${MENU_ATTRIBUTE}], [${FEATURE_ROOT_ATTRIBUTE}], [${TOOLTIP_ATTRIBUTE}], #${STYLE_ELEMENT_ID}`))) {
3553
+ for (const stale of Array.from(document.querySelectorAll(`[${MENU_ATTRIBUTE}], [${MORE_PANEL_ATTRIBUTE}], [${FEATURE_ROOT_ATTRIBUTE}], [${TOOLTIP_ATTRIBUTE}], #${STYLE_ELEMENT_ID}`))) {
3081
3554
  stale.remove();
3082
3555
  }
3083
3556
  for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_HIDDEN_ATTRIBUTE}]`))) {
3084
3557
  hidden.removeAttribute(NATIVE_HIDDEN_ATTRIBUTE);
3085
3558
  }
3559
+ for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_PANEL_LIST_ATTRIBUTE}]`))) {
3560
+ hidden.removeAttribute(NATIVE_PANEL_LIST_ATTRIBUTE);
3561
+ }
3086
3562
  document.documentElement.removeAttribute(ACTIVE_PAGE_ATTRIBUTE);
3087
3563
  activeOwner = owner;
3088
3564
  const style = document.createElement("style");
@@ -3102,6 +3578,87 @@ function mountFeatureCenter(options) {
3102
3578
  let tooltipElement;
3103
3579
  let tooltipTimer;
3104
3580
  let tooltipItem;
3581
+ let morePanel;
3582
+ let moreToggle;
3583
+ let morePanelOpen = false;
3584
+ let layout = { sidebar: [], more: [] };
3585
+ const preferences = () => options.preferences?.() ?? EMPTY_MENU_PREFERENCES;
3586
+ const menuFeatures = () => [
3587
+ ...registry.list(),
3588
+ ...options.additionalFeatures?.() ?? []
3589
+ ];
3590
+ const hideMorePanel = (restoreFocus = false) => {
3591
+ const panel = morePanel;
3592
+ const toggle = moreToggle;
3593
+ morePanel = void 0;
3594
+ moreToggle = void 0;
3595
+ if (!morePanelOpen && panel === void 0) return;
3596
+ morePanelOpen = false;
3597
+ panel?.remove();
3598
+ if (toggle !== void 0 && toggle.isConnected) {
3599
+ toggle.setAttribute("aria-expanded", "false");
3600
+ toggle.removeAttribute("data-hiwork-feature-active");
3601
+ if (restoreFocus) toggle.focus();
3602
+ }
3603
+ };
3604
+ const createMorePanelItem = (feature, active) => {
3605
+ const item = createMenuItem(feature.id, feature.label(), feature.icon ?? DEFAULT_FEATURE_ICON, active);
3606
+ item.setAttribute(MORE_PANEL_ITEM_ATTRIBUTE, feature.id);
3607
+ item.setAttribute("role", "menuitem");
3608
+ return item;
3609
+ };
3610
+ const renderMorePanel = () => {
3611
+ const panel = morePanel;
3612
+ if (panel === void 0) return;
3613
+ const active = registry.activeId();
3614
+ const pluginsActive = options.nativePanel?.pluginsActive() ?? false;
3615
+ panel.replaceChildren(...layout.more.map((feature) => createMorePanelItem(feature, feature.id === NATIVE_PLUGINS_MENU_ID ? pluginsActive : feature.id === active)));
3616
+ };
3617
+ const placeMorePanel = (toggle, panel) => {
3618
+ const rect = toggle.getBoundingClientRect();
3619
+ const width = panel.offsetWidth;
3620
+ const height = panel.offsetHeight;
3621
+ const overflowRight = rect.right + TOOLTIP_GAP_PX + width > window.innerWidth;
3622
+ const left = overflowRight ? rect.left - TOOLTIP_GAP_PX - width : rect.right + TOOLTIP_GAP_PX;
3623
+ panel.style.left = `${Math.max(0, left)}px`;
3624
+ panel.style.top = `${Math.max(8, Math.min(rect.top, window.innerHeight - height - 8))}px`;
3625
+ };
3626
+ const showMorePanel = (toggle) => {
3627
+ hideMorePanel();
3628
+ if (layout.more.length === 0) return;
3629
+ const panel = document.createElement("div");
3630
+ panel.setAttribute(MORE_PANEL_ATTRIBUTE, "");
3631
+ panel.setAttribute(OWNER_ATTRIBUTE, owner);
3632
+ panel.setAttribute("role", "menu");
3633
+ panel.addEventListener("click", onMorePanelClick);
3634
+ document.body.appendChild(panel);
3635
+ morePanel = panel;
3636
+ moreToggle = toggle;
3637
+ morePanelOpen = true;
3638
+ toggle.setAttribute("aria-expanded", "true");
3639
+ if (layout.more.some((feature) => feature.id === registry.activeId())) toggle.setAttribute("data-hiwork-feature-active", "true");
3640
+ renderMorePanel();
3641
+ placeMorePanel(toggle, panel);
3642
+ };
3643
+ const toggleMorePanel = (toggle) => {
3644
+ if (morePanelOpen) hideMorePanel();
3645
+ else showMorePanel(toggle);
3646
+ };
3647
+ function onMorePanelClick(event) {
3648
+ const target = event.target;
3649
+ if (!(target instanceof Element)) return;
3650
+ const item = target.closest(`[${MORE_PANEL_ITEM_ATTRIBUTE}]`);
3651
+ if (item === null) return;
3652
+ const id = item.getAttribute(MORE_PANEL_ITEM_ATTRIBUTE);
3653
+ hideMorePanel();
3654
+ if (id === null) return;
3655
+ if (id === NATIVE_PLUGINS_MENU_ID) {
3656
+ if (registry.activeId() !== null) registry.openNative();
3657
+ options.nativePanel?.openPlugins();
3658
+ return;
3659
+ }
3660
+ registry.open(id);
3661
+ }
3105
3662
  const hideTooltip = () => {
3106
3663
  if (tooltipTimer !== void 0) {
3107
3664
  clearTimeout(tooltipTimer);
@@ -3203,11 +3760,21 @@ function mountFeatureCenter(options) {
3203
3760
  renderContext.openNativeHome();
3204
3761
  return;
3205
3762
  }
3763
+ if (id === MORE_MENU_ITEM_ID) {
3764
+ toggleMorePanel(item);
3765
+ return;
3766
+ }
3767
+ if (id === NATIVE_PLUGINS_MENU_ID) {
3768
+ if (registry.activeId() !== null) registry.openNative();
3769
+ options.nativePanel?.openPlugins();
3770
+ return;
3771
+ }
3206
3772
  registry.open(id);
3207
3773
  };
3208
3774
  const createMenuItem = (id, label, icon, active) => {
3209
3775
  const item = document.createElement("button");
3210
3776
  item.type = "button";
3777
+ item.setAttribute(MENU_BUTTON_ATTRIBUTE, "");
3211
3778
  item.setAttribute(MENU_ITEM_ATTRIBUTE, id);
3212
3779
  if (active) item.setAttribute("data-hiwork-feature-active", "true");
3213
3780
  item.setAttribute("aria-label", label);
@@ -3222,6 +3789,13 @@ function mountFeatureCenter(options) {
3222
3789
  item.append(labelSpan);
3223
3790
  return item;
3224
3791
  };
3792
+ const createMoreRow = (active) => {
3793
+ const item = createMenuItem(MORE_MENU_ITEM_ID, MORE_MENU_LABEL, MORE_ICON, active);
3794
+ item.setAttribute(MORE_TOGGLE_ATTRIBUTE, "");
3795
+ item.setAttribute("aria-haspopup", "menu");
3796
+ item.setAttribute("aria-expanded", morePanelOpen ? "true" : "false");
3797
+ return item;
3798
+ };
3225
3799
  const nativeIcon = (native) => {
3226
3800
  const svg = native?.querySelector("svg");
3227
3801
  return svg === null || svg === void 0 ? DEFAULT_NEW_SESSION_ICON : svg.outerHTML;
@@ -3230,7 +3804,12 @@ function mountFeatureCenter(options) {
3230
3804
  const element = menuElement;
3231
3805
  if (element === void 0) return;
3232
3806
  const active = registry.activeId();
3233
- const features = registry.list();
3807
+ const features = menuFeatures();
3808
+ const prefs = preferences();
3809
+ const next = menuLayout(features, prefs);
3810
+ const nativePanel = options.nativePanel;
3811
+ const pluginsAvailable = nativePanel?.pluginsAvailable() ?? false;
3812
+ const pluginsActive = pluginsAvailable && (nativePanel?.pluginsActive() ?? false);
3234
3813
  const sidebar = sidebarRoot();
3235
3814
  const native = sidebar === void 0 ? void 0 : nativeNewSessionButton(sidebar);
3236
3815
  const newSessionLabel = options.newSessionLabel?.() ?? DEFAULT_NEW_SESSION_LABEL;
@@ -3238,17 +3817,27 @@ function mountFeatureCenter(options) {
3238
3817
  native === void 0 ? "no-native" : `native:${nativeIcon(native)}`,
3239
3818
  newSessionLabel,
3240
3819
  active ?? "",
3820
+ // 官方入口的可用性/激活态进签名:plugin-manager 装载完成后(面板注册进 main 槽)
3821
+ // 与用户在原生面板间切换时,菜单都要跟着重绘一次。
3822
+ `panel:${pluginsAvailable ? pluginsActive ? "active" : "idle" : "off"}`,
3823
+ // 分组与顺序都进签名:偏好一变(设置面板里拖一下)就必须重建,否则侧栏不动。
3824
+ `layout:${next.sidebar.map((item) => item.id).join(",")}>${next.more.map((item) => item.id).join(",")}`,
3241
3825
  ...features.map((item) => `${item.id}:${item.order ?? ""}:${item.label()}:${item.icon ?? ""}`)
3242
3826
  ].join("|");
3243
3827
  if (signature === menuSignature) return;
3244
3828
  menuSignature = signature;
3829
+ layout = next;
3245
3830
  hideTooltip();
3831
+ hideMorePanel();
3246
3832
  const items = [];
3247
3833
  if (native !== void 0) items.push(createMenuItem(NATIVE_MENU_ITEM_ID, newSessionLabel, nativeIcon(native), false));
3248
- for (const item of features) {
3249
- items.push(createMenuItem(item.id, item.label(), item.icon ?? DEFAULT_FEATURE_ICON, item.id === active));
3834
+ for (const item of next.sidebar) {
3835
+ const rowActive = item.id === NATIVE_PLUGINS_MENU_ID ? pluginsActive : item.id === active;
3836
+ items.push(createMenuItem(item.id, item.label(), item.icon ?? DEFAULT_FEATURE_ICON, rowActive));
3250
3837
  }
3838
+ if (next.more.length > 0) items.push(createMoreRow(next.more.some((item) => item.id === active)));
3251
3839
  element.replaceChildren(...items);
3840
+ syncNativePanelList(pluginsAvailable);
3252
3841
  };
3253
3842
  const ensureMenu = (sidebar, native) => {
3254
3843
  let element = menuElement;
@@ -3274,6 +3863,13 @@ function mountFeatureCenter(options) {
3274
3863
  if (node.getAttribute(OWNER_ATTRIBUTE) !== owner) node.remove();
3275
3864
  }
3276
3865
  };
3866
+ const syncNativePanelList = (pluginsAvailable) => {
3867
+ const sidebar = sidebarRoot();
3868
+ const nav = sidebar?.querySelector(PANEL_LIST_SELECTOR) ?? null;
3869
+ if (nav === null) return;
3870
+ if (pluginsAvailable) nav.setAttribute(NATIVE_PANEL_LIST_ATTRIBUTE, "hidden");
3871
+ else nav.removeAttribute(NATIVE_PANEL_LIST_ATTRIBUTE);
3872
+ };
3277
3873
  const syncMenu = () => {
3278
3874
  dedupe(`[${MENU_ATTRIBUTE}]`);
3279
3875
  const sidebar = sidebarRoot();
@@ -3294,6 +3890,7 @@ function mountFeatureCenter(options) {
3294
3890
  clearTakeover();
3295
3891
  return;
3296
3892
  }
3893
+ if (options.nativePanel?.anyActive() === true) options.nativePanel.returnToConversation();
3297
3894
  const host = conversationContainer();
3298
3895
  if (host === void 0) {
3299
3896
  clearTakeover();
@@ -3312,96 +3909,1435 @@ function mountFeatureCenter(options) {
3312
3909
  const reactRoot = (0, import_client2.createRoot)(element);
3313
3910
  root = reactRoot;
3314
3911
  try {
3315
- reactRoot.render((0, import_react7.createElement)(FeatureErrorBoundary, { onError: onRenderError }, target.render(renderContext)));
3316
- } catch (error) {
3317
- onRenderError(error);
3912
+ reactRoot.render((0, import_react7.createElement)(FeatureErrorBoundary, { onError: onRenderError }, target.render(renderContext)));
3913
+ } catch (error) {
3914
+ onRenderError(error);
3915
+ }
3916
+ };
3917
+ const pending = () => {
3918
+ const sidebar = sidebarRoot();
3919
+ if (sidebar === void 0) return true;
3920
+ if (menuElement === void 0 || !sidebar.contains(menuElement)) return true;
3921
+ if (registry.activeId() === null) return false;
3922
+ const host = conversationContainer();
3923
+ if (host === void 0) return true;
3924
+ return container === void 0 || !host.contains(container);
3925
+ };
3926
+ const scheduleRetry = () => {
3927
+ if (disposed || retryTimer !== void 0 || retries >= maxRetries) return;
3928
+ retryTimer = setTimeout(() => {
3929
+ retryTimer = void 0;
3930
+ retries += 1;
3931
+ sync();
3932
+ if (pending()) scheduleRetry();
3933
+ }, interval);
3934
+ };
3935
+ const onDocumentClickCapture = (event) => {
3936
+ const target = event.target;
3937
+ if (morePanelOpen) {
3938
+ const inside = target instanceof Element && (target.closest(`[${MORE_PANEL_ATTRIBUTE}]`) !== null || target.closest(`[${MORE_TOGGLE_ATTRIBUTE}]`) !== null);
3939
+ if (!inside) hideMorePanel();
3940
+ }
3941
+ if (registry.activeId() === null) return;
3942
+ if (!(target instanceof Element)) return;
3943
+ if (target.closest(`[${FEATURE_ROOT_ATTRIBUTE}]`) !== null) return;
3944
+ if (target.closest(`[${MENU_ATTRIBUTE}]`) !== null) return;
3945
+ if (target.closest(SESSION_ROW_SELECTOR) === null) return;
3946
+ registry.openNative();
3947
+ };
3948
+ const onDocumentKeyDown = (event) => {
3949
+ if (event.key !== "Escape" || !morePanelOpen) return;
3950
+ event.stopPropagation();
3951
+ hideMorePanel(true);
3952
+ };
3953
+ const onScrollOrResize = () => {
3954
+ hideTooltip();
3955
+ hideMorePanel();
3956
+ };
3957
+ const unsubscribeRegistry = registry.subscribe(() => {
3958
+ sync();
3959
+ });
3960
+ let unsubscribeLocale;
3961
+ if (options.subscribeLocale !== void 0) {
3962
+ try {
3963
+ unsubscribeLocale = options.subscribeLocale(() => {
3964
+ menuSignature = "";
3965
+ rebuildMenuItems();
3966
+ });
3967
+ } catch (error) {
3968
+ console.warn("hiwork feature center: locale subscription unavailable", error);
3969
+ }
3970
+ }
3971
+ let unsubscribePreferences;
3972
+ if (options.subscribePreferences !== void 0) {
3973
+ try {
3974
+ unsubscribePreferences = options.subscribePreferences(() => {
3975
+ menuSignature = "";
3976
+ rebuildMenuItems();
3977
+ });
3978
+ } catch (error) {
3979
+ console.warn("hiwork feature center: menu preferences subscription unavailable", error);
3980
+ }
3981
+ }
3982
+ let unsubscribeNativePanel;
3983
+ if (options.nativePanel !== void 0) {
3984
+ try {
3985
+ unsubscribeNativePanel = options.nativePanel.subscribe(() => {
3986
+ if (disposed) return;
3987
+ if (options.nativePanel?.anyActive() === true && registry.activeId() !== null) {
3988
+ registry.openNative();
3989
+ return;
3990
+ }
3991
+ menuSignature = "";
3992
+ rebuildMenuItems();
3993
+ });
3994
+ } catch (error) {
3995
+ console.warn("hiwork feature center: native panel subscription unavailable", error);
3996
+ }
3997
+ }
3998
+ const observer = new MutationObserver(() => {
3999
+ sync();
4000
+ });
4001
+ observer.observe(document.body, { childList: true, subtree: true });
4002
+ document.addEventListener("click", onDocumentClickCapture, true);
4003
+ document.addEventListener("keydown", onDocumentKeyDown, true);
4004
+ document.addEventListener("scroll", onScrollOrResize, true);
4005
+ window.addEventListener("resize", onScrollOrResize);
4006
+ const sync = () => {
4007
+ if (disposed || !isActiveOwner()) return;
4008
+ syncMenu();
4009
+ syncTakeover();
4010
+ if (pending()) scheduleRetry();
4011
+ };
4012
+ sync();
4013
+ return {
4014
+ dispose: () => {
4015
+ if (disposed) return;
4016
+ disposed = true;
4017
+ observer.disconnect();
4018
+ if (retryTimer !== void 0) clearTimeout(retryTimer);
4019
+ retryTimer = void 0;
4020
+ unsubscribeRegistry();
4021
+ unsubscribeLocale?.();
4022
+ unsubscribePreferences?.();
4023
+ unsubscribeNativePanel?.();
4024
+ document.removeEventListener("click", onDocumentClickCapture, true);
4025
+ document.removeEventListener("keydown", onDocumentKeyDown, true);
4026
+ document.removeEventListener("scroll", onScrollOrResize, true);
4027
+ window.removeEventListener("resize", onScrollOrResize);
4028
+ hideTooltip();
4029
+ hideMorePanel();
4030
+ const tooltip = tooltipElement;
4031
+ tooltipElement = void 0;
4032
+ if (tooltip !== void 0 && tooltip.getAttribute(OWNER_ATTRIBUTE) === owner) tooltip.remove();
4033
+ for (const panel of Array.from(document.querySelectorAll(`[${MORE_PANEL_ATTRIBUTE}]`))) {
4034
+ if (panel.getAttribute(OWNER_ATTRIBUTE) === owner) panel.remove();
4035
+ }
4036
+ clearTakeover();
4037
+ const element = menuElement;
4038
+ menuElement = void 0;
4039
+ if (element !== void 0) {
4040
+ element.removeEventListener("click", onMenuClick);
4041
+ if (element.getAttribute(OWNER_ATTRIBUTE) === owner) element.remove();
4042
+ }
4043
+ if (!isActiveOwner()) return;
4044
+ activeOwner = null;
4045
+ for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_HIDDEN_ATTRIBUTE}]`))) {
4046
+ hidden.removeAttribute(NATIVE_HIDDEN_ATTRIBUTE);
4047
+ }
4048
+ for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_PANEL_LIST_ATTRIBUTE}]`))) {
4049
+ hidden.removeAttribute(NATIVE_PANEL_LIST_ATTRIBUTE);
4050
+ }
4051
+ if (style.getAttribute(OWNER_ATTRIBUTE) === owner) style.remove();
4052
+ },
4053
+ openNativeHome: () => {
4054
+ renderContext.openNativeHome();
4055
+ }
4056
+ };
4057
+ }
4058
+
4059
+ // src/client/menu-section.tsx
4060
+ var import_jsx_runtime7 = require("react/jsx-runtime");
4061
+ var PLACEMENT_LABELS = {
4062
+ sidebar: "\u4FA7\u680F",
4063
+ more: "\u66F4\u591A",
4064
+ hidden: "\u9690\u85CF"
4065
+ };
4066
+ var sectionStyle4 = { display: "flex", flexDirection: "column", gap: 16 };
4067
+ var headerStyle2 = { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" };
4068
+ var titleStyle3 = { margin: 0, fontSize: 18, fontWeight: 600, color: textPrimary };
4069
+ var noteStyle2 = { margin: 0, fontSize: 12, lineHeight: "18px", color: textSecondary };
4070
+ var reasonStyle = { margin: 0, fontSize: 12, lineHeight: "18px", color: textTertiary };
4071
+ var listStyle2 = { display: "flex", flexDirection: "column", gap: 2, margin: 0, padding: 0, listStyle: "none" };
4072
+ var rowStyle = (drop, dragging) => ({
4073
+ display: "flex",
4074
+ alignItems: "center",
4075
+ gap: 10,
4076
+ padding: "6px 8px",
4077
+ borderRadius: 8,
4078
+ // 插入线:拖拽落点用 2px 主题色描边标出(不占位,不引起跳动)。
4079
+ ...drop === "before" ? { boxShadow: `inset 0 2px 0 0 ${brandAccent}` } : {},
4080
+ ...drop === "after" ? { boxShadow: `inset 0 -2px 0 0 ${brandAccent}` } : {},
4081
+ ...dragging ? { opacity: 0.5 } : {}
4082
+ });
4083
+ var handleStyle = (writable) => ({
4084
+ display: "flex",
4085
+ alignItems: "center",
4086
+ justifyContent: "center",
4087
+ width: 18,
4088
+ height: 24,
4089
+ flex: "0 0 auto",
4090
+ borderRadius: 6,
4091
+ color: writable ? textTertiary : textTertiary,
4092
+ cursor: writable ? "grab" : "default"
4093
+ });
4094
+ var iconStyle = {
4095
+ display: "flex",
4096
+ alignItems: "center",
4097
+ justifyContent: "center",
4098
+ width: 16,
4099
+ height: 16,
4100
+ flex: "0 0 auto",
4101
+ color: textSecondary
4102
+ };
4103
+ var labelStyle2 = {
4104
+ flex: 1,
4105
+ minWidth: 0,
4106
+ fontSize: 13,
4107
+ lineHeight: "20px",
4108
+ color: textPrimary,
4109
+ overflow: "hidden",
4110
+ textOverflow: "ellipsis",
4111
+ whiteSpace: "nowrap"
4112
+ };
4113
+ var markedStyle = { flex: "0 0 auto", fontSize: 11, lineHeight: "16px", color: textTertiary };
4114
+ var groupStyle = {
4115
+ display: "flex",
4116
+ alignItems: "center",
4117
+ gap: 2,
4118
+ flex: "0 0 auto",
4119
+ padding: 2,
4120
+ borderRadius: 8,
4121
+ background: bgLayer2,
4122
+ border: `1px solid ${borderL2}`
4123
+ };
4124
+ var optionStyle = (active, writable) => ({
4125
+ border: 0,
4126
+ borderRadius: 6,
4127
+ padding: "3px 10px",
4128
+ font: "inherit",
4129
+ fontSize: 12,
4130
+ lineHeight: "18px",
4131
+ color: active ? textPrimary : textSecondary,
4132
+ background: active ? "var(--dsw-alias-bg-layer-1, #ffffff)" : "transparent",
4133
+ boxShadow: active ? "0 1px 2px rgba(15,17,21,0.08)" : "none",
4134
+ cursor: writable ? "pointer" : "default",
4135
+ opacity: writable ? 1 : 0.6
4136
+ });
4137
+ var HANDLE_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><circle cx="6" cy="4" r="1.1"></circle><circle cx="10" cy="4" r="1.1"></circle><circle cx="6" cy="8" r="1.1"></circle><circle cx="10" cy="8" r="1.1"></circle><circle cx="6" cy="12" r="1.1"></circle><circle cx="10" cy="12" r="1.1"></circle></svg>';
4138
+ function MenuRow({
4139
+ entry,
4140
+ writable,
4141
+ dragging,
4142
+ drop,
4143
+ onPlacement,
4144
+ onDragStart,
4145
+ onDragOver,
4146
+ onDrop,
4147
+ onDragEnd,
4148
+ onKeyDown
4149
+ }) {
4150
+ const rowRef = (0, import_react8.useRef)(null);
4151
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4152
+ "li",
4153
+ {
4154
+ ref: rowRef,
4155
+ tabIndex: 0,
4156
+ role: "listitem",
4157
+ "aria-label": `${entry.label}\uFF0C\u7B2C ${entry.index + 1} \u9879\uFF0C\u5171 ${entry.total} \u9879`,
4158
+ style: rowStyle(drop, dragging),
4159
+ "data-hiwork-menu-row": entry.id,
4160
+ "data-hiwork-menu-placement": entry.placement,
4161
+ ...entry.marked ? { "data-hiwork-menu-marked": "true" } : {},
4162
+ ...drop === void 0 ? {} : { "data-hiwork-menu-drop": drop },
4163
+ draggable: writable,
4164
+ onDragStart: (event) => {
4165
+ event.dataTransfer.effectAllowed = "move";
4166
+ event.dataTransfer.setData("text/plain", entry.id);
4167
+ onDragStart(entry.id);
4168
+ },
4169
+ onDragEnd,
4170
+ onKeyDown: (event) => {
4171
+ onKeyDown(event, entry);
4172
+ },
4173
+ onDragOver: (event) => {
4174
+ onDragOver(event, entry.id);
4175
+ },
4176
+ onDrop: (event) => {
4177
+ onDrop(event, entry.id);
4178
+ },
4179
+ children: [
4180
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4181
+ "span",
4182
+ {
4183
+ style: handleStyle(writable),
4184
+ draggable: writable,
4185
+ "aria-hidden": "true",
4186
+ "data-hiwork-menu-handle": entry.id,
4187
+ dangerouslySetInnerHTML: { __html: HANDLE_ICON }
4188
+ }
4189
+ ),
4190
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: iconStyle, dangerouslySetInnerHTML: { __html: entry.icon ?? DEFAULT_FEATURE_ICON } }),
4191
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: labelStyle2, children: entry.label }),
4192
+ entry.marked ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: markedStyle, children: "\u5DF2\u8BBE\u7F6E" }) : null,
4193
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: groupStyle, role: "group", "aria-label": `${entry.label} \u663E\u793A\u4F4D\u7F6E`, children: MENU_PLACEMENTS.map((placement) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4194
+ "button",
4195
+ {
4196
+ type: "button",
4197
+ style: optionStyle(entry.placement === placement, writable),
4198
+ "aria-pressed": entry.placement === placement,
4199
+ "data-hiwork-menu-option": placement,
4200
+ disabled: !writable,
4201
+ onClick: () => {
4202
+ onPlacement(entry.id, placement);
4203
+ },
4204
+ children: PLACEMENT_LABELS[placement]
4205
+ },
4206
+ placement
4207
+ )) })
4208
+ ]
4209
+ }
4210
+ );
4211
+ }
4212
+ function MenuSection(props) {
4213
+ const state = props.useMenu((snapshot) => snapshot);
4214
+ const [dragId, setDragId] = (0, import_react8.useState)(void 0);
4215
+ const [drop, setDrop] = (0, import_react8.useState)(void 0);
4216
+ const { setPlacement, move, moveNear, reset } = props.actions;
4217
+ const writable = state.writable;
4218
+ const onDragOver = (event, id) => {
4219
+ if (dragId === void 0 || dragId === id) return;
4220
+ event.preventDefault();
4221
+ event.dataTransfer.dropEffect = "move";
4222
+ const rect = event.currentTarget.getBoundingClientRect();
4223
+ const position = event.clientY < rect.top + rect.height / 2 ? "before" : "after";
4224
+ setDrop((current) => current?.id === id && current.position === position ? current : { id, position });
4225
+ };
4226
+ const onDrop = (event, id) => {
4227
+ if (dragId === void 0 || dragId === id) return;
4228
+ event.preventDefault();
4229
+ const rect = event.currentTarget.getBoundingClientRect();
4230
+ const position = event.clientY < rect.top + rect.height / 2 ? "before" : "after";
4231
+ const moving = dragId;
4232
+ setDragId(void 0);
4233
+ setDrop(void 0);
4234
+ void moveNear(moving, id, position);
4235
+ };
4236
+ const onKeyDown = (event, entry) => {
4237
+ if (!writable) return;
4238
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
4239
+ if (event.target !== event.currentTarget) return;
4240
+ event.preventDefault();
4241
+ void move(entry.id, event.key === "ArrowUp" ? -1 : 1);
4242
+ };
4243
+ const marked = state.entries.some((entry) => entry.marked);
4244
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "aria-label": "\u4FA7\u680F\u83DC\u5355", style: sectionStyle4, "data-hiwork-menu-section": "", children: [
4245
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("header", { style: headerStyle2, children: [
4246
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: titleStyle3, children: "\u4FA7\u680F\u83DC\u5355" }),
4247
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4248
+ "button",
4249
+ {
4250
+ type: "button",
4251
+ style: writable && marked ? secondaryButton : disabledButton,
4252
+ disabled: !writable || !marked,
4253
+ "data-hiwork-menu-reset": "",
4254
+ onClick: () => {
4255
+ void reset();
4256
+ },
4257
+ children: "\u6062\u590D\u9ED8\u8BA4"
4258
+ }
4259
+ )
4260
+ ] }),
4261
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { style: noteStyle2, children: [
4262
+ "\u4FA7\u680F\u53EA\u663E\u793A\u4F60\u9009\u5B9A\u7684\u5165\u53E3\uFF0C\u5176\u4F59\u6536\u8FDB\u300C\u66F4\u591A\u300D\u2014\u2014\u70B9\u4FA7\u680F\u6700\u540E\u4E00\u884C\u300C\u66F4\u591A\u300D\u5C31\u80FD\u770B\u5230\u3002 \u653E\u5230\u300C\u66F4\u591A\u300D\u6216\u300C\u9690\u85CF\u300D\u90FD\u4F1A\u8BA9\u4FA7\u680F\u5C11\u4E00\u884C\uFF1B\u5F53\u524D\u6700\u591A\u663E\u793A ",
4263
+ state.limit,
4264
+ " \u4E2A\u5165\u53E3\u3002"
4265
+ ] }),
4266
+ state.reason === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: reasonStyle, "data-hiwork-menu-reason": "", children: state.reason }),
4267
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: listStyle2, children: state.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4268
+ MenuRow,
4269
+ {
4270
+ entry,
4271
+ writable,
4272
+ dragging: dragId === entry.id,
4273
+ drop: drop?.id === entry.id ? drop.position : void 0,
4274
+ onPlacement: (id, placement) => {
4275
+ void setPlacement(id, placement);
4276
+ },
4277
+ onDragStart: setDragId,
4278
+ onDragOver,
4279
+ onDrop,
4280
+ onDragEnd: () => {
4281
+ setDragId(void 0);
4282
+ setDrop(void 0);
4283
+ },
4284
+ onKeyDown
4285
+ },
4286
+ entry.id
4287
+ )) }),
4288
+ state.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: noteStyle2, children: "\u5F53\u524D\u8FD8\u6CA1\u6709\u53EF\u914D\u7F6E\u7684\u5165\u53E3\u3002" }) : null,
4289
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: reasonStyle, children: "\u63D0\u793A\uFF1A\u62D6\u52A8\u5DE6\u4FA7\u624B\u67C4\u53EF\u4EE5\u8C03\u6574\u987A\u5E8F\uFF1B\u952E\u76D8\u4E0A/\u4E0B\u65B9\u5411\u952E\u4E5F\u80FD\u79FB\u52A8\u3002" })
4290
+ ] });
4291
+ }
4292
+
4293
+ // src/client/menu-store.ts
4294
+ var import_dsh_client_store2 = require("@deepseek-ai/dsh-client-store");
4295
+ var MENU_FIELD = "menu";
4296
+ var LOADING_STATE = {
4297
+ status: "loading",
4298
+ writable: false,
4299
+ entries: [],
4300
+ limit: DEFAULT_SIDEBAR_LIMIT,
4301
+ reason: void 0
4302
+ };
4303
+ function reasonFor(status, writable) {
4304
+ if (status === "loading") return "\u6B63\u5728\u8BFB\u53D6\u5F53\u524D\u8BBE\u7F6E\u2026";
4305
+ if (status === "unavailable") return "\u5F53\u524D\u8FDE\u63A5\u4E0D\u652F\u6301\u4FDD\u5B58\u672C\u673A\u504F\u597D\uFF0C\u83DC\u5355\u6682\u6309\u9ED8\u8BA4\u663E\u793A\u3002";
4306
+ if (!writable) return "\u5F53\u524D\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u83DC\u5355\u504F\u597D\u3002";
4307
+ return void 0;
4308
+ }
4309
+ function toPathOps(ops) {
4310
+ return ops.map((op) => ({ op: "set", path: [MENU_FIELD, ...op.path], value: [...op.value] }));
4311
+ }
4312
+ var MenuStore = class {
4313
+ /**
4314
+ * @param form - `ai-router` entry 的配置表单(与网关卡片同一个共享实例,见 `src/client/index.ts`)。
4315
+ * @param registry - feature 注册表(列表内容与顺序的另一个来源)。
4316
+ * @param additionalFeatures - 追加的非注册表条目(官方「插件」代理项;可用性由调用方判断)。
4317
+ * 缺省时列表只有注册表成员——与旧版行为一致。
4318
+ */
4319
+ constructor(form, registry, additionalFeatures) {
4320
+ this.form = form;
4321
+ this.registry = registry;
4322
+ this.additionalFeatures = additionalFeatures;
4323
+ this.store = (0, import_dsh_client_store2.createSnapshotStore)(LOADING_STATE);
4324
+ const publish = () => {
4325
+ this.store.set(this.view());
4326
+ };
4327
+ publish();
4328
+ this.unsubscribe.push(form.subscribe(publish));
4329
+ this.unsubscribe.push(registry.subscribe(publish));
4330
+ }
4331
+ store;
4332
+ unsubscribe = [];
4333
+ /** 当前偏好(未就绪 / 值非法时按「从未自定义」处理)。 */
4334
+ preferences() {
4335
+ const snapshot = this.form.getSnapshot();
4336
+ if (snapshot.status !== "ready") return EMPTY_MENU_PREFERENCES;
4337
+ return normalizeMenuPreferences(snapshot.value?.menu);
4338
+ }
4339
+ /** 订阅偏好变化(菜单适配器用;与 `preferences()` 同源)。 */
4340
+ subscribePreferences(listener) {
4341
+ return this.form.subscribe(listener);
4342
+ }
4343
+ /** 菜单条目全集(注册表 + 追加的代理项),与侧栏适配器的来源同构。 */
4344
+ menuFeatures() {
4345
+ return [...this.registry.list(), ...this.additionalFeatures?.() ?? []];
4346
+ }
4347
+ /** 当前渲染状态。 */
4348
+ view() {
4349
+ const snapshot = this.form.getSnapshot();
4350
+ const value = snapshot.status === "ready" ? normalizeMenuPreferences(snapshot.value?.menu) : EMPTY_MENU_PREFERENCES;
4351
+ const features = this.menuFeatures();
4352
+ const layout = menuLayout(features, value);
4353
+ const placementById = new Map([
4354
+ ...layout.sidebar.map((feature) => [feature.id, "sidebar"]),
4355
+ ...layout.more.map((feature) => [feature.id, "more"])
4356
+ ]);
4357
+ const byId = new Map(features.map((feature) => [feature.id, feature]));
4358
+ const ordered = orderedFeatureIds(features, value);
4359
+ const entries = ordered.flatMap((id, index) => {
4360
+ const feature = byId.get(id);
4361
+ if (feature === void 0) return [];
4362
+ return [{
4363
+ id,
4364
+ label: feature.label(),
4365
+ icon: feature.icon,
4366
+ // 生效去向:不在前两组里的就是被隐藏的(hidden 的项两者都不出现)。
4367
+ placement: placementById.get(id) ?? "hidden",
4368
+ marked: placementOf(value, id) !== void 0,
4369
+ index,
4370
+ total: ordered.length
4371
+ }];
4372
+ });
4373
+ const limit = Math.max(0, DEFAULT_SIDEBAR_LIMIT - value.more.filter((id) => features.some((feature) => feature.id === id)).length);
4374
+ return {
4375
+ status: snapshot.status,
4376
+ writable: snapshot.status === "ready" && snapshot.writable,
4377
+ entries,
4378
+ limit,
4379
+ reason: reasonFor(snapshot.status, snapshot.writable)
4380
+ };
4381
+ }
4382
+ /** 清掉在这份 store 上的全部订阅(插件卸载时调用)。 */
4383
+ dispose() {
4384
+ for (const off of this.unsubscribe.splice(0)) off();
4385
+ }
4386
+ /** 立即重算渲染状态(追加条目的可用性变化等非表单/注册表来源的变化走这里)。 */
4387
+ refresh() {
4388
+ this.store.set(this.view());
4389
+ }
4390
+ /** 注入面:设置分区组件消费。 */
4391
+ inject() {
4392
+ return {
4393
+ hooks: { menu: this.store },
4394
+ actions: {
4395
+ setPlacement: (id, placement) => this.setPlacement(id, placement),
4396
+ move: (id, offset) => this.move(id, offset),
4397
+ moveNear: (id, targetId, position) => this.moveNear(id, targetId, position),
4398
+ reset: () => this.reset()
4399
+ }
4400
+ };
4401
+ }
4402
+ async setPlacement(id, placement) {
4403
+ const current = this.preferences();
4404
+ const effective = this.view().entries.find((entry) => entry.id === id)?.placement;
4405
+ if (effective === placement) return;
4406
+ await this.write(setMenuPlacement(current, id, placement), current);
4407
+ }
4408
+ async move(id, offset) {
4409
+ const current = this.preferences();
4410
+ const ids = this.view().entries.map((entry) => entry.id);
4411
+ const index = ids.indexOf(id);
4412
+ if (index < 0) return;
4413
+ await this.write(moveMenuEntry(current, this.menuFeatures(), id, index + offset), current);
4414
+ }
4415
+ async moveNear(id, targetId, position) {
4416
+ const current = this.preferences();
4417
+ await this.write(moveMenuEntryNear(current, this.menuFeatures(), id, targetId, position), current);
4418
+ }
4419
+ async reset() {
4420
+ const current = this.form.getSnapshot();
4421
+ if (current.status !== "ready" || !current.writable) return;
4422
+ await this.form.mutate([{ op: "unset", path: [MENU_FIELD] }]);
4423
+ }
4424
+ /** 未标记项的生效去向已由 `view()` 给出,这里不再另算一份。 */
4425
+ async write(next, current) {
4426
+ const snapshot = this.form.getSnapshot();
4427
+ if (snapshot.status !== "ready" || !snapshot.writable) return;
4428
+ const ops = menuPreferenceOps(current, next);
4429
+ if (ops.length === 0) return;
4430
+ await this.form.mutate(toPathOps(ops));
4431
+ }
4432
+ };
4433
+
4434
+ // src/client/images-data.ts
4435
+ var IMAGE_CHANNEL = "/hiwork-image";
4436
+ var IMAGE_LIST_PAGE_SIZE = 20;
4437
+ var IMAGE_LIST_MAX_PAGE_SIZE = 100;
4438
+ var IMAGE_DATA_ERROR_TEXT = "\u56FE\u7247\u8BB0\u5F55\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
4439
+ var ImageRpcError = class extends Error {
4440
+ constructor(code, message) {
4441
+ super(message);
4442
+ this.code = code;
4443
+ this.name = "ImageRpcError";
4444
+ }
4445
+ };
4446
+ function unwrapImageResult(value) {
4447
+ if (typeof value !== "object" || value === null || !("ok" in value)) {
4448
+ throw new ImageRpcError("internal", "\u56FE\u7247\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
4449
+ }
4450
+ const result = value;
4451
+ if (result.ok === true) return result.value;
4452
+ if (result.ok === false) {
4453
+ const code = typeof result.error?.code === "string" ? result.error.code : "internal";
4454
+ const message = typeof result.error?.message === "string" && result.error.message.trim() !== "" ? result.error.message : IMAGE_DATA_ERROR_TEXT;
4455
+ throw new ImageRpcError(code, message);
4456
+ }
4457
+ throw new ImageRpcError("internal", "\u56FE\u7247\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
4458
+ }
4459
+ function imageErrorOf(error) {
4460
+ if (error instanceof ImageRpcError) return { code: error.code, message: error.message };
4461
+ return { code: "internal", message: IMAGE_DATA_ERROR_TEXT };
4462
+ }
4463
+ function str(value) {
4464
+ return typeof value === "string" ? value : "";
4465
+ }
4466
+ function num(value) {
4467
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
4468
+ }
4469
+ function normalizeImageListPage(value, fallbackPage, fallbackPageSize) {
4470
+ const record = typeof value === "object" && value !== null ? value : {};
4471
+ const rawItems = record["items"];
4472
+ if (!Array.isArray(rawItems)) throw new ImageRpcError("internal", "\u56FE\u7247\u5217\u8868\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
4473
+ const items = [];
4474
+ for (const entry of rawItems) {
4475
+ const row = typeof entry === "object" && entry !== null ? entry : {};
4476
+ items.push({
4477
+ id: num(row["id"]),
4478
+ prompt: str(row["prompt"]),
4479
+ ...str(row["revisedPrompt"]) === "" ? {} : { revisedPrompt: str(row["revisedPrompt"]) },
4480
+ model: str(row["model"]),
4481
+ // `size` 是网关侧唯一的新增投影(spec §5.2 末);缺失时留空串,视图渲染 `—`。
4482
+ size: str(row["size"]),
4483
+ format: str(row["format"]),
4484
+ mimeType: str(row["mimeType"]),
4485
+ byteSize: num(row["byteSize"]),
4486
+ generationMs: num(row["generationMs"]),
4487
+ createdAt: str(row["createdAt"])
4488
+ });
4489
+ }
4490
+ return {
4491
+ items,
4492
+ total: num(record["total"]),
4493
+ page: num(record["page"]) > 0 ? num(record["page"]) : fallbackPage,
4494
+ pageSize: num(record["pageSize"]) > 0 ? num(record["pageSize"]) : fallbackPageSize
4495
+ };
4496
+ }
4497
+ function normalizeImageBytes(value) {
4498
+ const record = typeof value === "object" && value !== null ? value : {};
4499
+ const dataBase64 = str(record["dataBase64"]);
4500
+ if (dataBase64 === "") throw new ImageRpcError("internal", "\u56FE\u7247\u6570\u636E\u4E3A\u7A7A\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
4501
+ return {
4502
+ dataBase64,
4503
+ mimeType: str(record["mimeType"]) === "" ? "image/png" : str(record["mimeType"]),
4504
+ byteSize: num(record["byteSize"])
4505
+ };
4506
+ }
4507
+ var ImageGallerySource = class {
4508
+ constructor(rpc) {
4509
+ this.rpc = rpc;
4510
+ }
4511
+ /** 分页拉取历史记录(`page` 从 1 开始;`pageSize` 上限 100)。 */
4512
+ async list(page = 1, pageSize = IMAGE_LIST_PAGE_SIZE) {
4513
+ const size = Math.min(Math.max(1, Math.trunc(pageSize)), IMAGE_LIST_MAX_PAGE_SIZE);
4514
+ const raw = await this.rpc.call(IMAGE_CHANNEL, "list", { page: Math.max(1, Math.trunc(page)), pageSize: size });
4515
+ return normalizeImageListPage(unwrapImageResult(raw), page, size);
4516
+ }
4517
+ /** 取一张图的本体(**点哪张拉哪张**;超 20MiB 由 Host 回 `IMAGE_TOO_LARGE`)。 */
4518
+ async fetch(id) {
4519
+ const raw = await this.rpc.call(IMAGE_CHANNEL, "fetch", { id });
4520
+ return normalizeImageBytes(unwrapImageResult(raw));
4521
+ }
4522
+ /** 把一张图存到工作区(缺省落默认工作区,由 Host 决定)。 */
4523
+ async save(id, workspaceId) {
4524
+ const payload = workspaceId === void 0 || workspaceId === "" ? { id } : { id, workspaceId };
4525
+ const raw = await this.rpc.call(IMAGE_CHANNEL, "save", payload);
4526
+ return { path: str(unwrapImageResult(raw)["path"]) };
4527
+ }
4528
+ };
4529
+ function imageDataUrl(bytes) {
4530
+ const mime = bytes.mimeType === "" ? "image/png" : bytes.mimeType;
4531
+ return `data:${mime};base64,${bytes.dataBase64}`;
4532
+ }
4533
+ function formatImageBytes(bytes) {
4534
+ if (bytes === void 0 || !Number.isFinite(bytes) || bytes <= 0) return "";
4535
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4536
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
4537
+ return `${bytes} B`;
4538
+ }
4539
+ function imagePromptSummary(prompt, limit = 42) {
4540
+ const text6 = prompt.trim().replace(/\s+/g, " ");
4541
+ if (text6 === "") return "\uFF08\u65E0\u63D0\u793A\u8BCD\uFF09";
4542
+ return text6.length <= limit ? text6 : `${text6.slice(0, limit)}\u2026`;
4543
+ }
4544
+ function imageTimeLabel(createdAt) {
4545
+ const date = new Date(createdAt);
4546
+ if (Number.isNaN(date.getTime())) return "";
4547
+ return date.toLocaleString("zh-CN", { hour12: false });
4548
+ }
4549
+
4550
+ // src/client/images-page.tsx
4551
+ var import_react9 = require("react");
4552
+
4553
+ // src/client/image-styles.ts
4554
+ var IMAGES_STYLE_ID = "hiwork-core-images-styles";
4555
+ var IMAGES_STYLE_TAG_ID = "hiwork-core/images.css";
4556
+ function ensureImagesStyle(doc) {
4557
+ if (doc.getElementById(IMAGES_STYLE_ID) !== null) return;
4558
+ const style = doc.createElement("style");
4559
+ style.id = IMAGES_STYLE_ID;
4560
+ ownStyleTag(style, IMAGES_STYLE_TAG_ID);
4561
+ style.textContent = [imageCardsCss, imagesPageCss].join("\n");
4562
+ doc.head.appendChild(style);
4563
+ }
4564
+
4565
+ // src/client/images-page.tsx
4566
+ var IMAGES_FEATURE_ID = "images";
4567
+ var IMAGES_FEATURE_ORDER = 60;
4568
+ var IMAGES_FEATURE_LABEL = "\u6211\u7684\u56FE\u7247";
4569
+ var IMAGES_FEATURE_EFFECT_LABEL = "hiwork-core: images feature";
4570
+ var IMAGES_FEATURE_ICON = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="1.6" y="2.6" width="12.8" height="10.8" rx="1.8" stroke="currentColor" stroke-width="1.2"></rect><circle cx="5.7" cy="6.3" r="1.05" fill="currentColor"></circle><path d="M2.6 11.4l3.1-2.9c.4-.4 1-.4 1.4 0l2.1 2 1.4-1.3c.4-.4 1-.4 1.4 0l1.4 1.3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"></path></svg>';
4571
+ var IMAGES_EMPTY_TEXT = "\u8FD8\u6CA1\u6709\u751F\u6210\u8FC7\u56FE\u7247";
4572
+ var IMAGES_EMPTY_HINT = "\u5728\u4F1A\u8BDD\u91CC\u8BF4\u300E\u753B\u4E00\u5F20\u2026\u300F\u5C31\u80FD\u751F\u6210";
4573
+ var IMAGES_LOADING_TEXT = "\u6B63\u5728\u52A0\u8F7D\u56FE\u7247\u8BB0\u5F55\u2026";
4574
+ var IMAGES_PREVIEW_PLACEHOLDER = "\u4ECE\u5DE6\u4FA7\u9009\u4E00\u5F20\u56FE\u7247\u67E5\u770B";
4575
+ var IMAGES_PREVIEW_LOADING_TEXT = "\u6B63\u5728\u52A0\u8F7D\u56FE\u7247\u2026";
4576
+ var IMAGES_DISABLED_NOTE = "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u56FE\u7247\u751F\u6210";
4577
+ var IMAGES_MISSING_TEXT = "\u2014";
4578
+ var IMAGES_SAVE_TEXT = "\u5B58\u5230\u5DE5\u4F5C\u533A";
4579
+ var IMAGES_SAVE_SAVED_PREFIX = "\u5DF2\u4FDD\u5B58\u5230 ";
4580
+ var IMAGES_SAVE_RUNNING_TEXT = "\u6B63\u5728\u4FDD\u5B58\u2026";
4581
+ var IMAGES_COPY_TEXT = "\u590D\u5236\u63D0\u793A\u8BCD";
4582
+ var IMAGES_MORE_TEXT = "\u52A0\u8F7D\u66F4\u591A";
4583
+ var IMAGES_RETRY_TEXT = "\u91CD\u8BD5";
4584
+ var IMAGES_SAVE_HINT = "\u56FE\u7247\u4F1A\u5B58\u5230\u4F60\u7684\u9ED8\u8BA4\u5DE5\u4F5C\u533A\uFF08~/HiWork/Default\uFF09\u7684\u300C\u56FE\u7247\u300D\u76EE\u5F55\u3002";
4585
+ var IMAGES_COPY_HINTS = { copied: "\u5DF2\u590D\u5236", failed: "\u590D\u5236\u5931\u8D25" };
4586
+ async function copyText(text6) {
4587
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
4588
+ if (clipboard === void 0 || typeof clipboard.writeText !== "function") return false;
4589
+ try {
4590
+ await clipboard.writeText(text6);
4591
+ return true;
4592
+ } catch {
4593
+ return false;
4594
+ }
4595
+ }
4596
+ function rowMeta(row) {
4597
+ const time = imageTimeLabel(row.createdAt);
4598
+ return [
4599
+ row.size === "" ? IMAGES_MISSING_TEXT : row.size,
4600
+ time === "" ? IMAGES_MISSING_TEXT : time,
4601
+ formatImageBytes(row.byteSize)
4602
+ ].filter((part) => part !== "").join(" \xB7 ");
4603
+ }
4604
+ function ImagesPage({ source }) {
4605
+ const [rows, setRows] = (0, import_react9.useState)([]);
4606
+ const [total, setTotal] = (0, import_react9.useState)(0);
4607
+ const [page, setPage] = (0, import_react9.useState)(1);
4608
+ const [listState, setListState] = (0, import_react9.useState)("loading");
4609
+ const [listFailure, setListFailure] = (0, import_react9.useState)(null);
4610
+ const [selectedId, setSelectedId] = (0, import_react9.useState)(void 0);
4611
+ const [preview, setPreview] = (0, import_react9.useState)({ status: "idle" });
4612
+ const [saveState, setSaveState] = (0, import_react9.useState)({ status: "idle" });
4613
+ const [copyHint, setCopyHint] = (0, import_react9.useState)("");
4614
+ const listTicket = (0, import_react9.useRef)(0);
4615
+ const previewTicket = (0, import_react9.useRef)(0);
4616
+ const alive = (0, import_react9.useRef)(true);
4617
+ (0, import_react9.useEffect)(() => {
4618
+ if (typeof document === "undefined") return;
4619
+ ensureImagesStyle(document);
4620
+ }, []);
4621
+ (0, import_react9.useEffect)(() => {
4622
+ alive.current = true;
4623
+ return () => {
4624
+ alive.current = false;
4625
+ };
4626
+ }, []);
4627
+ const load = (0, import_react9.useCallback)(async (nextPage, append) => {
4628
+ const current = listTicket.current + 1;
4629
+ listTicket.current = current;
4630
+ setListState("loading");
4631
+ try {
4632
+ const result = await source.list(nextPage, IMAGE_LIST_PAGE_SIZE);
4633
+ if (!alive.current || current !== listTicket.current) return;
4634
+ setRows((prev) => append ? [...prev, ...result.items.filter((item) => !prev.some((existing) => existing.id === item.id))] : result.items);
4635
+ setTotal(result.total);
4636
+ setPage(result.page);
4637
+ setListFailure(null);
4638
+ setListState("ready");
4639
+ } catch (error) {
4640
+ if (!alive.current || current !== listTicket.current) return;
4641
+ setListFailure(imageErrorOf(error));
4642
+ setListState("failed");
4643
+ }
4644
+ }, [source]);
4645
+ (0, import_react9.useEffect)(() => {
4646
+ void load(1, false);
4647
+ }, [load]);
4648
+ const select = (0, import_react9.useCallback)(async (row) => {
4649
+ setSelectedId(row.id);
4650
+ setSaveState({ status: "idle" });
4651
+ setCopyHint("");
4652
+ const current = previewTicket.current + 1;
4653
+ previewTicket.current = current;
4654
+ setPreview({ status: "loading" });
4655
+ try {
4656
+ const bytes = await source.fetch(row.id);
4657
+ if (!alive.current || current !== previewTicket.current) return;
4658
+ setPreview({ status: "ready", url: imageDataUrl(bytes), byteSize: bytes.byteSize });
4659
+ } catch (error) {
4660
+ if (!alive.current || current !== previewTicket.current) return;
4661
+ const failure = imageErrorOf(error);
4662
+ setPreview({ status: "failed", code: failure.code, message: failure.message });
4663
+ }
4664
+ }, [source]);
4665
+ const save = (0, import_react9.useCallback)(async () => {
4666
+ if (selectedId === void 0) return;
4667
+ setSaveState({ status: "saving" });
4668
+ try {
4669
+ const { path } = await source.save(selectedId);
4670
+ if (!alive.current) return;
4671
+ setSaveState({ status: "saved", path });
4672
+ } catch (error) {
4673
+ if (!alive.current) return;
4674
+ setSaveState({ status: "failed", message: imageErrorOf(error).message });
4675
+ }
4676
+ }, [source, selectedId]);
4677
+ const selected = rows.find((row) => row.id === selectedId);
4678
+ const disabled = listFailure?.code === "IMAGE_DISABLED" || preview.status === "failed" && preview.code === "IMAGE_DISABLED";
4679
+ const previewBody = (() => {
4680
+ if (preview.status === "idle") {
4681
+ return (0, import_react9.createElement)("p", { className: "hiwork-images-note", "data-hiwork-images-placeholder": "" }, IMAGES_PREVIEW_PLACEHOLDER);
4682
+ }
4683
+ if (preview.status === "loading") {
4684
+ return (0, import_react9.createElement)("p", { className: "hiwork-images-note", "data-hiwork-images-loading": "" }, IMAGES_PREVIEW_LOADING_TEXT);
4685
+ }
4686
+ if (preview.status === "failed") {
4687
+ return (0, import_react9.createElement)(
4688
+ "div",
4689
+ { className: "hiwork-images-actions", "data-hiwork-images-preview-error": preview.code },
4690
+ (0, import_react9.createElement)("span", { className: "hiwork-images-error" }, preview.message),
4691
+ selected === void 0 ? null : (0, import_react9.createElement)(
4692
+ "button",
4693
+ { type: "button", className: "hiwork-images-action", "data-hiwork-images-retry-preview": "", onClick: () => {
4694
+ void select(selected);
4695
+ } },
4696
+ IMAGES_RETRY_TEXT
4697
+ )
4698
+ );
4699
+ }
4700
+ return (0, import_react9.createElement)(
4701
+ "div",
4702
+ { className: "hiwork-images-preview-body" },
4703
+ // 字节来自本次 fetch:data URL 渲染,**不写盘、不指向任何本地文件**。
4704
+ (0, import_react9.createElement)("img", {
4705
+ className: "hiwork-images-image",
4706
+ src: preview.url,
4707
+ alt: selected === void 0 || selected.prompt.trim() === "" ? "\u5386\u53F2\u56FE\u7247" : selected.prompt.trim(),
4708
+ "data-hiwork-images-image": ""
4709
+ }),
4710
+ selected === void 0 ? null : (0, import_react9.createElement)("p", { className: "hiwork-images-preview-prompt", "data-hiwork-images-prompt": "" }, selected.prompt),
4711
+ selected === void 0 ? null : (0, import_react9.createElement)("p", { className: "hiwork-images-note", "data-hiwork-images-preview-meta": "" }, [
4712
+ rowMeta(selected),
4713
+ formatImageBytes(preview.byteSize)
4714
+ ].filter((part) => part !== "").join(" \xB7 ")),
4715
+ (0, import_react9.createElement)(
4716
+ "div",
4717
+ { className: "hiwork-images-actions" },
4718
+ (0, import_react9.createElement)(
4719
+ "button",
4720
+ {
4721
+ type: "button",
4722
+ className: "hiwork-images-action",
4723
+ "data-hiwork-images-save": "",
4724
+ disabled: saveState.status === "saving",
4725
+ onClick: () => {
4726
+ void save();
4727
+ }
4728
+ },
4729
+ saveState.status === "saving" ? IMAGES_SAVE_RUNNING_TEXT : IMAGES_SAVE_TEXT
4730
+ ),
4731
+ (0, import_react9.createElement)(
4732
+ "button",
4733
+ {
4734
+ type: "button",
4735
+ className: "hiwork-images-action",
4736
+ "data-hiwork-images-copy": "",
4737
+ onClick: () => {
4738
+ void copyText(selected?.prompt ?? "").then((ok) => {
4739
+ setCopyHint(ok ? IMAGES_COPY_HINTS.copied : IMAGES_COPY_HINTS.failed);
4740
+ });
4741
+ }
4742
+ },
4743
+ IMAGES_COPY_TEXT
4744
+ ),
4745
+ copyHint === "" ? null : (0, import_react9.createElement)("span", { className: "hiwork-images-note", "data-hiwork-images-copy-state": copyHint }, copyHint)
4746
+ ),
4747
+ saveState.status === "saved" ? (0, import_react9.createElement)("span", { className: "hiwork-images-saved", "data-hiwork-images-saved": "" }, `${IMAGES_SAVE_SAVED_PREFIX}${saveState.path}`) : null,
4748
+ saveState.status === "failed" ? (0, import_react9.createElement)("span", { className: "hiwork-images-error", "data-hiwork-images-save-error": "" }, saveState.message) : null,
4749
+ (0, import_react9.createElement)("p", { className: "hiwork-images-note" }, IMAGES_SAVE_HINT)
4750
+ );
4751
+ })();
4752
+ const empty = listState === "ready" && rows.length === 0;
4753
+ return (0, import_react9.createElement)(
4754
+ "section",
4755
+ { className: "hiwork-images", "data-hiwork-images": "", "data-hiwork-images-state": listState },
4756
+ (0, import_react9.createElement)(
4757
+ "div",
4758
+ { className: "hiwork-images-head" },
4759
+ (0, import_react9.createElement)("h2", { className: "hiwork-images-title" }, IMAGES_FEATURE_LABEL),
4760
+ (0, import_react9.createElement)(
4761
+ "span",
4762
+ { className: "hiwork-images-count", "data-hiwork-images-count": "" },
4763
+ listState === "loading" && rows.length === 0 ? IMAGES_LOADING_TEXT : `\u5171 ${total} \u5F20`
4764
+ )
4765
+ ),
4766
+ // 关开关只关「生成」不关「查看」:历史照常可见,但要说清当前不能再生成(spec §7.2 V11)。
4767
+ disabled ? (0, import_react9.createElement)("div", { className: "hiwork-images-banner", "data-hiwork-images-disabled": "" }, IMAGES_DISABLED_NOTE) : null,
4768
+ // 列表级失败:行内红字 + 重试(成功过一页的话**保留已有行**,不清空)。
4769
+ listFailure === null ? null : (0, import_react9.createElement)(
4770
+ "div",
4771
+ { className: "hiwork-images-actions", "data-hiwork-images-error": listFailure.code },
4772
+ (0, import_react9.createElement)("span", { className: "hiwork-images-error" }, listFailure.message),
4773
+ (0, import_react9.createElement)(
4774
+ "button",
4775
+ { type: "button", className: "hiwork-images-action", "data-hiwork-images-retry": "", onClick: () => {
4776
+ void load(page, rows.length > 0);
4777
+ } },
4778
+ IMAGES_RETRY_TEXT
4779
+ )
4780
+ ),
4781
+ empty ? (0, import_react9.createElement)(
4782
+ "div",
4783
+ { className: "hiwork-images-empty", "data-hiwork-images-empty": "" },
4784
+ (0, import_react9.createElement)("p", { className: "hiwork-images-empty-title" }, IMAGES_EMPTY_TEXT),
4785
+ (0, import_react9.createElement)("p", { className: "hiwork-images-note" }, IMAGES_EMPTY_HINT)
4786
+ ) : (0, import_react9.createElement)(
4787
+ "div",
4788
+ { className: "hiwork-images-body" },
4789
+ (0, import_react9.createElement)(
4790
+ "ul",
4791
+ { className: "hiwork-images-list", "data-hiwork-images-list": "" },
4792
+ ...rows.map((row) => (0, import_react9.createElement)(
4793
+ "li",
4794
+ { key: row.id },
4795
+ (0, import_react9.createElement)(
4796
+ "button",
4797
+ {
4798
+ type: "button",
4799
+ className: "hiwork-images-row",
4800
+ "data-hiwork-images-row": row.id,
4801
+ "aria-pressed": row.id === selectedId,
4802
+ onClick: () => {
4803
+ void select(row);
4804
+ }
4805
+ },
4806
+ (0, import_react9.createElement)("span", { className: "hiwork-images-row-prompt" }, imagePromptSummary(row.prompt)),
4807
+ (0, import_react9.createElement)("span", { className: "hiwork-images-row-meta", "data-hiwork-images-row-meta": "" }, rowMeta(row))
4808
+ )
4809
+ ))
4810
+ ),
4811
+ (0, import_react9.createElement)("div", { className: "hiwork-images-preview", "data-hiwork-images-preview": "" }, previewBody)
4812
+ ),
4813
+ rows.length > 0 && rows.length < total ? (0, import_react9.createElement)(
4814
+ "button",
4815
+ {
4816
+ type: "button",
4817
+ className: "hiwork-images-more",
4818
+ "data-hiwork-images-more": "",
4819
+ disabled: listState === "loading",
4820
+ onClick: () => {
4821
+ void load(page + 1, true);
4822
+ }
4823
+ },
4824
+ IMAGES_MORE_TEXT
4825
+ ) : null
4826
+ );
4827
+ }
4828
+ function imagesFeature(source) {
4829
+ return {
4830
+ id: IMAGES_FEATURE_ID,
4831
+ order: IMAGES_FEATURE_ORDER,
4832
+ label: () => IMAGES_FEATURE_LABEL,
4833
+ icon: IMAGES_FEATURE_ICON,
4834
+ render: () => (0, import_react9.createElement)(ImagesPage, { source })
4835
+ };
4836
+ }
4837
+ function installImagesFeature(scope, registry, source) {
4838
+ scope.effect(() => registry.register(imagesFeature(source)), IMAGES_FEATURE_EFFECT_LABEL);
4839
+ }
4840
+
4841
+ // src/client/image-cards.tsx
4842
+ var import_react10 = require("react");
4843
+ var IMAGE_GENERATE_TOOL_NAME = "image_generate";
4844
+ var TOOL_VIEW_SLOT = "tool.call.toolview";
4845
+ var IMAGE_CARD_TITLE = "\u751F\u6210\u56FE\u7247";
4846
+ var IMAGE_CARD_GENERIC_ERROR = "\u56FE\u7247\u751F\u6210\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
4847
+ var IMAGE_ERROR_TEXT = {
4848
+ IMAGE_DISABLED: "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u56FE\u7247\u751F\u6210",
4849
+ IMAGE_NOT_CONFIGURED: "\u56FE\u7247\u751F\u6210\u8FD8\u6CA1\u6709\u914D\u7F6E\u597D\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
4850
+ IMAGE_BUDGET_EXCEEDED: "\u751F\u56FE\u6682\u65F6\u4E0D\u53EF\u7528\uFF1A\u53EF\u80FD\u662F\u672C\u6708\u989D\u5EA6\u7528\u5C3D\uFF0C\u4E5F\u53EF\u80FD\u662F\u8FD9\u4E2A\u6A21\u578B\u8FD8\u6CA1\u914D\u7F6E\u597D\u2014\u2014\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
4851
+ IMAGE_BAD_REQUEST: "\u8FD9\u4E2A\u63D0\u793A\u8BCD\u6CA1\u80FD\u751F\u6210\uFF0C\u6362\u4E2A\u8BF4\u6CD5\u518D\u8BD5",
4852
+ IMAGE_TOO_LARGE: "\u751F\u6210\u7684\u56FE\u7247\u592A\u5927\u4E86\uFF0C\u6362\u4E2A\u5C0F\u4E00\u70B9\u7684\u5C3A\u5BF8\uFF08\u5982 1024x1024\uFF09",
4853
+ IMAGE_TIMEOUT: "\u751F\u6210\u8D85\u65F6\u4E86\uFF0C\u518D\u8BD5\u4E00\u6B21\uFF08\u590D\u6742\u753B\u9762\u4F1A\u6BD4\u8F83\u6162\uFF09",
4854
+ IMAGE_UPSTREAM_ERROR: "\u56FE\u7247\u751F\u6210\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
4855
+ GATEWAY_UNREACHABLE: "\u56FE\u7247\u751F\u6210\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
4856
+ // spec §6.6 把 SESSION_EXPIRED 并入「服务不可用」那一行(**逐字同该行**),只在括号里
4857
+ // 注明例外「提示重新登录」——所以不加自创文案,另给一句 {@link IMAGE_RELOGIN_HINT}。
4858
+ SESSION_EXPIRED: "\u56FE\u7247\u751F\u6210\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
4859
+ // Host 侧写盘失败(src/imagegen.ts 的两种原因):图已经生成、也已经计费,绝不能让
4860
+ // 员工以为白花了钱——文案必须说清「图在,只是没落到工作区」。
4861
+ "write-failed": "\u56FE\u7247\u5DF2\u751F\u6210\u4F46\u6CA1\u80FD\u4FDD\u5B58\u5230\u5DE5\u4F5C\u533A",
4862
+ "path-outside-workspace": "\u56FE\u7247\u6CA1\u80FD\u4FDD\u5B58\u5230\u5DE5\u4F5C\u533A\uFF1A\u76EE\u6807\u8DEF\u5F84\u4E0D\u5728\u5DE5\u4F5C\u533A\u91CC",
4863
+ cancelled: "\u5DF2\u53D6\u6D88",
4864
+ // 平台级中断码(`interrupted` 是 shell 自己的停止标记,不是网关错误码)。
4865
+ interrupted: "\u5DF2\u505C\u6B62"
4866
+ };
4867
+ var IMAGE_RETRYABLE_CODES = [
4868
+ "IMAGE_BAD_REQUEST",
4869
+ "IMAGE_TOO_LARGE",
4870
+ "IMAGE_TIMEOUT",
4871
+ "IMAGE_UPSTREAM_ERROR",
4872
+ "GATEWAY_UNREACHABLE"
4873
+ ];
4874
+ var IMAGE_RELOGIN_CODES = ["SESSION_EXPIRED"];
4875
+ var IMAGE_RELOGIN_HINT = "\u8BF7\u91CD\u65B0\u767B\u5F55";
4876
+ function needsRelogin(code) {
4877
+ return IMAGE_RELOGIN_CODES.includes(code);
4878
+ }
4879
+ function imageErrorText(code, message = "") {
4880
+ const known = IMAGE_ERROR_TEXT[code];
4881
+ if (known !== void 0) return known;
4882
+ return message.trim() === "" ? IMAGE_CARD_GENERIC_ERROR : message;
4883
+ }
4884
+ function isRetryableImageError(code) {
4885
+ return IMAGE_RETRYABLE_CODES.includes(code);
4886
+ }
4887
+ function asRecord(value) {
4888
+ return typeof value === "object" && value !== null ? value : {};
4889
+ }
4890
+ function str2(value) {
4891
+ return typeof value === "string" ? value : "";
4892
+ }
4893
+ function num2(value) {
4894
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4895
+ }
4896
+ function parseImageArgs(argsRaw) {
4897
+ if (typeof argsRaw !== "string" || argsRaw.trim() === "") return {};
4898
+ try {
4899
+ return asRecord(JSON.parse(argsRaw));
4900
+ } catch {
4901
+ return {};
4902
+ }
4903
+ }
4904
+ function parseImageOutcome(content) {
4905
+ if (!Array.isArray(content)) return null;
4906
+ for (const block of content) {
4907
+ const text6 = typeof block?.text === "string" ? block.text : "";
4908
+ if (text6 === "") continue;
4909
+ const trimmed = text6.trim();
4910
+ if (!trimmed.startsWith("{")) continue;
4911
+ let parsed;
4912
+ try {
4913
+ parsed = JSON.parse(trimmed);
4914
+ } catch {
4915
+ continue;
4916
+ }
4917
+ const record = asRecord(parsed);
4918
+ if (typeof record["ok"] !== "boolean") continue;
4919
+ return record;
4920
+ }
4921
+ return null;
4922
+ }
4923
+ function parseImagePathsFromText(content) {
4924
+ if (!Array.isArray(content)) return [];
4925
+ const paths = [];
4926
+ for (const block of content) {
4927
+ const text6 = typeof block?.text === "string" ? block.text : "";
4928
+ for (const match of text6.matchAll(/<path>([^<>]+)<\/path>/g)) {
4929
+ const path = (match[1] ?? "").trim();
4930
+ if (path !== "" && !paths.includes(path)) paths.push(path);
4931
+ }
4932
+ }
4933
+ return paths;
4934
+ }
4935
+ function isAbsoluteImagePath(path) {
4936
+ return path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path);
4937
+ }
4938
+ function imageFileUrl(path, baseURI) {
4939
+ if (!isAbsoluteImagePath(path)) return "";
4940
+ return new URL(`api/file?path=${encodeURIComponent(path)}`, baseURI).href;
4941
+ }
4942
+ function abbreviateHome(path, home) {
4943
+ if (home === void 0 || home === "") return path;
4944
+ const trimmed = home.replace(/[\\/]+$/, "");
4945
+ if (trimmed === "") return path;
4946
+ if (path === trimmed) return "~";
4947
+ if (path.startsWith(`${trimmed}/`) || path.startsWith(`${trimmed}\\`)) return `~${path.slice(trimmed.length)}`;
4948
+ return path;
4949
+ }
4950
+ function formatGenerationMs(ms) {
4951
+ if (ms === void 0 || ms < 0) return "";
4952
+ if (ms < 1e3) return `${ms} \u6BEB\u79D2`;
4953
+ return `${(ms / 1e3).toFixed(1)} \u79D2`;
4954
+ }
4955
+ function formatByteSize(bytes) {
4956
+ if (bytes === void 0 || bytes < 0) return "";
4957
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4958
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
4959
+ return `${bytes} B`;
4960
+ }
4961
+ function toImageCardModel(block, baseURI) {
4962
+ const settled = block?.kind === "tool-result" || block?.phase === "result";
4963
+ const errorRecord = asRecord(block?.error);
4964
+ const errorCode = str2(errorRecord["code"]);
4965
+ const state = settled ? errorCode === "interrupted" ? "stopped" : block?.isError === true ? "error" : "ok" : "running";
4966
+ const args = parseImageArgs(settled ? block?.call?.argsRaw : block?.argsRaw);
4967
+ const outcome = settled ? parseImageOutcome(block?.content) : null;
4968
+ const prompt = str2(outcome?.["prompt"]) || str2(args["prompt"]);
4969
+ const size = str2(outcome?.["size"]) || str2(args["size"]);
4970
+ const model = str2(outcome?.["model"]);
4971
+ const generationMs = num2(outcome?.["generationMs"]);
4972
+ const alt = prompt.trim() === "" ? "\u751F\u6210\u7684\u56FE\u7247" : prompt.trim();
4973
+ const outcomeFailed = outcome !== null && outcome["ok"] === false;
4974
+ const transportFailed = !outcomeFailed && (state === "error" || state === "stopped");
4975
+ const rawFailureCode = outcomeFailed ? str2(outcome["code"]) : errorCode;
4976
+ const failureCode = rawFailureCode === "" && transportFailed ? "IMAGE_UPSTREAM_ERROR" : rawFailureCode;
4977
+ const failure = outcomeFailed ? imageErrorText(failureCode, str2(outcome["message"])) : transportFailed ? imageErrorText(failureCode, str2(errorRecord["reason"]) || str2(errorRecord["name"])) : null;
4978
+ const files = [];
4979
+ if (settled && failure === null) {
4980
+ const rawFiles = Array.isArray(outcome?.["files"]) ? outcome["files"] : void 0;
4981
+ if (rawFiles !== void 0) {
4982
+ for (const entry of rawFiles) {
4983
+ const record = asRecord(entry);
4984
+ const path = str2(record["path"]);
4985
+ const url = imageFileUrl(path, baseURI);
4986
+ if (url === "") continue;
4987
+ files.push({
4988
+ path,
4989
+ url,
4990
+ alt,
4991
+ byteSize: num2(record["byteSize"]),
4992
+ mimeType: str2(record["mimeType"]),
4993
+ revisedPrompt: str2(record["revisedPrompt"]) === "" ? void 0 : str2(record["revisedPrompt"])
4994
+ });
4995
+ }
4996
+ } else {
4997
+ for (const path of parseImagePathsFromText(block?.content)) {
4998
+ const url = imageFileUrl(path, baseURI);
4999
+ if (url !== "") {
5000
+ files.push({ path, url, alt, byteSize: void 0, mimeType: "", revisedPrompt: void 0 });
5001
+ }
5002
+ }
5003
+ }
5004
+ }
5005
+ return {
5006
+ state,
5007
+ prompt,
5008
+ size,
5009
+ model,
5010
+ generationMs,
5011
+ files,
5012
+ error: failure,
5013
+ errorCode: failureCode,
5014
+ retryable: failure !== null && isRetryableImageError(failureCode)
5015
+ };
5016
+ }
5017
+ var COPY_HINTS2 = { idle: "", copied: "\u5DF2\u590D\u5236", failed: "\u590D\u5236\u5931\u8D25" };
5018
+ async function copyPromptText(text6) {
5019
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
5020
+ if (clipboard === void 0 || typeof clipboard.writeText !== "function") return false;
5021
+ try {
5022
+ await clipboard.writeText(text6);
5023
+ return true;
5024
+ } catch {
5025
+ return false;
5026
+ }
5027
+ }
5028
+ var COPY_HINT_REVERT_MS2 = 1800;
5029
+ function currentBaseURI() {
5030
+ return typeof document === "undefined" ? "" : document.baseURI;
5031
+ }
5032
+ function ImageToolCard(props) {
5033
+ const { block, toolName = IMAGE_GENERATE_TOOL_NAME, home, openFile, inspect } = props;
5034
+ const model = toImageCardModel(block, currentBaseURI());
5035
+ const [zoom, setZoom] = (0, import_react10.useState)(null);
5036
+ const [copyHint, setCopyHint] = (0, import_react10.useState)(COPY_HINTS2.idle);
5037
+ const revertTimer = (0, import_react10.useRef)(void 0);
5038
+ (0, import_react10.useEffect)(() => {
5039
+ if (typeof document === "undefined") return;
5040
+ ensureImagesStyle(document);
5041
+ }, []);
5042
+ (0, import_react10.useEffect)(() => () => {
5043
+ if (revertTimer.current !== void 0) clearTimeout(revertTimer.current);
5044
+ }, []);
5045
+ const copy = (0, import_react10.useCallback)(() => {
5046
+ void copyPromptText(model.prompt).then((ok) => {
5047
+ setCopyHint(ok ? COPY_HINTS2.copied : COPY_HINTS2.failed);
5048
+ if (revertTimer.current !== void 0) clearTimeout(revertTimer.current);
5049
+ revertTimer.current = setTimeout(() => {
5050
+ setCopyHint(COPY_HINTS2.idle);
5051
+ }, COPY_HINT_REVERT_MS2);
5052
+ });
5053
+ }, [model.prompt]);
5054
+ const summary = () => {
5055
+ if (model.state === "running") return "\u6B63\u5728\u751F\u6210\u56FE\u7247\u2026";
5056
+ if (model.error !== null && model.files.length === 0) return "\u5931\u8D25";
5057
+ const count = model.files.length === 0 ? "" : `\u5DF2\u751F\u6210 ${model.files.length} \u5F20`;
5058
+ const elapsed = formatGenerationMs(model.generationMs);
5059
+ return [count, elapsed].filter((part) => part !== "").join(" \xB7 ");
5060
+ };
5061
+ const meta = () => {
5062
+ const parts = [model.size, formatGenerationMs(model.generationMs), model.model].filter((part) => part !== "");
5063
+ return parts.join(" \xB7 ");
5064
+ };
5065
+ return (0, import_react10.createElement)(
5066
+ "div",
5067
+ {
5068
+ className: "hiwork-image-card",
5069
+ "data-hiwork-image-card": toolName,
5070
+ "data-state": model.state
5071
+ },
5072
+ (0, import_react10.createElement)(
5073
+ "div",
5074
+ { className: "hiwork-image-card-head" },
5075
+ (0, import_react10.createElement)("span", { className: "hiwork-image-card-title" }, IMAGE_CARD_TITLE),
5076
+ (0, import_react10.createElement)("span", { className: "hiwork-image-card-summary" }, summary())
5077
+ ),
5078
+ // 提示词:running 阶段(start)就要显示——那是用户唯一的「它在按我说的画」凭据。
5079
+ model.prompt === "" ? null : (0, import_react10.createElement)("p", { className: "hiwork-image-card-prompt", "data-hiwork-image-prompt": "" }, model.prompt),
5080
+ ...model.files.map((file) => (0, import_react10.createElement)(
5081
+ "figure",
5082
+ { key: file.path, className: "hiwork-image-card-figure" },
5083
+ (0, import_react10.createElement)("img", {
5084
+ className: "hiwork-image-card-img",
5085
+ // 只画绝对路径(相对路径与远端 URL 保持惰性,与平台同规则):地址为空时不渲染本块。
5086
+ src: file.url,
5087
+ alt: file.alt,
5088
+ title: file.alt,
5089
+ "data-hiwork-image-file": file.path,
5090
+ onClick: () => {
5091
+ setZoom(file);
5092
+ }
5093
+ }),
5094
+ (0, import_react10.createElement)(
5095
+ "figcaption",
5096
+ { className: "hiwork-image-card-actions" },
5097
+ (0, import_react10.createElement)("span", { className: "hiwork-image-card-path", title: file.path, "data-hiwork-image-path": "" }, abbreviateHome(file.path, home)),
5098
+ meta() === "" ? null : (0, import_react10.createElement)("span", { className: "hiwork-image-card-meta", "data-hiwork-image-meta": "" }, [meta(), formatByteSize(file.byteSize)].filter((part) => part !== "").join(" \xB7 ")),
5099
+ // 可选能力:宿主没给 openFile 就不渲染按钮(渲染一个点了没反应的控件更差)。
5100
+ typeof openFile === "function" ? (0, import_react10.createElement)(
5101
+ "button",
5102
+ {
5103
+ type: "button",
5104
+ className: "hiwork-image-card-action",
5105
+ "data-hiwork-image-open": file.path,
5106
+ onClick: () => {
5107
+ openFile(file.path);
5108
+ }
5109
+ },
5110
+ "\u6253\u5F00"
5111
+ ) : null
5112
+ )
5113
+ )),
5114
+ // 卡片级动作:复制提示词(提示词是卡片级信息,每张图复制一次即可)。
5115
+ model.prompt === "" ? null : (0, import_react10.createElement)(
5116
+ "div",
5117
+ { className: "hiwork-image-card-actions" },
5118
+ (0, import_react10.createElement)(
5119
+ "button",
5120
+ { type: "button", className: "hiwork-image-card-action", "data-hiwork-image-copy": "", onClick: copy },
5121
+ "\u590D\u5236\u63D0\u793A\u8BCD"
5122
+ ),
5123
+ copyHint === "" ? null : (0, import_react10.createElement)("span", { className: "hiwork-image-card-copy-state", "data-hiwork-image-copy-state": copyHint }, copyHint),
5124
+ inspect === void 0 ? null : (0, import_react10.createElement)(
5125
+ "button",
5126
+ { type: "button", className: "hiwork-image-card-action", "data-hiwork-image-inspect": "", onClick: inspect },
5127
+ "\u67E5\u770B\u8F68\u8FF9"
5128
+ )
5129
+ ),
5130
+ model.error === null ? null : (0, import_react10.createElement)(
5131
+ "p",
5132
+ { className: "hiwork-image-card-error", role: "alert", "data-hiwork-image-error": model.errorCode },
5133
+ model.error,
5134
+ model.retryable ? (0, import_react10.createElement)("span", { className: "hiwork-image-card-retry-hint", "data-hiwork-image-retry-hint": "" }, "\u53EF\u4EE5\u518D\u8BD5\u4E00\u6B21") : null,
5135
+ needsRelogin(model.errorCode) ? (0, import_react10.createElement)("span", { className: "hiwork-image-card-retry-hint", "data-hiwork-image-relogin-hint": "" }, IMAGE_RELOGIN_HINT) : null
5136
+ ),
5137
+ zoom === null ? null : (0, import_react10.createElement)(
5138
+ "div",
5139
+ {
5140
+ className: "hiwork-image-zoom",
5141
+ role: "dialog",
5142
+ "aria-label": zoom.alt,
5143
+ "data-hiwork-image-zoom": "",
5144
+ onClick: () => {
5145
+ setZoom(null);
5146
+ }
5147
+ },
5148
+ (0, import_react10.createElement)("img", { src: zoom.url, alt: zoom.alt }),
5149
+ (0, import_react10.createElement)(
5150
+ "button",
5151
+ {
5152
+ type: "button",
5153
+ className: "hiwork-image-zoom-close",
5154
+ "data-hiwork-image-zoom-close": "",
5155
+ onClick: () => {
5156
+ setZoom(null);
5157
+ }
5158
+ },
5159
+ "\u5173\u95ED"
5160
+ )
5161
+ )
5162
+ );
5163
+ }
5164
+ function installImageToolCard(ctx) {
5165
+ const slots = ctx.slots;
5166
+ if (slots === void 0 || typeof slots.inject !== "function") return;
5167
+ slots.inject(TOOL_VIEW_SLOT, () => slots.register(
5168
+ { name: TOOL_VIEW_SLOT, key: IMAGE_GENERATE_TOOL_NAME },
5169
+ ImageToolCard
5170
+ ));
5171
+ }
5172
+
5173
+ // src/client/session-cost.tsx
5174
+ var import_react11 = require("react");
5175
+ var import_jsx_runtime8 = require("react/jsx-runtime");
5176
+ var SESSION_COST_ENDPOINT = "estimate";
5177
+ var SESSION_COST_CACHE_TTL_MS = 5 * 6e4;
5178
+ var SESSION_COST_HINT = "\u6309\u7F51\u5173\u4EF7\u683C\u4F30\u7B97\uFF0C\u4EC5\u4F9B\u53C2\u8003\uFF1B\u4EE5\u300C\u7528\u91CF\u4E0E\u6210\u672C\u300D\u4E2D\u7684\u6743\u5A01\u8D26\u5355\u4E3A\u51C6";
5179
+ var SESSION_COST_ESTIMATED_SUFFIX = "\uFF08\u4F30\u7B97\uFF09";
5180
+ function nonNegativeInteger(value) {
5181
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
5182
+ }
5183
+ function sessionCostMatchOf(data) {
5184
+ if (typeof data !== "object" || data === null) return null;
5185
+ const turn = nonNegativeInteger(data.turn);
5186
+ if (turn === void 0) return null;
5187
+ const usage = data.tokenUsage;
5188
+ if (typeof usage !== "object" || usage === null) return null;
5189
+ const input = nonNegativeInteger(usage.uncachedInputTokens);
5190
+ const output = nonNegativeInteger(usage.outputTokens);
5191
+ if (input === void 0 || output === void 0) return null;
5192
+ const routes = usage.routes;
5193
+ if (!Array.isArray(routes) || routes.length === 0) return null;
5194
+ const pairs = [];
5195
+ const seen = /* @__PURE__ */ new Set();
5196
+ for (const entry of routes) {
5197
+ if (typeof entry !== "object" || entry === null) return null;
5198
+ const { provider, model } = entry;
5199
+ if (typeof provider !== "string" || provider === "" || typeof model !== "string" || model === "") return null;
5200
+ const key = `${provider}\0${model}`;
5201
+ if (seen.has(key)) continue;
5202
+ seen.add(key);
5203
+ pairs.push({ route: provider, model });
5204
+ }
5205
+ if (pairs.length === 0) return null;
5206
+ const cacheRead = nonNegativeInteger(usage.cacheReadTokens);
5207
+ const cacheWrite = nonNegativeInteger(usage.cacheWriteTokens);
5208
+ if (input === 0 && output === 0 && (cacheRead ?? 0) === 0 && (cacheWrite ?? 0) === 0) return null;
5209
+ const first = pairs[0];
5210
+ if (first === void 0) return null;
5211
+ const at = rfc3339Of(data.time);
5212
+ const item = {
5213
+ route: first.route,
5214
+ model: first.model,
5215
+ input_tokens: input,
5216
+ output_tokens: output,
5217
+ // 桶缺失表示「并非每次尝试都上报了该桶」——原样省略,绝不猜 0。
5218
+ ...cacheRead === void 0 ? {} : { cache_read_tokens: cacheRead },
5219
+ ...cacheWrite === void 0 ? {} : { cache_write_tokens: cacheWrite },
5220
+ ...at === void 0 ? {} : { at }
5221
+ };
5222
+ const ambiguous = pairs.length > 1 || cacheRead === void 0 || cacheWrite === void 0;
5223
+ return { items: [item], turn, ambiguous };
5224
+ }
5225
+ function rfc3339Of(time) {
5226
+ if (typeof time !== "number" || !Number.isFinite(time) || time <= 0) return void 0;
5227
+ const date = new Date(time);
5228
+ return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
5229
+ }
5230
+ function closingTurnTailData(snapshot, messageId) {
5231
+ for (const key of snapshot.order) {
5232
+ const node = snapshot.nodes.get(key);
5233
+ if (node === void 0 || node.kind !== "turn-tail") continue;
5234
+ const data = node.data;
5235
+ if (data === void 0) continue;
5236
+ if (data.closing?.finalNode?.messageId === messageId) return data;
5237
+ }
5238
+ return void 0;
5239
+ }
5240
+ function sessionCostKey(sessionId, match) {
5241
+ return `${sessionId}\0${match.turn}\0${JSON.stringify(match.items)}`;
5242
+ }
5243
+ function normalizeSessionCost(value) {
5244
+ if (typeof value !== "object" || value === null) return void 0;
5245
+ const record = value;
5246
+ if (record.priced !== true) return void 0;
5247
+ const cost = record.cost;
5248
+ if (typeof cost !== "number" || !Number.isFinite(cost) || cost < 0) return void 0;
5249
+ return { cost, channelResolved: record.channelResolved === true };
5250
+ }
5251
+ var SessionCostStore = class {
5252
+ constructor(rpc, options = {}) {
5253
+ this.rpc = rpc;
5254
+ this.now = options.now ?? (() => Date.now());
5255
+ this.ttlMs = options.ttlMs ?? SESSION_COST_CACHE_TTL_MS;
5256
+ }
5257
+ entries = /* @__PURE__ */ new Map();
5258
+ pending = /* @__PURE__ */ new Map();
5259
+ now;
5260
+ ttlMs;
5261
+ /** 当前缓存条目数(测试断言用)。 */
5262
+ get size() {
5263
+ return this.entries.size;
5264
+ }
5265
+ /** 清空缓存(登出 / 切换账号)。 */
5266
+ clear() {
5267
+ this.entries.clear();
5268
+ }
5269
+ async estimate(input) {
5270
+ const key = sessionCostKey(input.sessionId, { turn: input.turn, items: [...input.items] });
5271
+ const cached = this.entries.get(key);
5272
+ if (cached !== void 0 && this.now() - cached.fetchedAt < this.ttlMs) return cached.result;
5273
+ const inflight = this.pending.get(key);
5274
+ if (inflight !== void 0) return await inflight;
5275
+ const task = this.request(key, input);
5276
+ this.pending.set(key, task);
5277
+ try {
5278
+ return await task;
5279
+ } finally {
5280
+ this.pending.delete(key);
3318
5281
  }
3319
- };
3320
- const pending = () => {
3321
- const sidebar = sidebarRoot();
3322
- if (sidebar === void 0) return true;
3323
- if (menuElement === void 0 || !sidebar.contains(menuElement)) return true;
3324
- if (registry.activeId() === null) return false;
3325
- const host = conversationContainer();
3326
- if (host === void 0) return true;
3327
- return container === void 0 || !host.contains(container);
3328
- };
3329
- const scheduleRetry = () => {
3330
- if (disposed || retryTimer !== void 0 || retries >= maxRetries) return;
3331
- retryTimer = setTimeout(() => {
3332
- retryTimer = void 0;
3333
- retries += 1;
3334
- sync();
3335
- if (pending()) scheduleRetry();
3336
- }, interval);
3337
- };
3338
- const onDocumentClickCapture = (event) => {
3339
- if (registry.activeId() === null) return;
3340
- const target = event.target;
3341
- if (!(target instanceof Element)) return;
3342
- if (target.closest(`[${FEATURE_ROOT_ATTRIBUTE}]`) !== null) return;
3343
- if (target.closest(`[${MENU_ATTRIBUTE}]`) !== null) return;
3344
- if (target.closest(SESSION_ROW_SELECTOR) === null) return;
3345
- registry.openNative();
3346
- };
3347
- const unsubscribeRegistry = registry.subscribe(() => {
3348
- sync();
3349
- });
3350
- let unsubscribeLocale;
3351
- if (options.subscribeLocale !== void 0) {
5282
+ }
5283
+ async request(key, input) {
3352
5284
  try {
3353
- unsubscribeLocale = options.subscribeLocale(() => {
3354
- menuSignature = "";
3355
- rebuildMenuItems();
3356
- });
3357
- } catch (error) {
3358
- console.warn("hiwork feature center: locale subscription unavailable", error);
5285
+ const raw = await this.rpc.call(USAGE_CHANNEL, SESSION_COST_ENDPOINT, { items: input.items }, input.signal);
5286
+ const result = normalizeSessionCost(unwrapUsageResult(raw));
5287
+ if (result !== void 0) this.entries.set(key, { result, fetchedAt: this.now() });
5288
+ return result;
5289
+ } catch {
5290
+ return void 0;
3359
5291
  }
3360
5292
  }
3361
- const observer = new MutationObserver(() => {
3362
- sync();
3363
- });
3364
- observer.observe(document.body, { childList: true, subtree: true });
3365
- document.addEventListener("click", onDocumentClickCapture, true);
3366
- const sync = () => {
3367
- if (disposed || !isActiveOwner()) return;
3368
- syncMenu();
3369
- syncTakeover();
3370
- if (pending()) scheduleRetry();
3371
- };
3372
- sync();
3373
- return {
3374
- dispose: () => {
3375
- if (disposed) return;
5293
+ };
5294
+ var costStyle = {
5295
+ // 字号/颜色与同一行的「用量」「用时」胶囊同源变量,保证在动作行里不显得突兀。
5296
+ color: textTertiary,
5297
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
5298
+ lineHeight: "18px",
5299
+ whiteSpace: "nowrap",
5300
+ cursor: "default"
5301
+ };
5302
+ function SessionCost({ messageId, sessionId, useChat, store }) {
5303
+ const data = useChat((snapshot) => closingTurnTailData(snapshot, messageId));
5304
+ const match = sessionCostMatchOf(data);
5305
+ const key = match === null ? null : sessionCostKey(sessionId, match);
5306
+ const [shown, setShown] = (0, import_react11.useState)(void 0);
5307
+ (0, import_react11.useEffect)(() => {
5308
+ if (match === null || key === null) return;
5309
+ let disposed = false;
5310
+ const controller = new AbortController();
5311
+ void store.estimate({ sessionId, turn: match.turn, items: match.items, signal: controller.signal }).then((value) => {
5312
+ if (!disposed) setShown({ key, result: value });
5313
+ }).catch(() => {
5314
+ if (!disposed) setShown({ key, result: void 0 });
5315
+ });
5316
+ return () => {
3376
5317
  disposed = true;
3377
- observer.disconnect();
3378
- if (retryTimer !== void 0) clearTimeout(retryTimer);
3379
- retryTimer = void 0;
3380
- unsubscribeRegistry();
3381
- unsubscribeLocale?.();
3382
- document.removeEventListener("click", onDocumentClickCapture, true);
3383
- document.removeEventListener("scroll", hideTooltip, true);
3384
- hideTooltip();
3385
- const tooltip = tooltipElement;
3386
- tooltipElement = void 0;
3387
- if (tooltip !== void 0 && tooltip.getAttribute(OWNER_ATTRIBUTE) === owner) tooltip.remove();
3388
- clearTakeover();
3389
- const element = menuElement;
3390
- menuElement = void 0;
3391
- if (element !== void 0) {
3392
- element.removeEventListener("click", onMenuClick);
3393
- if (element.getAttribute(OWNER_ATTRIBUTE) === owner) element.remove();
3394
- }
3395
- if (!isActiveOwner()) return;
3396
- activeOwner = null;
3397
- for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_HIDDEN_ATTRIBUTE}]`))) {
3398
- hidden.removeAttribute(NATIVE_HIDDEN_ATTRIBUTE);
3399
- }
3400
- if (style.getAttribute(OWNER_ATTRIBUTE) === owner) style.remove();
3401
- },
3402
- openNativeHome: () => {
3403
- renderContext.openNativeHome();
3404
- }
5318
+ controller.abort();
5319
+ };
5320
+ }, [store, key]);
5321
+ if (match === null || key === null || shown === void 0 || shown.key !== key || shown.result === void 0) return null;
5322
+ const result = shown.result;
5323
+ const estimated = !result.channelResolved || match.ambiguous;
5324
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: costStyle, "data-hiwork-session-cost": estimated ? "estimated" : "exact", title: SESSION_COST_HINT, children: `\u2248 ${formatYuan(result.cost)}${estimated ? SESSION_COST_ESTIMATED_SUFFIX : ""}` });
5325
+ }
5326
+
5327
+ // src/client/account-cache.ts
5328
+ function accountIdentityOf(state) {
5329
+ return `${state.username ?? ""}\0${state.activeKeyId ?? ""}`;
5330
+ }
5331
+ function observeAccountChange(source, onChange) {
5332
+ let identity = accountIdentityOf(source.getSnapshot());
5333
+ const unsubscribe = source.subscribe(() => {
5334
+ const next = accountIdentityOf(source.getSnapshot());
5335
+ if (next === identity) return;
5336
+ identity = next;
5337
+ onChange();
5338
+ });
5339
+ return () => {
5340
+ unsubscribe();
3405
5341
  };
3406
5342
  }
3407
5343
 
@@ -4405,17 +6341,17 @@ var PmsTodoStore = class {
4405
6341
  };
4406
6342
 
4407
6343
  // src/client/voice/MicButton.tsx
4408
- var import_react8 = require("react");
4409
- var import_jsx_runtime8 = require("react/jsx-runtime");
6344
+ var import_react12 = require("react");
6345
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4410
6346
  function MicIcon({ size }) {
4411
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
4412
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "9", y: "2.75", width: "6", height: "11.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
4413
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M5.5 11.5a6.5 6.5 0 0 0 13 0", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
4414
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 18v3.25", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
6347
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
6348
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { x: "9", y: "2.75", width: "6", height: "11.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
6349
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M5.5 11.5a6.5 6.5 0 0 0 13 0", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
6350
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 18v3.25", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
4415
6351
  ] });
4416
6352
  }
4417
6353
  function MicButton({ open }) {
4418
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
6354
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4419
6355
  "button",
4420
6356
  {
4421
6357
  type: "button",
@@ -4424,13 +6360,13 @@ function MicButton({ open }) {
4424
6360
  "aria-label": "\u8BED\u97F3\u8F93\u5165",
4425
6361
  "aria-haspopup": "dialog",
4426
6362
  onClick: () => open(),
4427
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MicIcon, { size: 18 })
6363
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MicIcon, { size: 18 })
4428
6364
  }
4429
6365
  );
4430
6366
  }
4431
6367
 
4432
6368
  // src/client/voice/entry.ts
4433
- var import_react10 = require("react");
6369
+ var import_react14 = require("react");
4434
6370
  var import_client3 = require("react-dom/client");
4435
6371
 
4436
6372
  // src/client/voice/composer.ts
@@ -4486,7 +6422,7 @@ function insertDraftText(sessions, text6, sessionId) {
4486
6422
  }
4487
6423
 
4488
6424
  // src/client/voice/VoiceOverlay.tsx
4489
- var import_react9 = require("react");
6425
+ var import_react13 = require("react");
4490
6426
 
4491
6427
  // src/client/voice/recording.ts
4492
6428
  var RecordingError = class extends Error {
@@ -4769,7 +6705,7 @@ function createVoiceRuntime(rpc) {
4769
6705
  }
4770
6706
 
4771
6707
  // src/client/voice/VoiceOverlay.tsx
4772
- var import_jsx_runtime9 = require("react/jsx-runtime");
6708
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4773
6709
  var BAR_COUNT = 28;
4774
6710
  var LEVEL_INTERVAL_MS = 80;
4775
6711
  var TICK_INTERVAL_MS = 200;
@@ -4783,25 +6719,25 @@ function rmsToLevel(rms) {
4783
6719
  return Math.min(1, Math.max(0, rms) * 6);
4784
6720
  }
4785
6721
  function MicIcon2({ size }) {
4786
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
4787
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { x: "9", y: "2.75", width: "6", height: "11.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
4788
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M5.5 11.5a6.5 6.5 0 0 0 13 0", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
4789
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 18v3.25", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
6722
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
6723
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { x: "9", y: "2.75", width: "6", height: "11.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
6724
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M5.5 11.5a6.5 6.5 0 0 0 13 0", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
6725
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 18v3.25", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
4790
6726
  ] });
4791
6727
  }
4792
6728
  function VoiceOverlay(props) {
4793
6729
  const { runtime, onClose, insertDraft } = props;
4794
- const [phase, setPhase] = (0, import_react9.useState)("checking");
4795
- const [error, setError] = (0, import_react9.useState)(void 0);
4796
- const [levels, setLevels] = (0, import_react9.useState)(() => Array.from({ length: BAR_COUNT }, () => 0.06));
4797
- const [elapsed, setElapsed] = (0, import_react9.useState)(0);
4798
- const [maxDuration, setMaxDuration] = (0, import_react9.useState)(60);
4799
- const [copied, setCopied] = (0, import_react9.useState)(false);
4800
- const recordingRef = (0, import_react9.useRef)(void 0);
4801
- const wavRef = (0, import_react9.useRef)(void 0);
4802
- const startedAtRef = (0, import_react9.useRef)(0);
4803
- const abortRef = (0, import_react9.useRef)(void 0);
4804
- const disposedRef = (0, import_react9.useRef)(false);
6730
+ const [phase, setPhase] = (0, import_react13.useState)("checking");
6731
+ const [error, setError] = (0, import_react13.useState)(void 0);
6732
+ const [levels, setLevels] = (0, import_react13.useState)(() => Array.from({ length: BAR_COUNT }, () => 0.06));
6733
+ const [elapsed, setElapsed] = (0, import_react13.useState)(0);
6734
+ const [maxDuration, setMaxDuration] = (0, import_react13.useState)(60);
6735
+ const [copied, setCopied] = (0, import_react13.useState)(false);
6736
+ const recordingRef = (0, import_react13.useRef)(void 0);
6737
+ const wavRef = (0, import_react13.useRef)(void 0);
6738
+ const startedAtRef = (0, import_react13.useRef)(0);
6739
+ const abortRef = (0, import_react13.useRef)(void 0);
6740
+ const disposedRef = (0, import_react13.useRef)(false);
4805
6741
  const failWith = (message, opts = {}) => {
4806
6742
  if (disposedRef.current) return;
4807
6743
  setPhase("error");
@@ -4898,7 +6834,7 @@ function VoiceOverlay(props) {
4898
6834
  failWith(err instanceof Error ? err.message : "\u5F55\u97F3\u5904\u7406\u5931\u8D25\u3002", { holdWav: false });
4899
6835
  });
4900
6836
  };
4901
- (0, import_react9.useEffect)(() => {
6837
+ (0, import_react13.useEffect)(() => {
4902
6838
  disposedRef.current = false;
4903
6839
  let cancelled = false;
4904
6840
  runtime.status().then((status) => {
@@ -4921,7 +6857,7 @@ function VoiceOverlay(props) {
4921
6857
  recordingRef.current = void 0;
4922
6858
  };
4923
6859
  }, []);
4924
- (0, import_react9.useEffect)(() => {
6860
+ (0, import_react13.useEffect)(() => {
4925
6861
  if (phase !== "recording") return;
4926
6862
  const levelTimer = window.setInterval(() => {
4927
6863
  const rms = recordingRef.current?.amplitude() ?? 0;
@@ -4935,7 +6871,7 @@ function VoiceOverlay(props) {
4935
6871
  window.clearInterval(clockTimer);
4936
6872
  };
4937
6873
  }, [phase]);
4938
- (0, import_react9.useEffect)(() => {
6874
+ (0, import_react13.useEffect)(() => {
4939
6875
  if (phase !== "recording") return;
4940
6876
  if (elapsed < maxDuration) return;
4941
6877
  finish();
@@ -4944,7 +6880,7 @@ function VoiceOverlay(props) {
4944
6880
  abortRef.current?.abort();
4945
6881
  onClose();
4946
6882
  };
4947
- (0, import_react9.useEffect)(() => {
6883
+ (0, import_react13.useEffect)(() => {
4948
6884
  const onKey = (event) => {
4949
6885
  if (event.key !== "Escape") return;
4950
6886
  event.preventDefault();
@@ -4962,21 +6898,21 @@ function VoiceOverlay(props) {
4962
6898
  const idle = phase === "checking" || phase === "requesting" || phase === "transcribing";
4963
6899
  const canRetryTranscribe = error?.holdWav === true && error.recognizedText === void 0;
4964
6900
  const canRestart = error?.canRestart === true;
4965
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6901
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4966
6902
  "div",
4967
6903
  {
4968
6904
  className: "hiwv-backdrop",
4969
6905
  onMouseDown: (event) => {
4970
6906
  if (event.target === event.currentTarget && !idle) handleClose();
4971
6907
  },
4972
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-panel", role: "dialog", "aria-modal": "true", "aria-label": "\u8BED\u97F3\u8F93\u5165", children: [
4973
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-head", children: [
4974
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "hiwv-title", children: "\u8BED\u97F3\u8F93\u5165" }),
4975
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "hiwv-hint", children: recording ? `\u5DF2\u5F55 ${formatClock(elapsed)} / \u4E0A\u9650 ${formatClock(maxDuration)}` : `\u5355\u6B21\u6700\u957F ${formatClock(maxDuration)}` })
6908
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-panel", role: "dialog", "aria-modal": "true", "aria-label": "\u8BED\u97F3\u8F93\u5165", children: [
6909
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-head", children: [
6910
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "hiwv-title", children: "\u8BED\u97F3\u8F93\u5165" }),
6911
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "hiwv-hint", children: recording ? `\u5DF2\u5F55 ${formatClock(elapsed)} / \u4E0A\u9650 ${formatClock(maxDuration)}` : `\u5355\u6B21\u6700\u957F ${formatClock(maxDuration)}` })
4976
6912
  ] }),
4977
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `hiwv-stage${recording ? "" : " hiwv-stageIdle"}`, children: recording || phase === "transcribing" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
4978
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `hiwv-orb${phase === "transcribing" ? " hiwv-orbBusy" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MicIcon2, { size: 30 }) }),
4979
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-bars", "aria-hidden": "true", children: levels.map((level, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6913
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: `hiwv-stage${recording ? "" : " hiwv-stageIdle"}`, children: recording || phase === "transcribing" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
6914
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: `hiwv-orb${phase === "transcribing" ? " hiwv-orbBusy" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MicIcon2, { size: 30 }) }),
6915
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "hiwv-bars", "aria-hidden": "true", children: levels.map((level, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4980
6916
  "i",
4981
6917
  {
4982
6918
  style: { height: `${Math.round(8 + level * 46)}px` },
@@ -4984,19 +6920,19 @@ function VoiceOverlay(props) {
4984
6920
  },
4985
6921
  index
4986
6922
  )) }),
4987
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-status", children: phase === "transcribing" ? "\u6B63\u5728\u8BC6\u522B\u2026" : "\u6B63\u5728\u8046\u542C\uFF0C\u8BB2\u5B8C\u540E\u70B9\u300C\u5B8C\u6210\u300D" })
4988
- ] }) : phase === "checking" || phase === "requesting" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-status", children: "\u6B63\u5728\u51C6\u5907\u9EA6\u514B\u98CE\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-errorBox", children: [
4989
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-errorMsg", children: error?.message ?? "\u51FA\u4E86\u70B9\u95EE\u9898\u3002" }),
4990
- error?.recognizedText !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-recognized", children: [
4991
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hiwv-recognizedText", children: error.recognizedText }),
4992
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnSecondary", onClick: copyRecognized, children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6587\u672C" })
6923
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "hiwv-status", children: phase === "transcribing" ? "\u6B63\u5728\u8BC6\u522B\u2026" : "\u6B63\u5728\u8046\u542C\uFF0C\u8BB2\u5B8C\u540E\u70B9\u300C\u5B8C\u6210\u300D" })
6924
+ ] }) : phase === "checking" || phase === "requesting" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "hiwv-status", children: "\u6B63\u5728\u51C6\u5907\u9EA6\u514B\u98CE\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-errorBox", children: [
6925
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "hiwv-errorMsg", children: error?.message ?? "\u51FA\u4E86\u70B9\u95EE\u9898\u3002" }),
6926
+ error?.recognizedText !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-recognized", children: [
6927
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "hiwv-recognizedText", children: error.recognizedText }),
6928
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnSecondary", onClick: copyRecognized, children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6587\u672C" })
4993
6929
  ] })
4994
6930
  ] }) }),
4995
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hiwv-actions", children: [
4996
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnSecondary", onClick: handleClose, children: "\u53D6\u6D88" }),
4997
- recording && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnPrimary", onClick: finish, children: "\u5B8C\u6210" }),
4998
- phase === "error" && canRetryTranscribe && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnPrimary", onClick: transcribeWav, children: "\u91CD\u8BD5\u8BC6\u522B" }),
4999
- phase === "error" && canRestart && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6931
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-actions", children: [
6932
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnSecondary", onClick: handleClose, children: "\u53D6\u6D88" }),
6933
+ recording && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnPrimary", onClick: finish, children: "\u5B8C\u6210" }),
6934
+ phase === "error" && canRetryTranscribe && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnPrimary", onClick: transcribeWav, children: "\u91CD\u8BD5\u8BC6\u522B" }),
6935
+ phase === "error" && canRestart && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5000
6936
  "button",
5001
6937
  {
5002
6938
  type: "button",
@@ -5060,7 +6996,7 @@ function createOverlayController(deps) {
5060
6996
  doc.body.appendChild(container);
5061
6997
  root = (0, import_client3.createRoot)(container);
5062
6998
  root.render(
5063
- (0, import_react10.createElement)(VoiceOverlay, {
6999
+ (0, import_react14.createElement)(VoiceOverlay, {
5064
7000
  runtime: deps.runtime,
5065
7001
  onClose: close,
5066
7002
  // 会话 id 缺省(拿不到槽位注入)时由 insertDraftText 退到当前打开会话;
@@ -5133,7 +7069,7 @@ async function installVoiceInput(deps) {
5133
7069
  }
5134
7070
 
5135
7071
  // src/client/index.ts
5136
- var inject = ["slots", "locale", "remote", "remote.credentials", "settingsScope", "connection"];
7072
+ var inject = ["slots", "locale", "remote", "remote.credentials", "configForms", "connection", "layout"];
5137
7073
  var INTERACTION_STYLE_ID = "hiwork-core-interaction-styles";
5138
7074
  function injectInteractionStyles() {
5139
7075
  if (typeof document === "undefined") return;
@@ -5146,12 +7082,12 @@ function injectInteractionStyles() {
5146
7082
  }
5147
7083
  function apply(ctx) {
5148
7084
  injectInteractionStyles();
5149
- const scope = ctx.settingsScope.bind({ namespace: AI_ROUTER_NS });
7085
+ const form = ctx.configForms.get(AI_ROUTER_NS);
5150
7086
  const storeCredential = async (ref, value) => {
5151
7087
  const response = await ctx.remote.credentials.set(ref, value);
5152
7088
  if (!response.ok) throw new Error(response.error.message);
5153
7089
  };
5154
- const controller = new AiRouterCardController(scope, storeCredential);
7090
+ const controller = new AiRouterCardController(form, storeCredential);
5155
7091
  ctx.slots.inject("settings.section", () => ctx.slots.register({
5156
7092
  name: "settings.section",
5157
7093
  id: AI_ROUTER_NS,
@@ -5159,12 +7095,6 @@ function apply(ctx) {
5159
7095
  label: () => AI_ROUTER_SECTION_LABEL,
5160
7096
  inject: () => controller.inject()
5161
7097
  }, AiRouterSection));
5162
- ctx.slots.inject("settings.section", () => ctx.slots.register({
5163
- name: "settings.section",
5164
- id: "hiwork-about",
5165
- order: Number.MAX_SAFE_INTEGER,
5166
- label: () => "\u5173\u4E8E"
5167
- }, AboutSection));
5168
7098
  const connection = ctx.get("connection");
5169
7099
  if (connection === void 0) throw new Error("connection service unavailable");
5170
7100
  const usage = new UsageDataStore(connection.rpc);
@@ -5175,6 +7105,13 @@ function apply(ctx) {
5175
7105
  const managedWorkbench = new ManagedWorkbenchStore(connection.rpc);
5176
7106
  const pmsTodo = new PmsTodoStore(connection.rpc);
5177
7107
  const appVersion = createAppVersion(connection.rpc);
7108
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
7109
+ name: "settings.section",
7110
+ id: "hiwork-about",
7111
+ order: Number.MAX_SAFE_INTEGER,
7112
+ label: () => "\u5173\u4E8E",
7113
+ inject: () => ({ appVersion })
7114
+ }, AboutSection));
5178
7115
  const pluginUpdates = createPluginUpdates(connection.rpc);
5179
7116
  ctx.slots.inject("settings.section", () => ctx.slots.register({
5180
7117
  name: "settings.section",
@@ -5183,6 +7120,57 @@ function apply(ctx) {
5183
7120
  label: () => "\u7528\u91CF\u4E0E\u6210\u672C",
5184
7121
  inject: () => ({ hooks: controller.inject().hooks, usage })
5185
7122
  }, UsageSection));
7123
+ const featureRegistry = createFeatureRegistry();
7124
+ const images = new ImageGallerySource(connection.rpc);
7125
+ installImagesFeature(ctx, featureRegistry, images);
7126
+ installImageToolCard(ctx);
7127
+ const PLUGINS_PANEL_ID = "plugins";
7128
+ const asPanelId = (id) => id;
7129
+ const layoutFace = () => {
7130
+ try {
7131
+ return ctx.layout ?? void 0;
7132
+ } catch {
7133
+ return void 0;
7134
+ }
7135
+ };
7136
+ const nativePanel = {
7137
+ pluginsAvailable: () => {
7138
+ const layout = layoutFace();
7139
+ if (layout === void 0) return false;
7140
+ try {
7141
+ return ctx.slots.entries("main").some((entry) => entry.options.key === PLUGINS_PANEL_ID);
7142
+ } catch {
7143
+ return false;
7144
+ }
7145
+ },
7146
+ anyActive: () => (layoutFace()?.panelInfo.getSnapshot().activePanelId ?? null) !== null,
7147
+ pluginsActive: () => layoutFace()?.panelInfo.getSnapshot().activePanelId === PLUGINS_PANEL_ID,
7148
+ openPlugins: () => {
7149
+ layoutFace()?.selectPanel(asPanelId(PLUGINS_PANEL_ID));
7150
+ },
7151
+ returnToConversation: () => {
7152
+ layoutFace()?.selectPanel(null);
7153
+ },
7154
+ subscribe: (listener) => {
7155
+ const layout = layoutFace();
7156
+ if (layout === void 0) return () => void 0;
7157
+ const offSlots = ctx.slots.subscribe("main", listener);
7158
+ const offPanel = layout.panelInfo.subscribe(listener);
7159
+ return () => {
7160
+ offSlots();
7161
+ offPanel();
7162
+ };
7163
+ }
7164
+ };
7165
+ const additionalFeatures = () => nativePanel.pluginsAvailable() ? [nativePluginsMenuItem()] : [];
7166
+ const menuStore = new MenuStore(form, featureRegistry, additionalFeatures);
7167
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
7168
+ name: "settings.section",
7169
+ id: HIWORK_MENU_NS,
7170
+ order: 3,
7171
+ label: () => MENU_SECTION_LABEL,
7172
+ inject: () => menuStore.inject()
7173
+ }, MenuSection));
5186
7174
  ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
5187
7175
  name: "conversation.chat.assistant-actions",
5188
7176
  id: "hiwork-session-cost",
@@ -5228,6 +7216,7 @@ function apply(ctx) {
5228
7216
  ctx.effect(() => observeAccountChange(controller.inject().hooks.aiRouterCard, () => {
5229
7217
  usage.clear();
5230
7218
  sessionCost.clear();
7219
+ dailyQuotes.clear();
5231
7220
  managedMcp.clear();
5232
7221
  managedSkills.clear();
5233
7222
  managedWorkbench.clear();
@@ -5248,12 +7237,11 @@ function apply(ctx) {
5248
7237
  id: AI_ROUTER_NS,
5249
7238
  inject: () => ({ ...controller.inject(), pluginUpdates })
5250
7239
  }, UserBadge));
5251
- ctx.effect(() => mountLoginGate(scope, storeCredential), "hiwork-core: login gate");
7240
+ ctx.effect(() => mountLoginGate(form, storeCredential), "hiwork-core: login gate");
5252
7241
  ctx.effect(() => observeHeroDecorations(document, dailyQuotes), "hiwork-core: hero decorations");
5253
- installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench, pmsTodo);
7242
+ installFeatureCenter(ctx, featureRegistry, menuStore, managedMcp, managedSkills, managedWorkbench, pmsTodo, additionalFeatures, nativePanel);
5254
7243
  }
5255
- function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench, pmsTodo) {
5256
- const featureRegistry = createFeatureRegistry();
7244
+ function installFeatureCenter(ctx, featureRegistry, menuStore, managedMcp, managedSkills, managedWorkbench, pmsTodo, additionalFeatures, nativePanel) {
5257
7245
  let center;
5258
7246
  const resolveSessions = () => ctx.get("sessions");
5259
7247
  const locale = ctx.get("locale");
@@ -5332,12 +7320,20 @@ function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench,
5332
7320
  openNativeSession: (sessionId) => {
5333
7321
  resolveSessions()?.open(sessionId);
5334
7322
  },
5335
- ...locale === void 0 ? {} : { subscribeLocale: (listener) => locale.subscribe(listener) }
7323
+ additionalFeatures,
7324
+ nativePanel,
7325
+ ...locale === void 0 ? {} : { subscribeLocale: (listener) => locale.subscribe(listener) },
7326
+ // 侧栏分组与顺序(`hiwork-menu`,见 menu-preferences.ts / menu-store.ts):
7327
+ // `preferences` 每次重建菜单时现取,`subscribePreferences` 让设置页里的一次改动
7328
+ // 立刻反映到侧栏,不需要重启。
7329
+ preferences: () => menuStore.preferences(),
7330
+ subscribePreferences: (listener) => menuStore.subscribePreferences(listener)
5336
7331
  });
5337
7332
  return () => {
5338
7333
  center?.dispose();
5339
7334
  center = void 0;
5340
7335
  };
5341
7336
  }, "hiwork-core: feature center");
7337
+ ctx.effect(() => () => menuStore.dispose(), "hiwork-core: menu preferences");
5342
7338
  }
5343
7339
  return module.exports; } });