hiwork-core 0.0.30 → 0.0.35

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 (38) hide show
  1. package/cordis.patch.yml +15 -1
  2. package/lib/ai-router-client.js +394 -2
  3. package/lib/client-policy.js +2 -2
  4. package/lib/client.js +2413 -439
  5. package/lib/image-rpc.js +306 -0
  6. package/lib/imagegen.js +424 -0
  7. package/lib/index.js +19212 -7694
  8. package/lib/knowledge-config.js +91 -1
  9. package/lib/provision-session.js +10 -2
  10. package/lib/tool-names.js +10 -0
  11. package/lib/tools.js +205 -0
  12. package/lib/types/ai-router-client.d.ts +255 -2
  13. package/lib/types/client/about-section.d.ts +27 -7
  14. package/lib/types/client/controller.d.ts +17 -9
  15. package/lib/types/client/feature-center.d.ts +46 -1
  16. package/lib/types/client/feature-registry.d.ts +36 -3
  17. package/lib/types/client/image-cards.d.ts +218 -0
  18. package/lib/types/client/image-styles.d.ts +9 -0
  19. package/lib/types/client/images-data.d.ts +127 -0
  20. package/lib/types/client/images-page.d.ts +97 -0
  21. package/lib/types/client/index.d.ts +0 -1
  22. package/lib/types/client/login-gate.d.ts +2 -2
  23. package/lib/types/client/logo.d.ts +7 -2
  24. package/lib/types/client/menu-preferences.d.ts +163 -0
  25. package/lib/types/client/menu-section.d.ts +5 -0
  26. package/lib/types/client/menu-store.d.ts +101 -0
  27. package/lib/types/client/styles.d.ts +16 -0
  28. package/lib/types/client/types.d.ts +9 -0
  29. package/lib/types/image-rpc.d.ts +142 -0
  30. package/lib/types/imagegen.d.ts +241 -0
  31. package/lib/types/index.d.ts +70 -8
  32. package/lib/types/knowledge-config.d.ts +50 -2
  33. package/lib/types/tool-names.d.ts +11 -0
  34. package/lib/types/tools.d.ts +80 -0
  35. package/lib/types/user-agent.d.ts +31 -0
  36. package/lib/user-agent.js +54 -0
  37. package/lib/web-search.js +2 -2
  38. 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,20 +1784,94 @@ 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";
@@ -1551,65 +2070,6 @@ function removeStaleHeroGreeting(doc) {
1551
2070
  for (const stale of Array.from(doc.querySelectorAll("[data-hiwork-hero-greeting]"))) stale.remove();
1552
2071
  }
1553
2072
 
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
2073
  // src/client/daily-card.ts
1614
2074
  var DAILY_CARD_STYLE_ID = "dcu-daily-card-style";
1615
2075
  var DAILY_MAIN_FONT_SIZE_MAX = 64;
@@ -1991,7 +2451,7 @@ function observeAboutNavIcon(doc = document) {
1991
2451
  }
1992
2452
 
1993
2453
  // src/client/login-gate.tsx
1994
- var import_react4 = require("react");
2454
+ var import_react5 = require("react");
1995
2455
  var import_client = require("react-dom/client");
1996
2456
  var import_jsx_runtime5 = require("react/jsx-runtime");
1997
2457
  var INK = "#111b2f";
@@ -2105,12 +2565,12 @@ function loginGateVisible(snapshot) {
2105
2565
  return snapshot.value?.lastLogin === void 0 || errorCode === "SESSION_EXPIRED";
2106
2566
  }
2107
2567
  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)(() => {
2568
+ const [username, setUsername] = (0, import_react5.useState)(props.initialUsername ?? "");
2569
+ const [password, setPassword] = (0, import_react5.useState)("");
2570
+ const [rememberPassword, setRememberPassword] = (0, import_react5.useState)(props.initialRememberPassword === true);
2571
+ const [submitError, setSubmitError] = (0, import_react5.useState)();
2572
+ const [formOpen, setFormOpen] = (0, import_react5.useState)(false);
2573
+ (0, import_react5.useEffect)(() => {
2114
2574
  openAuthorizeUrl(props.browserLoginUrl);
2115
2575
  }, [props.browserLoginUrl]);
2116
2576
  const showBrowserLogin = props.loginMode !== "form";
@@ -2123,7 +2583,7 @@ function LoginGateModal(props) {
2123
2583
  const submit = () => {
2124
2584
  if (loginDisabled) return;
2125
2585
  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"));
2586
+ 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
2587
  };
2128
2588
  const onSubmit = (event) => {
2129
2589
  event.preventDefault();
@@ -2134,9 +2594,9 @@ function LoginGateModal(props) {
2134
2594
  /* @__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
2595
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: browserActionsStyle, children: [
2136
2596
  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" })
2597
+ /* @__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
2598
  ] })
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;
2599
+ ] }) : /* @__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
2600
  const form = formVisible ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit, style: { display: "flex", flexDirection: "column", gap: 14 }, children: [
2141
2601
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: fieldStyle, children: [
2142
2602
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { htmlFor: "hiwork-core-username", style: labelStyle, children: "\u7528\u6237\u540D" }),
@@ -2151,7 +2611,7 @@ function LoginGateModal(props) {
2151
2611
  "\u8BB0\u4F4F\u5BC6\u7801\uFF0C\u4E0B\u6B21\u81EA\u52A8\u767B\u5F55"
2152
2612
  ] }),
2153
2613
  /* @__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
2614
+ 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
2615
  ] }) : !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
2616
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: overlayStyle, children: [
2157
2617
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OceanWaves, {}),
@@ -2192,7 +2652,7 @@ function LoginGateModal(props) {
2192
2652
  ] })
2193
2653
  ] });
2194
2654
  }
2195
- function mountLoginGate(scope, storeCredential) {
2655
+ function mountLoginGate(form, storeCredential) {
2196
2656
  let root;
2197
2657
  let gateContainer;
2198
2658
  const appRoot = typeof document === "undefined" ? null : document.getElementById("root");
@@ -2208,7 +2668,7 @@ function mountLoginGate(scope, storeCredential) {
2208
2668
  markReady();
2209
2669
  };
2210
2670
  const render = () => {
2211
- const snapshot = scope.getSnapshot();
2671
+ const snapshot = form.getSnapshot();
2212
2672
  if (!loginGateVisible(snapshot)) {
2213
2673
  unmount();
2214
2674
  return;
@@ -2222,10 +2682,10 @@ function mountLoginGate(scope, storeCredential) {
2222
2682
  root = (0, import_client.createRoot)(gateContainer);
2223
2683
  if (appRoot !== null) appRoot.inert = true;
2224
2684
  }
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) }));
2685
+ 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
2686
  };
2227
2687
  render();
2228
- const dispose = scope.subscribe(() => render());
2688
+ const dispose = form.subscribe(() => render());
2229
2689
  return () => {
2230
2690
  dispose();
2231
2691
  unmount();
@@ -2233,7 +2693,7 @@ function mountLoginGate(scope, storeCredential) {
2233
2693
  }
2234
2694
 
2235
2695
  // src/client/usage-section.tsx
2236
- var import_react5 = require("react");
2696
+ var import_react6 = require("react");
2237
2697
 
2238
2698
  // src/client/usage-data.ts
2239
2699
  var USAGE_CHANNEL = "/hiwork-usage";
@@ -2565,16 +3025,16 @@ function RatioRow({ name, value, pct }) {
2565
3025
  }
2566
3026
  function UsageSection(props) {
2567
3027
  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);
3028
+ const [selectedKeyId, setSelectedKeyId] = (0, import_react6.useState)(void 0);
3029
+ const [result, setResult] = (0, import_react6.useState)(void 0);
3030
+ const [loading, setLoading] = (0, import_react6.useState)(true);
3031
+ const alive = (0, import_react6.useRef)(true);
3032
+ const ticket = (0, import_react6.useRef)(0);
2573
3033
  const settingsKeyId = card.activeKeyId;
2574
3034
  const requestKeyId = selectedKeyId ?? settingsKeyId;
2575
3035
  const displayKeyId = requestKeyId ?? result?.data.keys?.[0]?.id;
2576
3036
  const loader = props.usage;
2577
- const load = (0, import_react5.useCallback)(async (force) => {
3037
+ const load = (0, import_react6.useCallback)(async (force) => {
2578
3038
  const current = ticket.current + 1;
2579
3039
  ticket.current = current;
2580
3040
  setLoading(true);
@@ -2587,13 +3047,13 @@ function UsageSection(props) {
2587
3047
  setResult(next);
2588
3048
  setLoading(false);
2589
3049
  }, [loader, requestKeyId]);
2590
- (0, import_react5.useEffect)(() => {
3050
+ (0, import_react6.useEffect)(() => {
2591
3051
  alive.current = true;
2592
3052
  return () => {
2593
3053
  alive.current = false;
2594
3054
  };
2595
3055
  }, []);
2596
- (0, import_react5.useEffect)(() => {
3056
+ (0, import_react6.useEffect)(() => {
2597
3057
  void load(false);
2598
3058
  }, [load]);
2599
3059
  const data = result?.data;
@@ -2732,179 +3192,27 @@ function UsageSection(props) {
2732
3192
  ] });
2733
3193
  }
2734
3194
 
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;
2744
- }
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 = [];
2757
- 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 }
2783
- };
2784
- const ambiguous = pairs.length > 1 || cacheRead === void 0 || cacheWrite === void 0;
2785
- return { items: [item], turn, ambiguous };
2786
- }
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();
2791
- }
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;
2799
- }
2800
- return void 0;
2801
- }
2802
- function sessionCostKey(sessionId, match) {
2803
- return `${sessionId}\0${match.turn}\0${JSON.stringify(match.items)}`;
2804
- }
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
- }
3195
+ // src/client/menu-section.tsx
3196
+ var import_react8 = require("react");
2888
3197
 
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
- }
3198
+ // src/client/feature-center.ts
3199
+ var import_react7 = require("react");
3200
+ var import_client2 = require("react-dom/client");
2905
3201
 
2906
3202
  // src/client/feature-registry.ts
2907
3203
  var DEFAULT_FEATURE_ORDER = 1e3;
3204
+ 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>';
3205
+ var NATIVE_PLUGINS_MENU_ID = "__native_plugins__";
3206
+ var NATIVE_PLUGINS_MENU_LABEL = "\u63D2\u4EF6";
3207
+ var NATIVE_PLUGINS_MENU_ORDER = 70;
3208
+ function nativePluginsMenuItem() {
3209
+ return {
3210
+ id: NATIVE_PLUGINS_MENU_ID,
3211
+ label: () => NATIVE_PLUGINS_MENU_LABEL,
3212
+ icon: DEFAULT_FEATURE_ICON,
3213
+ order: NATIVE_PLUGINS_MENU_ORDER
3214
+ };
3215
+ }
2908
3216
  function compareFeatures(left, right) {
2909
3217
  const leftOrder = left.order ?? DEFAULT_FEATURE_ORDER;
2910
3218
  const rightOrder = right.order ?? DEFAULT_FEATURE_ORDER;
@@ -2970,11 +3278,145 @@ function createFeatureRegistry() {
2970
3278
  };
2971
3279
  }
2972
3280
 
2973
- // src/client/feature-center.ts
2974
- var import_react7 = require("react");
2975
- var import_client2 = require("react-dom/client");
2976
- var SIDEBAR_SELECTOR = '[data-pane="sidebar"], [class*="sidebarCol"]';
2977
- var CONVERSATION_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]';
3281
+ // src/client/menu-preferences.ts
3282
+ var HIWORK_MENU_NS = "hiwork-menu";
3283
+ var DEFAULT_SIDEBAR_LIMIT = 5;
3284
+ var MORE_MENU_ITEM_ID = "__hiwork_more__";
3285
+ var MORE_MENU_LABEL = "\u66F4\u591A";
3286
+ var MENU_SECTION_LABEL = "\u4FA7\u680F\u83DC\u5355";
3287
+ var MENU_PLACEMENTS = ["sidebar", "more", "hidden"];
3288
+ var EMPTY_MENU_PREFERENCES = { order: [], sidebar: [], more: [], hidden: [] };
3289
+ function uniqueIds(value) {
3290
+ const seen = /* @__PURE__ */ new Set();
3291
+ const result = [];
3292
+ for (const item of value) {
3293
+ if (typeof item !== "string") continue;
3294
+ const id = item.trim();
3295
+ if (id === "" || seen.has(id)) continue;
3296
+ seen.add(id);
3297
+ result.push(id);
3298
+ }
3299
+ return result;
3300
+ }
3301
+ function idList(value) {
3302
+ return Array.isArray(value) ? uniqueIds(value.filter((item) => typeof item === "string")) : [];
3303
+ }
3304
+ function normalizeMenuPreferences(raw) {
3305
+ if (raw === null || typeof raw !== "object") return EMPTY_MENU_PREFERENCES;
3306
+ const value = raw;
3307
+ return {
3308
+ order: idList(value.order),
3309
+ sidebar: idList(value.sidebar),
3310
+ more: idList(value.more),
3311
+ hidden: idList(value.hidden)
3312
+ };
3313
+ }
3314
+ function placementOf(preferences, id) {
3315
+ if (preferences.hidden.includes(id)) return "hidden";
3316
+ if (preferences.more.includes(id)) return "more";
3317
+ if (preferences.sidebar.includes(id)) return "sidebar";
3318
+ return void 0;
3319
+ }
3320
+ function compareFeatures2(left, right) {
3321
+ const leftOrder = left.order ?? DEFAULT_FEATURE_ORDER;
3322
+ const rightOrder = right.order ?? DEFAULT_FEATURE_ORDER;
3323
+ if (leftOrder !== rightOrder) return leftOrder - rightOrder;
3324
+ if (left.id === right.id) return 0;
3325
+ return left.id < right.id ? -1 : 1;
3326
+ }
3327
+ function orderedFeatureIds(features, preferences) {
3328
+ const known = new Set(features.map((feature) => feature.id));
3329
+ const result = [];
3330
+ const seen = /* @__PURE__ */ new Set();
3331
+ for (const id of preferences.order) {
3332
+ if (!known.has(id) || seen.has(id)) continue;
3333
+ seen.add(id);
3334
+ result.push(id);
3335
+ }
3336
+ for (const feature of [...features].sort(compareFeatures2)) {
3337
+ if (seen.has(feature.id)) continue;
3338
+ seen.add(feature.id);
3339
+ result.push(feature.id);
3340
+ }
3341
+ return result;
3342
+ }
3343
+ function menuLayout(features, preferences) {
3344
+ const hidden = new Set(preferences.hidden);
3345
+ const explicitMore = new Set(preferences.more.filter((id) => !hidden.has(id)));
3346
+ const explicitSidebar = new Set(preferences.sidebar.filter((id) => !hidden.has(id) && !explicitMore.has(id)));
3347
+ const byId = new Map(features.map((feature) => [feature.id, feature]));
3348
+ const markedMore = [...explicitMore].filter((id) => byId.has(id)).length;
3349
+ const budget = Math.max(0, DEFAULT_SIDEBAR_LIMIT - markedMore);
3350
+ const sidebar = [];
3351
+ const more = [];
3352
+ for (const id of orderedFeatureIds(features, preferences)) {
3353
+ const feature = byId.get(id);
3354
+ if (feature === void 0 || hidden.has(id)) continue;
3355
+ if (explicitSidebar.has(id)) {
3356
+ sidebar.push(feature);
3357
+ continue;
3358
+ }
3359
+ if (explicitMore.has(id) || sidebar.length >= budget) {
3360
+ more.push(feature);
3361
+ continue;
3362
+ }
3363
+ sidebar.push(feature);
3364
+ }
3365
+ return { sidebar, more };
3366
+ }
3367
+ function withoutPlacement(preferences, id) {
3368
+ return {
3369
+ order: preferences.order,
3370
+ sidebar: preferences.sidebar.filter((item) => item !== id),
3371
+ more: preferences.more.filter((item) => item !== id),
3372
+ hidden: preferences.hidden.filter((item) => item !== id)
3373
+ };
3374
+ }
3375
+ function setMenuPlacement(preferences, id, placement) {
3376
+ const current = placementOf(preferences, id);
3377
+ if (current === placement) return preferences;
3378
+ const base = withoutPlacement(preferences, id);
3379
+ if (placement === "sidebar") return { ...base, sidebar: [...base.sidebar, id] };
3380
+ if (placement === "more") return { ...base, more: [...base.more, id] };
3381
+ return { ...base, hidden: [...base.hidden, id] };
3382
+ }
3383
+ function moveMenuEntry(preferences, features, id, targetIndex) {
3384
+ const ids = orderedFeatureIds(features, preferences);
3385
+ const from = ids.indexOf(id);
3386
+ if (from < 0) return preferences;
3387
+ const to = Math.max(0, Math.min(ids.length - 1, Math.trunc(targetIndex)));
3388
+ if (to === from) return preferences;
3389
+ const next = [...ids];
3390
+ next.splice(from, 1);
3391
+ next.splice(to, 0, id);
3392
+ return { ...preferences, order: next };
3393
+ }
3394
+ function sameIds(left, right) {
3395
+ return left.length === right.length && left.every((item, index) => item === right[index]);
3396
+ }
3397
+ function moveMenuEntryNear(preferences, features, id, targetId, position) {
3398
+ const ids = orderedFeatureIds(features, preferences);
3399
+ const from = ids.indexOf(id);
3400
+ if (from < 0 || id === targetId || !ids.includes(targetId)) return preferences;
3401
+ const rest = ids.filter((item) => item !== id);
3402
+ const anchor = rest.indexOf(targetId);
3403
+ const next = [...rest];
3404
+ next.splice(position === "before" ? anchor : anchor + 1, 0, id);
3405
+ if (sameIds(next, ids)) return preferences;
3406
+ return { ...preferences, order: next };
3407
+ }
3408
+ function menuPreferenceOps(current, next) {
3409
+ const ops = [];
3410
+ for (const field of ["order", "sidebar", "more", "hidden"]) {
3411
+ if (sameIds(current[field], next[field])) continue;
3412
+ ops.push({ op: "set", path: [field], value: [...next[field]] });
3413
+ }
3414
+ return ops;
3415
+ }
3416
+
3417
+ // src/client/feature-center.ts
3418
+ var SIDEBAR_SELECTOR = '[data-pane="sidebar"], [class*="sidebarCol"]';
3419
+ var CONVERSATION_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]';
2978
3420
  var REGION_SELECTOR = '[class*="regionArea"]';
2979
3421
  var NEW_SESSION_SELECTOR = [
2980
3422
  'button[data-testid="new-session"]',
@@ -2983,11 +3425,16 @@ var NEW_SESSION_SELECTOR = [
2983
3425
  ].join(", ");
2984
3426
  var MENU_ATTRIBUTE = "data-hiwork-feature-menu";
2985
3427
  var MENU_ITEM_ATTRIBUTE = "data-hiwork-feature-menu-item";
3428
+ var MENU_BUTTON_ATTRIBUTE = "data-hiwork-feature-menu-button";
3429
+ var MORE_TOGGLE_ATTRIBUTE = "data-hiwork-feature-more-toggle";
3430
+ var MORE_PANEL_ATTRIBUTE = "data-hiwork-feature-more-panel";
3431
+ var MORE_PANEL_ITEM_ATTRIBUTE = "data-hiwork-feature-more-item";
2986
3432
  var MENU_ICON_ATTRIBUTE = "data-hiwork-feature-menu-icon";
2987
3433
  var MENU_LABEL_ATTRIBUTE = "data-hiwork-feature-menu-label";
2988
3434
  var TOOLTIP_ATTRIBUTE = "data-hiwork-feature-tooltip";
2989
3435
  var TOOLTIP_VISIBLE_ATTRIBUTE = "data-hiwork-feature-tooltip-visible";
2990
3436
  var NATIVE_HIDDEN_ATTRIBUTE = "data-hiwork-native-new-session";
3437
+ var NATIVE_PANEL_LIST_ATTRIBUTE = "data-hiwork-native-panel-list";
2991
3438
  var FEATURE_ROOT_ATTRIBUTE = "data-hiwork-feature-root";
2992
3439
  var OWNER_ATTRIBUTE = "data-hiwork-owner";
2993
3440
  var ACTIVE_PAGE_ATTRIBUTE = "data-hiwork-active-page";
@@ -2997,7 +3444,7 @@ var STYLE_ELEMENT_ID = "hiwork-feature-center-styles";
2997
3444
  var TOOLTIP_DELAY_MS = 500;
2998
3445
  var TOOLTIP_GAP_PX = 8;
2999
3446
  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>';
3447
+ 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
3448
  var SESSION_ROW_SELECTOR = [
3002
3449
  '[class*="sessionRow"]',
3003
3450
  '[class*="projectRow"]',
@@ -3007,18 +3454,22 @@ var SESSION_ROW_SELECTOR = [
3007
3454
  ].join(", ");
3008
3455
  var featureCenterCss = [
3009
3456
  `[${NATIVE_HIDDEN_ATTRIBUTE}="hidden"] { display: none !important; }`,
3457
+ `[${NATIVE_PANEL_LIST_ATTRIBUTE}="hidden"] { display: none !important; }`,
3010
3458
  `[${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)); }`,
3459
+ `[${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; }`,
3460
+ `[${MENU_BUTTON_ATTRIBUTE}]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 17, 21, 0.06)); }`,
3461
+ `[${MENU_BUTTON_ATTRIBUTE}][data-hiwork-feature-active="true"] { background: var(--dsw-alias-interactive-bg-active, rgba(15, 17, 21, 0.1)); }`,
3014
3462
  `[${MENU_ICON_ATTRIBUTE}] { display: inline-flex; flex: none; width: 16px; height: 16px; }`,
3015
3463
  `[${MENU_ICON_ATTRIBUTE}] > svg { display: block; width: 16px; height: 16px; }`,
3016
3464
  // 侧栏折叠:宿主在侧栏根元素上加 `*_collapsed`(rc.1 为 hHd-Xa_collapsed),
3017
3465
  // 原生控件此时只显示图标;注入菜单必须同形,否则文字会在 56px 宽栏里竖排。
3018
3466
  `[class*="sidebarCol"] [class*="collapsed"] [${MENU_ATTRIBUTE}] { margin: 0 4px 8px; }`,
3019
3467
  `[class*="sidebarCol"] [class*="collapsed"] [${MENU_LABEL_ATTRIBUTE}] { display: none; }`,
3020
- `[class*="sidebarCol"] [class*="collapsed"] [${MENU_ITEM_ATTRIBUTE}] { justify-content: center; padding: 8px 0; }`,
3468
+ `[class*="sidebarCol"] [class*="collapsed"] [${MENU_BUTTON_ATTRIBUTE}] { justify-content: center; padding: 8px 0; }`,
3021
3469
  `[class*="sidebarCol"] [class*="collapsed"] [${MENU_ICON_ATTRIBUTE}], [class*="sidebarCol"] [class*="collapsed"] [${MENU_ICON_ATTRIBUTE}] > svg { width: 18px; height: 18px; }`,
3470
+ // 「更多」浮层:与框架浮层(Tooltip/Popover)同色同圆角,固定定位挂 body(不受侧栏 overflow 裁剪)。
3471
+ `[${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); }`,
3472
+ `[${MORE_PANEL_ATTRIBUTE}] [${MENU_BUTTON_ATTRIBUTE}] { white-space: nowrap; }`,
3022
3473
  // 折叠态提示气泡:几何与配色逐项对齐框架 `ui-primitives` 的 `Tooltip.module.css`
3023
3474
  // (黑底白字、3px 7px 内边距、8px 圆角、13/20 字号、150ms 淡入、最大半屏宽)。
3024
3475
  `[${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 +3512,7 @@ function nativeNewSessionButton(root) {
3061
3512
  function conversationContainer() {
3062
3513
  return document.querySelector(CONVERSATION_SELECTOR) ?? void 0;
3063
3514
  }
3515
+ var PANEL_LIST_SELECTOR = 'nav[class*="panelList"]';
3064
3516
  function isCollapsedRail(item) {
3065
3517
  const host = item.closest('[class*="collapsed"]');
3066
3518
  if (host === null) return false;
@@ -3077,12 +3529,15 @@ function mountFeatureCenter(options) {
3077
3529
  return { dispose: () => void 0, openNativeHome: () => void 0 };
3078
3530
  }
3079
3531
  const isActiveOwner = () => activeOwner === owner;
3080
- for (const stale of Array.from(document.querySelectorAll(`[${MENU_ATTRIBUTE}], [${FEATURE_ROOT_ATTRIBUTE}], [${TOOLTIP_ATTRIBUTE}], #${STYLE_ELEMENT_ID}`))) {
3532
+ for (const stale of Array.from(document.querySelectorAll(`[${MENU_ATTRIBUTE}], [${MORE_PANEL_ATTRIBUTE}], [${FEATURE_ROOT_ATTRIBUTE}], [${TOOLTIP_ATTRIBUTE}], #${STYLE_ELEMENT_ID}`))) {
3081
3533
  stale.remove();
3082
3534
  }
3083
3535
  for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_HIDDEN_ATTRIBUTE}]`))) {
3084
3536
  hidden.removeAttribute(NATIVE_HIDDEN_ATTRIBUTE);
3085
3537
  }
3538
+ for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_PANEL_LIST_ATTRIBUTE}]`))) {
3539
+ hidden.removeAttribute(NATIVE_PANEL_LIST_ATTRIBUTE);
3540
+ }
3086
3541
  document.documentElement.removeAttribute(ACTIVE_PAGE_ATTRIBUTE);
3087
3542
  activeOwner = owner;
3088
3543
  const style = document.createElement("style");
@@ -3102,6 +3557,87 @@ function mountFeatureCenter(options) {
3102
3557
  let tooltipElement;
3103
3558
  let tooltipTimer;
3104
3559
  let tooltipItem;
3560
+ let morePanel;
3561
+ let moreToggle;
3562
+ let morePanelOpen = false;
3563
+ let layout = { sidebar: [], more: [] };
3564
+ const preferences = () => options.preferences?.() ?? EMPTY_MENU_PREFERENCES;
3565
+ const menuFeatures = () => [
3566
+ ...registry.list(),
3567
+ ...options.additionalFeatures?.() ?? []
3568
+ ];
3569
+ const hideMorePanel = (restoreFocus = false) => {
3570
+ const panel = morePanel;
3571
+ const toggle = moreToggle;
3572
+ morePanel = void 0;
3573
+ moreToggle = void 0;
3574
+ if (!morePanelOpen && panel === void 0) return;
3575
+ morePanelOpen = false;
3576
+ panel?.remove();
3577
+ if (toggle !== void 0 && toggle.isConnected) {
3578
+ toggle.setAttribute("aria-expanded", "false");
3579
+ toggle.removeAttribute("data-hiwork-feature-active");
3580
+ if (restoreFocus) toggle.focus();
3581
+ }
3582
+ };
3583
+ const createMorePanelItem = (feature, active) => {
3584
+ const item = createMenuItem(feature.id, feature.label(), feature.icon ?? DEFAULT_FEATURE_ICON, active);
3585
+ item.setAttribute(MORE_PANEL_ITEM_ATTRIBUTE, feature.id);
3586
+ item.setAttribute("role", "menuitem");
3587
+ return item;
3588
+ };
3589
+ const renderMorePanel = () => {
3590
+ const panel = morePanel;
3591
+ if (panel === void 0) return;
3592
+ const active = registry.activeId();
3593
+ const pluginsActive = options.nativePanel?.pluginsActive() ?? false;
3594
+ panel.replaceChildren(...layout.more.map((feature) => createMorePanelItem(feature, feature.id === NATIVE_PLUGINS_MENU_ID ? pluginsActive : feature.id === active)));
3595
+ };
3596
+ const placeMorePanel = (toggle, panel) => {
3597
+ const rect = toggle.getBoundingClientRect();
3598
+ const width = panel.offsetWidth;
3599
+ const height = panel.offsetHeight;
3600
+ const overflowRight = rect.right + TOOLTIP_GAP_PX + width > window.innerWidth;
3601
+ const left = overflowRight ? rect.left - TOOLTIP_GAP_PX - width : rect.right + TOOLTIP_GAP_PX;
3602
+ panel.style.left = `${Math.max(0, left)}px`;
3603
+ panel.style.top = `${Math.max(8, Math.min(rect.top, window.innerHeight - height - 8))}px`;
3604
+ };
3605
+ const showMorePanel = (toggle) => {
3606
+ hideMorePanel();
3607
+ if (layout.more.length === 0) return;
3608
+ const panel = document.createElement("div");
3609
+ panel.setAttribute(MORE_PANEL_ATTRIBUTE, "");
3610
+ panel.setAttribute(OWNER_ATTRIBUTE, owner);
3611
+ panel.setAttribute("role", "menu");
3612
+ panel.addEventListener("click", onMorePanelClick);
3613
+ document.body.appendChild(panel);
3614
+ morePanel = panel;
3615
+ moreToggle = toggle;
3616
+ morePanelOpen = true;
3617
+ toggle.setAttribute("aria-expanded", "true");
3618
+ if (layout.more.some((feature) => feature.id === registry.activeId())) toggle.setAttribute("data-hiwork-feature-active", "true");
3619
+ renderMorePanel();
3620
+ placeMorePanel(toggle, panel);
3621
+ };
3622
+ const toggleMorePanel = (toggle) => {
3623
+ if (morePanelOpen) hideMorePanel();
3624
+ else showMorePanel(toggle);
3625
+ };
3626
+ function onMorePanelClick(event) {
3627
+ const target = event.target;
3628
+ if (!(target instanceof Element)) return;
3629
+ const item = target.closest(`[${MORE_PANEL_ITEM_ATTRIBUTE}]`);
3630
+ if (item === null) return;
3631
+ const id = item.getAttribute(MORE_PANEL_ITEM_ATTRIBUTE);
3632
+ hideMorePanel();
3633
+ if (id === null) return;
3634
+ if (id === NATIVE_PLUGINS_MENU_ID) {
3635
+ if (registry.activeId() !== null) registry.openNative();
3636
+ options.nativePanel?.openPlugins();
3637
+ return;
3638
+ }
3639
+ registry.open(id);
3640
+ }
3105
3641
  const hideTooltip = () => {
3106
3642
  if (tooltipTimer !== void 0) {
3107
3643
  clearTimeout(tooltipTimer);
@@ -3203,11 +3739,21 @@ function mountFeatureCenter(options) {
3203
3739
  renderContext.openNativeHome();
3204
3740
  return;
3205
3741
  }
3742
+ if (id === MORE_MENU_ITEM_ID) {
3743
+ toggleMorePanel(item);
3744
+ return;
3745
+ }
3746
+ if (id === NATIVE_PLUGINS_MENU_ID) {
3747
+ if (registry.activeId() !== null) registry.openNative();
3748
+ options.nativePanel?.openPlugins();
3749
+ return;
3750
+ }
3206
3751
  registry.open(id);
3207
3752
  };
3208
3753
  const createMenuItem = (id, label, icon, active) => {
3209
3754
  const item = document.createElement("button");
3210
3755
  item.type = "button";
3756
+ item.setAttribute(MENU_BUTTON_ATTRIBUTE, "");
3211
3757
  item.setAttribute(MENU_ITEM_ATTRIBUTE, id);
3212
3758
  if (active) item.setAttribute("data-hiwork-feature-active", "true");
3213
3759
  item.setAttribute("aria-label", label);
@@ -3222,6 +3768,13 @@ function mountFeatureCenter(options) {
3222
3768
  item.append(labelSpan);
3223
3769
  return item;
3224
3770
  };
3771
+ const createMoreRow = (active) => {
3772
+ const item = createMenuItem(MORE_MENU_ITEM_ID, MORE_MENU_LABEL, MORE_ICON, active);
3773
+ item.setAttribute(MORE_TOGGLE_ATTRIBUTE, "");
3774
+ item.setAttribute("aria-haspopup", "menu");
3775
+ item.setAttribute("aria-expanded", morePanelOpen ? "true" : "false");
3776
+ return item;
3777
+ };
3225
3778
  const nativeIcon = (native) => {
3226
3779
  const svg = native?.querySelector("svg");
3227
3780
  return svg === null || svg === void 0 ? DEFAULT_NEW_SESSION_ICON : svg.outerHTML;
@@ -3230,7 +3783,12 @@ function mountFeatureCenter(options) {
3230
3783
  const element = menuElement;
3231
3784
  if (element === void 0) return;
3232
3785
  const active = registry.activeId();
3233
- const features = registry.list();
3786
+ const features = menuFeatures();
3787
+ const prefs = preferences();
3788
+ const next = menuLayout(features, prefs);
3789
+ const nativePanel = options.nativePanel;
3790
+ const pluginsAvailable = nativePanel?.pluginsAvailable() ?? false;
3791
+ const pluginsActive = pluginsAvailable && (nativePanel?.pluginsActive() ?? false);
3234
3792
  const sidebar = sidebarRoot();
3235
3793
  const native = sidebar === void 0 ? void 0 : nativeNewSessionButton(sidebar);
3236
3794
  const newSessionLabel = options.newSessionLabel?.() ?? DEFAULT_NEW_SESSION_LABEL;
@@ -3238,17 +3796,27 @@ function mountFeatureCenter(options) {
3238
3796
  native === void 0 ? "no-native" : `native:${nativeIcon(native)}`,
3239
3797
  newSessionLabel,
3240
3798
  active ?? "",
3799
+ // 官方入口的可用性/激活态进签名:plugin-manager 装载完成后(面板注册进 main 槽)
3800
+ // 与用户在原生面板间切换时,菜单都要跟着重绘一次。
3801
+ `panel:${pluginsAvailable ? pluginsActive ? "active" : "idle" : "off"}`,
3802
+ // 分组与顺序都进签名:偏好一变(设置面板里拖一下)就必须重建,否则侧栏不动。
3803
+ `layout:${next.sidebar.map((item) => item.id).join(",")}>${next.more.map((item) => item.id).join(",")}`,
3241
3804
  ...features.map((item) => `${item.id}:${item.order ?? ""}:${item.label()}:${item.icon ?? ""}`)
3242
3805
  ].join("|");
3243
3806
  if (signature === menuSignature) return;
3244
3807
  menuSignature = signature;
3808
+ layout = next;
3245
3809
  hideTooltip();
3810
+ hideMorePanel();
3246
3811
  const items = [];
3247
3812
  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));
3813
+ for (const item of next.sidebar) {
3814
+ const rowActive = item.id === NATIVE_PLUGINS_MENU_ID ? pluginsActive : item.id === active;
3815
+ items.push(createMenuItem(item.id, item.label(), item.icon ?? DEFAULT_FEATURE_ICON, rowActive));
3250
3816
  }
3817
+ if (next.more.length > 0) items.push(createMoreRow(next.more.some((item) => item.id === active)));
3251
3818
  element.replaceChildren(...items);
3819
+ syncNativePanelList(pluginsAvailable);
3252
3820
  };
3253
3821
  const ensureMenu = (sidebar, native) => {
3254
3822
  let element = menuElement;
@@ -3274,6 +3842,13 @@ function mountFeatureCenter(options) {
3274
3842
  if (node.getAttribute(OWNER_ATTRIBUTE) !== owner) node.remove();
3275
3843
  }
3276
3844
  };
3845
+ const syncNativePanelList = (pluginsAvailable) => {
3846
+ const sidebar = sidebarRoot();
3847
+ const nav = sidebar?.querySelector(PANEL_LIST_SELECTOR) ?? null;
3848
+ if (nav === null) return;
3849
+ if (pluginsAvailable) nav.setAttribute(NATIVE_PANEL_LIST_ATTRIBUTE, "hidden");
3850
+ else nav.removeAttribute(NATIVE_PANEL_LIST_ATTRIBUTE);
3851
+ };
3277
3852
  const syncMenu = () => {
3278
3853
  dedupe(`[${MENU_ATTRIBUTE}]`);
3279
3854
  const sidebar = sidebarRoot();
@@ -3294,6 +3869,7 @@ function mountFeatureCenter(options) {
3294
3869
  clearTakeover();
3295
3870
  return;
3296
3871
  }
3872
+ if (options.nativePanel?.anyActive() === true) options.nativePanel.returnToConversation();
3297
3873
  const host = conversationContainer();
3298
3874
  if (host === void 0) {
3299
3875
  clearTakeover();
@@ -3312,96 +3888,1435 @@ function mountFeatureCenter(options) {
3312
3888
  const reactRoot = (0, import_client2.createRoot)(element);
3313
3889
  root = reactRoot;
3314
3890
  try {
3315
- reactRoot.render((0, import_react7.createElement)(FeatureErrorBoundary, { onError: onRenderError }, target.render(renderContext)));
3316
- } catch (error) {
3317
- onRenderError(error);
3891
+ reactRoot.render((0, import_react7.createElement)(FeatureErrorBoundary, { onError: onRenderError }, target.render(renderContext)));
3892
+ } catch (error) {
3893
+ onRenderError(error);
3894
+ }
3895
+ };
3896
+ const pending = () => {
3897
+ const sidebar = sidebarRoot();
3898
+ if (sidebar === void 0) return true;
3899
+ if (menuElement === void 0 || !sidebar.contains(menuElement)) return true;
3900
+ if (registry.activeId() === null) return false;
3901
+ const host = conversationContainer();
3902
+ if (host === void 0) return true;
3903
+ return container === void 0 || !host.contains(container);
3904
+ };
3905
+ const scheduleRetry = () => {
3906
+ if (disposed || retryTimer !== void 0 || retries >= maxRetries) return;
3907
+ retryTimer = setTimeout(() => {
3908
+ retryTimer = void 0;
3909
+ retries += 1;
3910
+ sync();
3911
+ if (pending()) scheduleRetry();
3912
+ }, interval);
3913
+ };
3914
+ const onDocumentClickCapture = (event) => {
3915
+ const target = event.target;
3916
+ if (morePanelOpen) {
3917
+ const inside = target instanceof Element && (target.closest(`[${MORE_PANEL_ATTRIBUTE}]`) !== null || target.closest(`[${MORE_TOGGLE_ATTRIBUTE}]`) !== null);
3918
+ if (!inside) hideMorePanel();
3919
+ }
3920
+ if (registry.activeId() === null) return;
3921
+ if (!(target instanceof Element)) return;
3922
+ if (target.closest(`[${FEATURE_ROOT_ATTRIBUTE}]`) !== null) return;
3923
+ if (target.closest(`[${MENU_ATTRIBUTE}]`) !== null) return;
3924
+ if (target.closest(SESSION_ROW_SELECTOR) === null) return;
3925
+ registry.openNative();
3926
+ };
3927
+ const onDocumentKeyDown = (event) => {
3928
+ if (event.key !== "Escape" || !morePanelOpen) return;
3929
+ event.stopPropagation();
3930
+ hideMorePanel(true);
3931
+ };
3932
+ const onScrollOrResize = () => {
3933
+ hideTooltip();
3934
+ hideMorePanel();
3935
+ };
3936
+ const unsubscribeRegistry = registry.subscribe(() => {
3937
+ sync();
3938
+ });
3939
+ let unsubscribeLocale;
3940
+ if (options.subscribeLocale !== void 0) {
3941
+ try {
3942
+ unsubscribeLocale = options.subscribeLocale(() => {
3943
+ menuSignature = "";
3944
+ rebuildMenuItems();
3945
+ });
3946
+ } catch (error) {
3947
+ console.warn("hiwork feature center: locale subscription unavailable", error);
3948
+ }
3949
+ }
3950
+ let unsubscribePreferences;
3951
+ if (options.subscribePreferences !== void 0) {
3952
+ try {
3953
+ unsubscribePreferences = options.subscribePreferences(() => {
3954
+ menuSignature = "";
3955
+ rebuildMenuItems();
3956
+ });
3957
+ } catch (error) {
3958
+ console.warn("hiwork feature center: menu preferences subscription unavailable", error);
3959
+ }
3960
+ }
3961
+ let unsubscribeNativePanel;
3962
+ if (options.nativePanel !== void 0) {
3963
+ try {
3964
+ unsubscribeNativePanel = options.nativePanel.subscribe(() => {
3965
+ if (disposed) return;
3966
+ if (options.nativePanel?.anyActive() === true && registry.activeId() !== null) {
3967
+ registry.openNative();
3968
+ return;
3969
+ }
3970
+ menuSignature = "";
3971
+ rebuildMenuItems();
3972
+ });
3973
+ } catch (error) {
3974
+ console.warn("hiwork feature center: native panel subscription unavailable", error);
3975
+ }
3976
+ }
3977
+ const observer = new MutationObserver(() => {
3978
+ sync();
3979
+ });
3980
+ observer.observe(document.body, { childList: true, subtree: true });
3981
+ document.addEventListener("click", onDocumentClickCapture, true);
3982
+ document.addEventListener("keydown", onDocumentKeyDown, true);
3983
+ document.addEventListener("scroll", onScrollOrResize, true);
3984
+ window.addEventListener("resize", onScrollOrResize);
3985
+ const sync = () => {
3986
+ if (disposed || !isActiveOwner()) return;
3987
+ syncMenu();
3988
+ syncTakeover();
3989
+ if (pending()) scheduleRetry();
3990
+ };
3991
+ sync();
3992
+ return {
3993
+ dispose: () => {
3994
+ if (disposed) return;
3995
+ disposed = true;
3996
+ observer.disconnect();
3997
+ if (retryTimer !== void 0) clearTimeout(retryTimer);
3998
+ retryTimer = void 0;
3999
+ unsubscribeRegistry();
4000
+ unsubscribeLocale?.();
4001
+ unsubscribePreferences?.();
4002
+ unsubscribeNativePanel?.();
4003
+ document.removeEventListener("click", onDocumentClickCapture, true);
4004
+ document.removeEventListener("keydown", onDocumentKeyDown, true);
4005
+ document.removeEventListener("scroll", onScrollOrResize, true);
4006
+ window.removeEventListener("resize", onScrollOrResize);
4007
+ hideTooltip();
4008
+ hideMorePanel();
4009
+ const tooltip = tooltipElement;
4010
+ tooltipElement = void 0;
4011
+ if (tooltip !== void 0 && tooltip.getAttribute(OWNER_ATTRIBUTE) === owner) tooltip.remove();
4012
+ for (const panel of Array.from(document.querySelectorAll(`[${MORE_PANEL_ATTRIBUTE}]`))) {
4013
+ if (panel.getAttribute(OWNER_ATTRIBUTE) === owner) panel.remove();
4014
+ }
4015
+ clearTakeover();
4016
+ const element = menuElement;
4017
+ menuElement = void 0;
4018
+ if (element !== void 0) {
4019
+ element.removeEventListener("click", onMenuClick);
4020
+ if (element.getAttribute(OWNER_ATTRIBUTE) === owner) element.remove();
4021
+ }
4022
+ if (!isActiveOwner()) return;
4023
+ activeOwner = null;
4024
+ for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_HIDDEN_ATTRIBUTE}]`))) {
4025
+ hidden.removeAttribute(NATIVE_HIDDEN_ATTRIBUTE);
4026
+ }
4027
+ for (const hidden of Array.from(document.querySelectorAll(`[${NATIVE_PANEL_LIST_ATTRIBUTE}]`))) {
4028
+ hidden.removeAttribute(NATIVE_PANEL_LIST_ATTRIBUTE);
4029
+ }
4030
+ if (style.getAttribute(OWNER_ATTRIBUTE) === owner) style.remove();
4031
+ },
4032
+ openNativeHome: () => {
4033
+ renderContext.openNativeHome();
4034
+ }
4035
+ };
4036
+ }
4037
+
4038
+ // src/client/menu-section.tsx
4039
+ var import_jsx_runtime7 = require("react/jsx-runtime");
4040
+ var PLACEMENT_LABELS = {
4041
+ sidebar: "\u4FA7\u680F",
4042
+ more: "\u66F4\u591A",
4043
+ hidden: "\u9690\u85CF"
4044
+ };
4045
+ var sectionStyle4 = { display: "flex", flexDirection: "column", gap: 16 };
4046
+ var headerStyle2 = { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" };
4047
+ var titleStyle3 = { margin: 0, fontSize: 18, fontWeight: 600, color: textPrimary };
4048
+ var noteStyle2 = { margin: 0, fontSize: 12, lineHeight: "18px", color: textSecondary };
4049
+ var reasonStyle = { margin: 0, fontSize: 12, lineHeight: "18px", color: textTertiary };
4050
+ var listStyle2 = { display: "flex", flexDirection: "column", gap: 2, margin: 0, padding: 0, listStyle: "none" };
4051
+ var rowStyle = (drop, dragging) => ({
4052
+ display: "flex",
4053
+ alignItems: "center",
4054
+ gap: 10,
4055
+ padding: "6px 8px",
4056
+ borderRadius: 8,
4057
+ // 插入线:拖拽落点用 2px 主题色描边标出(不占位,不引起跳动)。
4058
+ ...drop === "before" ? { boxShadow: `inset 0 2px 0 0 ${brandAccent}` } : {},
4059
+ ...drop === "after" ? { boxShadow: `inset 0 -2px 0 0 ${brandAccent}` } : {},
4060
+ ...dragging ? { opacity: 0.5 } : {}
4061
+ });
4062
+ var handleStyle = (writable) => ({
4063
+ display: "flex",
4064
+ alignItems: "center",
4065
+ justifyContent: "center",
4066
+ width: 18,
4067
+ height: 24,
4068
+ flex: "0 0 auto",
4069
+ borderRadius: 6,
4070
+ color: writable ? textTertiary : textTertiary,
4071
+ cursor: writable ? "grab" : "default"
4072
+ });
4073
+ var iconStyle = {
4074
+ display: "flex",
4075
+ alignItems: "center",
4076
+ justifyContent: "center",
4077
+ width: 16,
4078
+ height: 16,
4079
+ flex: "0 0 auto",
4080
+ color: textSecondary
4081
+ };
4082
+ var labelStyle2 = {
4083
+ flex: 1,
4084
+ minWidth: 0,
4085
+ fontSize: 13,
4086
+ lineHeight: "20px",
4087
+ color: textPrimary,
4088
+ overflow: "hidden",
4089
+ textOverflow: "ellipsis",
4090
+ whiteSpace: "nowrap"
4091
+ };
4092
+ var markedStyle = { flex: "0 0 auto", fontSize: 11, lineHeight: "16px", color: textTertiary };
4093
+ var groupStyle = {
4094
+ display: "flex",
4095
+ alignItems: "center",
4096
+ gap: 2,
4097
+ flex: "0 0 auto",
4098
+ padding: 2,
4099
+ borderRadius: 8,
4100
+ background: bgLayer2,
4101
+ border: `1px solid ${borderL2}`
4102
+ };
4103
+ var optionStyle = (active, writable) => ({
4104
+ border: 0,
4105
+ borderRadius: 6,
4106
+ padding: "3px 10px",
4107
+ font: "inherit",
4108
+ fontSize: 12,
4109
+ lineHeight: "18px",
4110
+ color: active ? textPrimary : textSecondary,
4111
+ background: active ? "var(--dsw-alias-bg-layer-1, #ffffff)" : "transparent",
4112
+ boxShadow: active ? "0 1px 2px rgba(15,17,21,0.08)" : "none",
4113
+ cursor: writable ? "pointer" : "default",
4114
+ opacity: writable ? 1 : 0.6
4115
+ });
4116
+ 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>';
4117
+ function MenuRow({
4118
+ entry,
4119
+ writable,
4120
+ dragging,
4121
+ drop,
4122
+ onPlacement,
4123
+ onDragStart,
4124
+ onDragOver,
4125
+ onDrop,
4126
+ onDragEnd,
4127
+ onKeyDown
4128
+ }) {
4129
+ const rowRef = (0, import_react8.useRef)(null);
4130
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4131
+ "li",
4132
+ {
4133
+ ref: rowRef,
4134
+ tabIndex: 0,
4135
+ role: "listitem",
4136
+ "aria-label": `${entry.label}\uFF0C\u7B2C ${entry.index + 1} \u9879\uFF0C\u5171 ${entry.total} \u9879`,
4137
+ style: rowStyle(drop, dragging),
4138
+ "data-hiwork-menu-row": entry.id,
4139
+ "data-hiwork-menu-placement": entry.placement,
4140
+ ...entry.marked ? { "data-hiwork-menu-marked": "true" } : {},
4141
+ ...drop === void 0 ? {} : { "data-hiwork-menu-drop": drop },
4142
+ draggable: writable,
4143
+ onDragStart: (event) => {
4144
+ event.dataTransfer.effectAllowed = "move";
4145
+ event.dataTransfer.setData("text/plain", entry.id);
4146
+ onDragStart(entry.id);
4147
+ },
4148
+ onDragEnd,
4149
+ onKeyDown: (event) => {
4150
+ onKeyDown(event, entry);
4151
+ },
4152
+ onDragOver: (event) => {
4153
+ onDragOver(event, entry.id);
4154
+ },
4155
+ onDrop: (event) => {
4156
+ onDrop(event, entry.id);
4157
+ },
4158
+ children: [
4159
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4160
+ "span",
4161
+ {
4162
+ style: handleStyle(writable),
4163
+ draggable: writable,
4164
+ "aria-hidden": "true",
4165
+ "data-hiwork-menu-handle": entry.id,
4166
+ dangerouslySetInnerHTML: { __html: HANDLE_ICON }
4167
+ }
4168
+ ),
4169
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: iconStyle, dangerouslySetInnerHTML: { __html: entry.icon ?? DEFAULT_FEATURE_ICON } }),
4170
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: labelStyle2, children: entry.label }),
4171
+ entry.marked ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: markedStyle, children: "\u5DF2\u8BBE\u7F6E" }) : null,
4172
+ /* @__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)(
4173
+ "button",
4174
+ {
4175
+ type: "button",
4176
+ style: optionStyle(entry.placement === placement, writable),
4177
+ "aria-pressed": entry.placement === placement,
4178
+ "data-hiwork-menu-option": placement,
4179
+ disabled: !writable,
4180
+ onClick: () => {
4181
+ onPlacement(entry.id, placement);
4182
+ },
4183
+ children: PLACEMENT_LABELS[placement]
4184
+ },
4185
+ placement
4186
+ )) })
4187
+ ]
4188
+ }
4189
+ );
4190
+ }
4191
+ function MenuSection(props) {
4192
+ const state = props.useMenu((snapshot) => snapshot);
4193
+ const [dragId, setDragId] = (0, import_react8.useState)(void 0);
4194
+ const [drop, setDrop] = (0, import_react8.useState)(void 0);
4195
+ const { setPlacement, move, moveNear, reset } = props.actions;
4196
+ const writable = state.writable;
4197
+ const onDragOver = (event, id) => {
4198
+ if (dragId === void 0 || dragId === id) return;
4199
+ event.preventDefault();
4200
+ event.dataTransfer.dropEffect = "move";
4201
+ const rect = event.currentTarget.getBoundingClientRect();
4202
+ const position = event.clientY < rect.top + rect.height / 2 ? "before" : "after";
4203
+ setDrop((current) => current?.id === id && current.position === position ? current : { id, position });
4204
+ };
4205
+ const onDrop = (event, id) => {
4206
+ if (dragId === void 0 || dragId === id) return;
4207
+ event.preventDefault();
4208
+ const rect = event.currentTarget.getBoundingClientRect();
4209
+ const position = event.clientY < rect.top + rect.height / 2 ? "before" : "after";
4210
+ const moving = dragId;
4211
+ setDragId(void 0);
4212
+ setDrop(void 0);
4213
+ void moveNear(moving, id, position);
4214
+ };
4215
+ const onKeyDown = (event, entry) => {
4216
+ if (!writable) return;
4217
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
4218
+ if (event.target !== event.currentTarget) return;
4219
+ event.preventDefault();
4220
+ void move(entry.id, event.key === "ArrowUp" ? -1 : 1);
4221
+ };
4222
+ const marked = state.entries.some((entry) => entry.marked);
4223
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "aria-label": "\u4FA7\u680F\u83DC\u5355", style: sectionStyle4, "data-hiwork-menu-section": "", children: [
4224
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("header", { style: headerStyle2, children: [
4225
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: titleStyle3, children: "\u4FA7\u680F\u83DC\u5355" }),
4226
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4227
+ "button",
4228
+ {
4229
+ type: "button",
4230
+ style: writable && marked ? secondaryButton : disabledButton,
4231
+ disabled: !writable || !marked,
4232
+ "data-hiwork-menu-reset": "",
4233
+ onClick: () => {
4234
+ void reset();
4235
+ },
4236
+ children: "\u6062\u590D\u9ED8\u8BA4"
4237
+ }
4238
+ )
4239
+ ] }),
4240
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { style: noteStyle2, children: [
4241
+ "\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 ",
4242
+ state.limit,
4243
+ " \u4E2A\u5165\u53E3\u3002"
4244
+ ] }),
4245
+ state.reason === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: reasonStyle, "data-hiwork-menu-reason": "", children: state.reason }),
4246
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: listStyle2, children: state.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4247
+ MenuRow,
4248
+ {
4249
+ entry,
4250
+ writable,
4251
+ dragging: dragId === entry.id,
4252
+ drop: drop?.id === entry.id ? drop.position : void 0,
4253
+ onPlacement: (id, placement) => {
4254
+ void setPlacement(id, placement);
4255
+ },
4256
+ onDragStart: setDragId,
4257
+ onDragOver,
4258
+ onDrop,
4259
+ onDragEnd: () => {
4260
+ setDragId(void 0);
4261
+ setDrop(void 0);
4262
+ },
4263
+ onKeyDown
4264
+ },
4265
+ entry.id
4266
+ )) }),
4267
+ 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,
4268
+ /* @__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" })
4269
+ ] });
4270
+ }
4271
+
4272
+ // src/client/menu-store.ts
4273
+ var import_dsh_client_store2 = require("@deepseek-ai/dsh-client-store");
4274
+ var MENU_FIELD = "menu";
4275
+ var LOADING_STATE = {
4276
+ status: "loading",
4277
+ writable: false,
4278
+ entries: [],
4279
+ limit: DEFAULT_SIDEBAR_LIMIT,
4280
+ reason: void 0
4281
+ };
4282
+ function reasonFor(status, writable) {
4283
+ if (status === "loading") return "\u6B63\u5728\u8BFB\u53D6\u5F53\u524D\u8BBE\u7F6E\u2026";
4284
+ 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";
4285
+ if (!writable) return "\u5F53\u524D\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u83DC\u5355\u504F\u597D\u3002";
4286
+ return void 0;
4287
+ }
4288
+ function toPathOps(ops) {
4289
+ return ops.map((op) => ({ op: "set", path: [MENU_FIELD, ...op.path], value: [...op.value] }));
4290
+ }
4291
+ var MenuStore = class {
4292
+ /**
4293
+ * @param form - `ai-router` entry 的配置表单(与网关卡片同一个共享实例,见 `src/client/index.ts`)。
4294
+ * @param registry - feature 注册表(列表内容与顺序的另一个来源)。
4295
+ * @param additionalFeatures - 追加的非注册表条目(官方「插件」代理项;可用性由调用方判断)。
4296
+ * 缺省时列表只有注册表成员——与旧版行为一致。
4297
+ */
4298
+ constructor(form, registry, additionalFeatures) {
4299
+ this.form = form;
4300
+ this.registry = registry;
4301
+ this.additionalFeatures = additionalFeatures;
4302
+ this.store = (0, import_dsh_client_store2.createSnapshotStore)(LOADING_STATE);
4303
+ const publish = () => {
4304
+ this.store.set(this.view());
4305
+ };
4306
+ publish();
4307
+ this.unsubscribe.push(form.subscribe(publish));
4308
+ this.unsubscribe.push(registry.subscribe(publish));
4309
+ }
4310
+ store;
4311
+ unsubscribe = [];
4312
+ /** 当前偏好(未就绪 / 值非法时按「从未自定义」处理)。 */
4313
+ preferences() {
4314
+ const snapshot = this.form.getSnapshot();
4315
+ if (snapshot.status !== "ready") return EMPTY_MENU_PREFERENCES;
4316
+ return normalizeMenuPreferences(snapshot.value?.menu);
4317
+ }
4318
+ /** 订阅偏好变化(菜单适配器用;与 `preferences()` 同源)。 */
4319
+ subscribePreferences(listener) {
4320
+ return this.form.subscribe(listener);
4321
+ }
4322
+ /** 菜单条目全集(注册表 + 追加的代理项),与侧栏适配器的来源同构。 */
4323
+ menuFeatures() {
4324
+ return [...this.registry.list(), ...this.additionalFeatures?.() ?? []];
4325
+ }
4326
+ /** 当前渲染状态。 */
4327
+ view() {
4328
+ const snapshot = this.form.getSnapshot();
4329
+ const value = snapshot.status === "ready" ? normalizeMenuPreferences(snapshot.value?.menu) : EMPTY_MENU_PREFERENCES;
4330
+ const features = this.menuFeatures();
4331
+ const layout = menuLayout(features, value);
4332
+ const placementById = new Map([
4333
+ ...layout.sidebar.map((feature) => [feature.id, "sidebar"]),
4334
+ ...layout.more.map((feature) => [feature.id, "more"])
4335
+ ]);
4336
+ const byId = new Map(features.map((feature) => [feature.id, feature]));
4337
+ const ordered = orderedFeatureIds(features, value);
4338
+ const entries = ordered.flatMap((id, index) => {
4339
+ const feature = byId.get(id);
4340
+ if (feature === void 0) return [];
4341
+ return [{
4342
+ id,
4343
+ label: feature.label(),
4344
+ icon: feature.icon,
4345
+ // 生效去向:不在前两组里的就是被隐藏的(hidden 的项两者都不出现)。
4346
+ placement: placementById.get(id) ?? "hidden",
4347
+ marked: placementOf(value, id) !== void 0,
4348
+ index,
4349
+ total: ordered.length
4350
+ }];
4351
+ });
4352
+ const limit = Math.max(0, DEFAULT_SIDEBAR_LIMIT - value.more.filter((id) => features.some((feature) => feature.id === id)).length);
4353
+ return {
4354
+ status: snapshot.status,
4355
+ writable: snapshot.status === "ready" && snapshot.writable,
4356
+ entries,
4357
+ limit,
4358
+ reason: reasonFor(snapshot.status, snapshot.writable)
4359
+ };
4360
+ }
4361
+ /** 清掉在这份 store 上的全部订阅(插件卸载时调用)。 */
4362
+ dispose() {
4363
+ for (const off of this.unsubscribe.splice(0)) off();
4364
+ }
4365
+ /** 立即重算渲染状态(追加条目的可用性变化等非表单/注册表来源的变化走这里)。 */
4366
+ refresh() {
4367
+ this.store.set(this.view());
4368
+ }
4369
+ /** 注入面:设置分区组件消费。 */
4370
+ inject() {
4371
+ return {
4372
+ hooks: { menu: this.store },
4373
+ actions: {
4374
+ setPlacement: (id, placement) => this.setPlacement(id, placement),
4375
+ move: (id, offset) => this.move(id, offset),
4376
+ moveNear: (id, targetId, position) => this.moveNear(id, targetId, position),
4377
+ reset: () => this.reset()
4378
+ }
4379
+ };
4380
+ }
4381
+ async setPlacement(id, placement) {
4382
+ const current = this.preferences();
4383
+ const effective = this.view().entries.find((entry) => entry.id === id)?.placement;
4384
+ if (effective === placement) return;
4385
+ await this.write(setMenuPlacement(current, id, placement), current);
4386
+ }
4387
+ async move(id, offset) {
4388
+ const current = this.preferences();
4389
+ const ids = this.view().entries.map((entry) => entry.id);
4390
+ const index = ids.indexOf(id);
4391
+ if (index < 0) return;
4392
+ await this.write(moveMenuEntry(current, this.menuFeatures(), id, index + offset), current);
4393
+ }
4394
+ async moveNear(id, targetId, position) {
4395
+ const current = this.preferences();
4396
+ await this.write(moveMenuEntryNear(current, this.menuFeatures(), id, targetId, position), current);
4397
+ }
4398
+ async reset() {
4399
+ const current = this.form.getSnapshot();
4400
+ if (current.status !== "ready" || !current.writable) return;
4401
+ await this.form.mutate([{ op: "unset", path: [MENU_FIELD] }]);
4402
+ }
4403
+ /** 未标记项的生效去向已由 `view()` 给出,这里不再另算一份。 */
4404
+ async write(next, current) {
4405
+ const snapshot = this.form.getSnapshot();
4406
+ if (snapshot.status !== "ready" || !snapshot.writable) return;
4407
+ const ops = menuPreferenceOps(current, next);
4408
+ if (ops.length === 0) return;
4409
+ await this.form.mutate(toPathOps(ops));
4410
+ }
4411
+ };
4412
+
4413
+ // src/client/images-data.ts
4414
+ var IMAGE_CHANNEL = "/hiwork-image";
4415
+ var IMAGE_LIST_PAGE_SIZE = 20;
4416
+ var IMAGE_LIST_MAX_PAGE_SIZE = 100;
4417
+ var IMAGE_DATA_ERROR_TEXT = "\u56FE\u7247\u8BB0\u5F55\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
4418
+ var ImageRpcError = class extends Error {
4419
+ constructor(code, message) {
4420
+ super(message);
4421
+ this.code = code;
4422
+ this.name = "ImageRpcError";
4423
+ }
4424
+ };
4425
+ function unwrapImageResult(value) {
4426
+ if (typeof value !== "object" || value === null || !("ok" in value)) {
4427
+ throw new ImageRpcError("internal", "\u56FE\u7247\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
4428
+ }
4429
+ const result = value;
4430
+ if (result.ok === true) return result.value;
4431
+ if (result.ok === false) {
4432
+ const code = typeof result.error?.code === "string" ? result.error.code : "internal";
4433
+ const message = typeof result.error?.message === "string" && result.error.message.trim() !== "" ? result.error.message : IMAGE_DATA_ERROR_TEXT;
4434
+ throw new ImageRpcError(code, message);
4435
+ }
4436
+ throw new ImageRpcError("internal", "\u56FE\u7247\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
4437
+ }
4438
+ function imageErrorOf(error) {
4439
+ if (error instanceof ImageRpcError) return { code: error.code, message: error.message };
4440
+ return { code: "internal", message: IMAGE_DATA_ERROR_TEXT };
4441
+ }
4442
+ function str(value) {
4443
+ return typeof value === "string" ? value : "";
4444
+ }
4445
+ function num(value) {
4446
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
4447
+ }
4448
+ function normalizeImageListPage(value, fallbackPage, fallbackPageSize) {
4449
+ const record = typeof value === "object" && value !== null ? value : {};
4450
+ const rawItems = record["items"];
4451
+ if (!Array.isArray(rawItems)) throw new ImageRpcError("internal", "\u56FE\u7247\u5217\u8868\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
4452
+ const items = [];
4453
+ for (const entry of rawItems) {
4454
+ const row = typeof entry === "object" && entry !== null ? entry : {};
4455
+ items.push({
4456
+ id: num(row["id"]),
4457
+ prompt: str(row["prompt"]),
4458
+ ...str(row["revisedPrompt"]) === "" ? {} : { revisedPrompt: str(row["revisedPrompt"]) },
4459
+ model: str(row["model"]),
4460
+ // `size` 是网关侧唯一的新增投影(spec §5.2 末);缺失时留空串,视图渲染 `—`。
4461
+ size: str(row["size"]),
4462
+ format: str(row["format"]),
4463
+ mimeType: str(row["mimeType"]),
4464
+ byteSize: num(row["byteSize"]),
4465
+ generationMs: num(row["generationMs"]),
4466
+ createdAt: str(row["createdAt"])
4467
+ });
4468
+ }
4469
+ return {
4470
+ items,
4471
+ total: num(record["total"]),
4472
+ page: num(record["page"]) > 0 ? num(record["page"]) : fallbackPage,
4473
+ pageSize: num(record["pageSize"]) > 0 ? num(record["pageSize"]) : fallbackPageSize
4474
+ };
4475
+ }
4476
+ function normalizeImageBytes(value) {
4477
+ const record = typeof value === "object" && value !== null ? value : {};
4478
+ const dataBase64 = str(record["dataBase64"]);
4479
+ if (dataBase64 === "") throw new ImageRpcError("internal", "\u56FE\u7247\u6570\u636E\u4E3A\u7A7A\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
4480
+ return {
4481
+ dataBase64,
4482
+ mimeType: str(record["mimeType"]) === "" ? "image/png" : str(record["mimeType"]),
4483
+ byteSize: num(record["byteSize"])
4484
+ };
4485
+ }
4486
+ var ImageGallerySource = class {
4487
+ constructor(rpc) {
4488
+ this.rpc = rpc;
4489
+ }
4490
+ /** 分页拉取历史记录(`page` 从 1 开始;`pageSize` 上限 100)。 */
4491
+ async list(page = 1, pageSize = IMAGE_LIST_PAGE_SIZE) {
4492
+ const size = Math.min(Math.max(1, Math.trunc(pageSize)), IMAGE_LIST_MAX_PAGE_SIZE);
4493
+ const raw = await this.rpc.call(IMAGE_CHANNEL, "list", { page: Math.max(1, Math.trunc(page)), pageSize: size });
4494
+ return normalizeImageListPage(unwrapImageResult(raw), page, size);
4495
+ }
4496
+ /** 取一张图的本体(**点哪张拉哪张**;超 20MiB 由 Host 回 `IMAGE_TOO_LARGE`)。 */
4497
+ async fetch(id) {
4498
+ const raw = await this.rpc.call(IMAGE_CHANNEL, "fetch", { id });
4499
+ return normalizeImageBytes(unwrapImageResult(raw));
4500
+ }
4501
+ /** 把一张图存到工作区(缺省落默认工作区,由 Host 决定)。 */
4502
+ async save(id, workspaceId) {
4503
+ const payload = workspaceId === void 0 || workspaceId === "" ? { id } : { id, workspaceId };
4504
+ const raw = await this.rpc.call(IMAGE_CHANNEL, "save", payload);
4505
+ return { path: str(unwrapImageResult(raw)["path"]) };
4506
+ }
4507
+ };
4508
+ function imageDataUrl(bytes) {
4509
+ const mime = bytes.mimeType === "" ? "image/png" : bytes.mimeType;
4510
+ return `data:${mime};base64,${bytes.dataBase64}`;
4511
+ }
4512
+ function formatImageBytes(bytes) {
4513
+ if (bytes === void 0 || !Number.isFinite(bytes) || bytes <= 0) return "";
4514
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4515
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
4516
+ return `${bytes} B`;
4517
+ }
4518
+ function imagePromptSummary(prompt, limit = 42) {
4519
+ const text6 = prompt.trim().replace(/\s+/g, " ");
4520
+ if (text6 === "") return "\uFF08\u65E0\u63D0\u793A\u8BCD\uFF09";
4521
+ return text6.length <= limit ? text6 : `${text6.slice(0, limit)}\u2026`;
4522
+ }
4523
+ function imageTimeLabel(createdAt) {
4524
+ const date = new Date(createdAt);
4525
+ if (Number.isNaN(date.getTime())) return "";
4526
+ return date.toLocaleString("zh-CN", { hour12: false });
4527
+ }
4528
+
4529
+ // src/client/images-page.tsx
4530
+ var import_react9 = require("react");
4531
+
4532
+ // src/client/image-styles.ts
4533
+ var IMAGES_STYLE_ID = "hiwork-core-images-styles";
4534
+ var IMAGES_STYLE_TAG_ID = "hiwork-core/images.css";
4535
+ function ensureImagesStyle(doc) {
4536
+ if (doc.getElementById(IMAGES_STYLE_ID) !== null) return;
4537
+ const style = doc.createElement("style");
4538
+ style.id = IMAGES_STYLE_ID;
4539
+ ownStyleTag(style, IMAGES_STYLE_TAG_ID);
4540
+ style.textContent = [imageCardsCss, imagesPageCss].join("\n");
4541
+ doc.head.appendChild(style);
4542
+ }
4543
+
4544
+ // src/client/images-page.tsx
4545
+ var IMAGES_FEATURE_ID = "images";
4546
+ var IMAGES_FEATURE_ORDER = 60;
4547
+ var IMAGES_FEATURE_LABEL = "\u6211\u7684\u56FE\u7247";
4548
+ var IMAGES_FEATURE_EFFECT_LABEL = "hiwork-core: images feature";
4549
+ 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>';
4550
+ var IMAGES_EMPTY_TEXT = "\u8FD8\u6CA1\u6709\u751F\u6210\u8FC7\u56FE\u7247";
4551
+ var IMAGES_EMPTY_HINT = "\u5728\u4F1A\u8BDD\u91CC\u8BF4\u300E\u753B\u4E00\u5F20\u2026\u300F\u5C31\u80FD\u751F\u6210";
4552
+ var IMAGES_LOADING_TEXT = "\u6B63\u5728\u52A0\u8F7D\u56FE\u7247\u8BB0\u5F55\u2026";
4553
+ var IMAGES_PREVIEW_PLACEHOLDER = "\u4ECE\u5DE6\u4FA7\u9009\u4E00\u5F20\u56FE\u7247\u67E5\u770B";
4554
+ var IMAGES_PREVIEW_LOADING_TEXT = "\u6B63\u5728\u52A0\u8F7D\u56FE\u7247\u2026";
4555
+ var IMAGES_DISABLED_NOTE = "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u56FE\u7247\u751F\u6210";
4556
+ var IMAGES_MISSING_TEXT = "\u2014";
4557
+ var IMAGES_SAVE_TEXT = "\u5B58\u5230\u5DE5\u4F5C\u533A";
4558
+ var IMAGES_SAVE_SAVED_PREFIX = "\u5DF2\u4FDD\u5B58\u5230 ";
4559
+ var IMAGES_SAVE_RUNNING_TEXT = "\u6B63\u5728\u4FDD\u5B58\u2026";
4560
+ var IMAGES_COPY_TEXT = "\u590D\u5236\u63D0\u793A\u8BCD";
4561
+ var IMAGES_MORE_TEXT = "\u52A0\u8F7D\u66F4\u591A";
4562
+ var IMAGES_RETRY_TEXT = "\u91CD\u8BD5";
4563
+ 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";
4564
+ var IMAGES_COPY_HINTS = { copied: "\u5DF2\u590D\u5236", failed: "\u590D\u5236\u5931\u8D25" };
4565
+ async function copyText(text6) {
4566
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
4567
+ if (clipboard === void 0 || typeof clipboard.writeText !== "function") return false;
4568
+ try {
4569
+ await clipboard.writeText(text6);
4570
+ return true;
4571
+ } catch {
4572
+ return false;
4573
+ }
4574
+ }
4575
+ function rowMeta(row) {
4576
+ const time = imageTimeLabel(row.createdAt);
4577
+ return [
4578
+ row.size === "" ? IMAGES_MISSING_TEXT : row.size,
4579
+ time === "" ? IMAGES_MISSING_TEXT : time,
4580
+ formatImageBytes(row.byteSize)
4581
+ ].filter((part) => part !== "").join(" \xB7 ");
4582
+ }
4583
+ function ImagesPage({ source }) {
4584
+ const [rows, setRows] = (0, import_react9.useState)([]);
4585
+ const [total, setTotal] = (0, import_react9.useState)(0);
4586
+ const [page, setPage] = (0, import_react9.useState)(1);
4587
+ const [listState, setListState] = (0, import_react9.useState)("loading");
4588
+ const [listFailure, setListFailure] = (0, import_react9.useState)(null);
4589
+ const [selectedId, setSelectedId] = (0, import_react9.useState)(void 0);
4590
+ const [preview, setPreview] = (0, import_react9.useState)({ status: "idle" });
4591
+ const [saveState, setSaveState] = (0, import_react9.useState)({ status: "idle" });
4592
+ const [copyHint, setCopyHint] = (0, import_react9.useState)("");
4593
+ const listTicket = (0, import_react9.useRef)(0);
4594
+ const previewTicket = (0, import_react9.useRef)(0);
4595
+ const alive = (0, import_react9.useRef)(true);
4596
+ (0, import_react9.useEffect)(() => {
4597
+ if (typeof document === "undefined") return;
4598
+ ensureImagesStyle(document);
4599
+ }, []);
4600
+ (0, import_react9.useEffect)(() => {
4601
+ alive.current = true;
4602
+ return () => {
4603
+ alive.current = false;
4604
+ };
4605
+ }, []);
4606
+ const load = (0, import_react9.useCallback)(async (nextPage, append) => {
4607
+ const current = listTicket.current + 1;
4608
+ listTicket.current = current;
4609
+ setListState("loading");
4610
+ try {
4611
+ const result = await source.list(nextPage, IMAGE_LIST_PAGE_SIZE);
4612
+ if (!alive.current || current !== listTicket.current) return;
4613
+ setRows((prev) => append ? [...prev, ...result.items.filter((item) => !prev.some((existing) => existing.id === item.id))] : result.items);
4614
+ setTotal(result.total);
4615
+ setPage(result.page);
4616
+ setListFailure(null);
4617
+ setListState("ready");
4618
+ } catch (error) {
4619
+ if (!alive.current || current !== listTicket.current) return;
4620
+ setListFailure(imageErrorOf(error));
4621
+ setListState("failed");
4622
+ }
4623
+ }, [source]);
4624
+ (0, import_react9.useEffect)(() => {
4625
+ void load(1, false);
4626
+ }, [load]);
4627
+ const select = (0, import_react9.useCallback)(async (row) => {
4628
+ setSelectedId(row.id);
4629
+ setSaveState({ status: "idle" });
4630
+ setCopyHint("");
4631
+ const current = previewTicket.current + 1;
4632
+ previewTicket.current = current;
4633
+ setPreview({ status: "loading" });
4634
+ try {
4635
+ const bytes = await source.fetch(row.id);
4636
+ if (!alive.current || current !== previewTicket.current) return;
4637
+ setPreview({ status: "ready", url: imageDataUrl(bytes), byteSize: bytes.byteSize });
4638
+ } catch (error) {
4639
+ if (!alive.current || current !== previewTicket.current) return;
4640
+ const failure = imageErrorOf(error);
4641
+ setPreview({ status: "failed", code: failure.code, message: failure.message });
4642
+ }
4643
+ }, [source]);
4644
+ const save = (0, import_react9.useCallback)(async () => {
4645
+ if (selectedId === void 0) return;
4646
+ setSaveState({ status: "saving" });
4647
+ try {
4648
+ const { path } = await source.save(selectedId);
4649
+ if (!alive.current) return;
4650
+ setSaveState({ status: "saved", path });
4651
+ } catch (error) {
4652
+ if (!alive.current) return;
4653
+ setSaveState({ status: "failed", message: imageErrorOf(error).message });
4654
+ }
4655
+ }, [source, selectedId]);
4656
+ const selected = rows.find((row) => row.id === selectedId);
4657
+ const disabled = listFailure?.code === "IMAGE_DISABLED" || preview.status === "failed" && preview.code === "IMAGE_DISABLED";
4658
+ const previewBody = (() => {
4659
+ if (preview.status === "idle") {
4660
+ return (0, import_react9.createElement)("p", { className: "hiwork-images-note", "data-hiwork-images-placeholder": "" }, IMAGES_PREVIEW_PLACEHOLDER);
4661
+ }
4662
+ if (preview.status === "loading") {
4663
+ return (0, import_react9.createElement)("p", { className: "hiwork-images-note", "data-hiwork-images-loading": "" }, IMAGES_PREVIEW_LOADING_TEXT);
4664
+ }
4665
+ if (preview.status === "failed") {
4666
+ return (0, import_react9.createElement)(
4667
+ "div",
4668
+ { className: "hiwork-images-actions", "data-hiwork-images-preview-error": preview.code },
4669
+ (0, import_react9.createElement)("span", { className: "hiwork-images-error" }, preview.message),
4670
+ selected === void 0 ? null : (0, import_react9.createElement)(
4671
+ "button",
4672
+ { type: "button", className: "hiwork-images-action", "data-hiwork-images-retry-preview": "", onClick: () => {
4673
+ void select(selected);
4674
+ } },
4675
+ IMAGES_RETRY_TEXT
4676
+ )
4677
+ );
4678
+ }
4679
+ return (0, import_react9.createElement)(
4680
+ "div",
4681
+ { className: "hiwork-images-preview-body" },
4682
+ // 字节来自本次 fetch:data URL 渲染,**不写盘、不指向任何本地文件**。
4683
+ (0, import_react9.createElement)("img", {
4684
+ className: "hiwork-images-image",
4685
+ src: preview.url,
4686
+ alt: selected === void 0 || selected.prompt.trim() === "" ? "\u5386\u53F2\u56FE\u7247" : selected.prompt.trim(),
4687
+ "data-hiwork-images-image": ""
4688
+ }),
4689
+ selected === void 0 ? null : (0, import_react9.createElement)("p", { className: "hiwork-images-preview-prompt", "data-hiwork-images-prompt": "" }, selected.prompt),
4690
+ selected === void 0 ? null : (0, import_react9.createElement)("p", { className: "hiwork-images-note", "data-hiwork-images-preview-meta": "" }, [
4691
+ rowMeta(selected),
4692
+ formatImageBytes(preview.byteSize)
4693
+ ].filter((part) => part !== "").join(" \xB7 ")),
4694
+ (0, import_react9.createElement)(
4695
+ "div",
4696
+ { className: "hiwork-images-actions" },
4697
+ (0, import_react9.createElement)(
4698
+ "button",
4699
+ {
4700
+ type: "button",
4701
+ className: "hiwork-images-action",
4702
+ "data-hiwork-images-save": "",
4703
+ disabled: saveState.status === "saving",
4704
+ onClick: () => {
4705
+ void save();
4706
+ }
4707
+ },
4708
+ saveState.status === "saving" ? IMAGES_SAVE_RUNNING_TEXT : IMAGES_SAVE_TEXT
4709
+ ),
4710
+ (0, import_react9.createElement)(
4711
+ "button",
4712
+ {
4713
+ type: "button",
4714
+ className: "hiwork-images-action",
4715
+ "data-hiwork-images-copy": "",
4716
+ onClick: () => {
4717
+ void copyText(selected?.prompt ?? "").then((ok) => {
4718
+ setCopyHint(ok ? IMAGES_COPY_HINTS.copied : IMAGES_COPY_HINTS.failed);
4719
+ });
4720
+ }
4721
+ },
4722
+ IMAGES_COPY_TEXT
4723
+ ),
4724
+ copyHint === "" ? null : (0, import_react9.createElement)("span", { className: "hiwork-images-note", "data-hiwork-images-copy-state": copyHint }, copyHint)
4725
+ ),
4726
+ saveState.status === "saved" ? (0, import_react9.createElement)("span", { className: "hiwork-images-saved", "data-hiwork-images-saved": "" }, `${IMAGES_SAVE_SAVED_PREFIX}${saveState.path}`) : null,
4727
+ saveState.status === "failed" ? (0, import_react9.createElement)("span", { className: "hiwork-images-error", "data-hiwork-images-save-error": "" }, saveState.message) : null,
4728
+ (0, import_react9.createElement)("p", { className: "hiwork-images-note" }, IMAGES_SAVE_HINT)
4729
+ );
4730
+ })();
4731
+ const empty = listState === "ready" && rows.length === 0;
4732
+ return (0, import_react9.createElement)(
4733
+ "section",
4734
+ { className: "hiwork-images", "data-hiwork-images": "", "data-hiwork-images-state": listState },
4735
+ (0, import_react9.createElement)(
4736
+ "div",
4737
+ { className: "hiwork-images-head" },
4738
+ (0, import_react9.createElement)("h2", { className: "hiwork-images-title" }, IMAGES_FEATURE_LABEL),
4739
+ (0, import_react9.createElement)(
4740
+ "span",
4741
+ { className: "hiwork-images-count", "data-hiwork-images-count": "" },
4742
+ listState === "loading" && rows.length === 0 ? IMAGES_LOADING_TEXT : `\u5171 ${total} \u5F20`
4743
+ )
4744
+ ),
4745
+ // 关开关只关「生成」不关「查看」:历史照常可见,但要说清当前不能再生成(spec §7.2 V11)。
4746
+ disabled ? (0, import_react9.createElement)("div", { className: "hiwork-images-banner", "data-hiwork-images-disabled": "" }, IMAGES_DISABLED_NOTE) : null,
4747
+ // 列表级失败:行内红字 + 重试(成功过一页的话**保留已有行**,不清空)。
4748
+ listFailure === null ? null : (0, import_react9.createElement)(
4749
+ "div",
4750
+ { className: "hiwork-images-actions", "data-hiwork-images-error": listFailure.code },
4751
+ (0, import_react9.createElement)("span", { className: "hiwork-images-error" }, listFailure.message),
4752
+ (0, import_react9.createElement)(
4753
+ "button",
4754
+ { type: "button", className: "hiwork-images-action", "data-hiwork-images-retry": "", onClick: () => {
4755
+ void load(page, rows.length > 0);
4756
+ } },
4757
+ IMAGES_RETRY_TEXT
4758
+ )
4759
+ ),
4760
+ empty ? (0, import_react9.createElement)(
4761
+ "div",
4762
+ { className: "hiwork-images-empty", "data-hiwork-images-empty": "" },
4763
+ (0, import_react9.createElement)("p", { className: "hiwork-images-empty-title" }, IMAGES_EMPTY_TEXT),
4764
+ (0, import_react9.createElement)("p", { className: "hiwork-images-note" }, IMAGES_EMPTY_HINT)
4765
+ ) : (0, import_react9.createElement)(
4766
+ "div",
4767
+ { className: "hiwork-images-body" },
4768
+ (0, import_react9.createElement)(
4769
+ "ul",
4770
+ { className: "hiwork-images-list", "data-hiwork-images-list": "" },
4771
+ ...rows.map((row) => (0, import_react9.createElement)(
4772
+ "li",
4773
+ { key: row.id },
4774
+ (0, import_react9.createElement)(
4775
+ "button",
4776
+ {
4777
+ type: "button",
4778
+ className: "hiwork-images-row",
4779
+ "data-hiwork-images-row": row.id,
4780
+ "aria-pressed": row.id === selectedId,
4781
+ onClick: () => {
4782
+ void select(row);
4783
+ }
4784
+ },
4785
+ (0, import_react9.createElement)("span", { className: "hiwork-images-row-prompt" }, imagePromptSummary(row.prompt)),
4786
+ (0, import_react9.createElement)("span", { className: "hiwork-images-row-meta", "data-hiwork-images-row-meta": "" }, rowMeta(row))
4787
+ )
4788
+ ))
4789
+ ),
4790
+ (0, import_react9.createElement)("div", { className: "hiwork-images-preview", "data-hiwork-images-preview": "" }, previewBody)
4791
+ ),
4792
+ rows.length > 0 && rows.length < total ? (0, import_react9.createElement)(
4793
+ "button",
4794
+ {
4795
+ type: "button",
4796
+ className: "hiwork-images-more",
4797
+ "data-hiwork-images-more": "",
4798
+ disabled: listState === "loading",
4799
+ onClick: () => {
4800
+ void load(page + 1, true);
4801
+ }
4802
+ },
4803
+ IMAGES_MORE_TEXT
4804
+ ) : null
4805
+ );
4806
+ }
4807
+ function imagesFeature(source) {
4808
+ return {
4809
+ id: IMAGES_FEATURE_ID,
4810
+ order: IMAGES_FEATURE_ORDER,
4811
+ label: () => IMAGES_FEATURE_LABEL,
4812
+ icon: IMAGES_FEATURE_ICON,
4813
+ render: () => (0, import_react9.createElement)(ImagesPage, { source })
4814
+ };
4815
+ }
4816
+ function installImagesFeature(scope, registry, source) {
4817
+ scope.effect(() => registry.register(imagesFeature(source)), IMAGES_FEATURE_EFFECT_LABEL);
4818
+ }
4819
+
4820
+ // src/client/image-cards.tsx
4821
+ var import_react10 = require("react");
4822
+ var IMAGE_GENERATE_TOOL_NAME = "image_generate";
4823
+ var TOOL_VIEW_SLOT = "tool.call.toolview";
4824
+ var IMAGE_CARD_TITLE = "\u751F\u6210\u56FE\u7247";
4825
+ var IMAGE_CARD_GENERIC_ERROR = "\u56FE\u7247\u751F\u6210\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
4826
+ var IMAGE_ERROR_TEXT = {
4827
+ IMAGE_DISABLED: "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u56FE\u7247\u751F\u6210",
4828
+ IMAGE_NOT_CONFIGURED: "\u56FE\u7247\u751F\u6210\u8FD8\u6CA1\u6709\u914D\u7F6E\u597D\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
4829
+ 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",
4830
+ IMAGE_BAD_REQUEST: "\u8FD9\u4E2A\u63D0\u793A\u8BCD\u6CA1\u80FD\u751F\u6210\uFF0C\u6362\u4E2A\u8BF4\u6CD5\u518D\u8BD5",
4831
+ IMAGE_TOO_LARGE: "\u751F\u6210\u7684\u56FE\u7247\u592A\u5927\u4E86\uFF0C\u6362\u4E2A\u5C0F\u4E00\u70B9\u7684\u5C3A\u5BF8\uFF08\u5982 1024x1024\uFF09",
4832
+ IMAGE_TIMEOUT: "\u751F\u6210\u8D85\u65F6\u4E86\uFF0C\u518D\u8BD5\u4E00\u6B21\uFF08\u590D\u6742\u753B\u9762\u4F1A\u6BD4\u8F83\u6162\uFF09",
4833
+ IMAGE_UPSTREAM_ERROR: "\u56FE\u7247\u751F\u6210\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
4834
+ GATEWAY_UNREACHABLE: "\u56FE\u7247\u751F\u6210\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
4835
+ // spec §6.6 把 SESSION_EXPIRED 并入「服务不可用」那一行(**逐字同该行**),只在括号里
4836
+ // 注明例外「提示重新登录」——所以不加自创文案,另给一句 {@link IMAGE_RELOGIN_HINT}。
4837
+ SESSION_EXPIRED: "\u56FE\u7247\u751F\u6210\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
4838
+ // Host 侧写盘失败(src/imagegen.ts 的两种原因):图已经生成、也已经计费,绝不能让
4839
+ // 员工以为白花了钱——文案必须说清「图在,只是没落到工作区」。
4840
+ "write-failed": "\u56FE\u7247\u5DF2\u751F\u6210\u4F46\u6CA1\u80FD\u4FDD\u5B58\u5230\u5DE5\u4F5C\u533A",
4841
+ "path-outside-workspace": "\u56FE\u7247\u6CA1\u80FD\u4FDD\u5B58\u5230\u5DE5\u4F5C\u533A\uFF1A\u76EE\u6807\u8DEF\u5F84\u4E0D\u5728\u5DE5\u4F5C\u533A\u91CC",
4842
+ cancelled: "\u5DF2\u53D6\u6D88",
4843
+ // 平台级中断码(`interrupted` 是 shell 自己的停止标记,不是网关错误码)。
4844
+ interrupted: "\u5DF2\u505C\u6B62"
4845
+ };
4846
+ var IMAGE_RETRYABLE_CODES = [
4847
+ "IMAGE_BAD_REQUEST",
4848
+ "IMAGE_TOO_LARGE",
4849
+ "IMAGE_TIMEOUT",
4850
+ "IMAGE_UPSTREAM_ERROR",
4851
+ "GATEWAY_UNREACHABLE"
4852
+ ];
4853
+ var IMAGE_RELOGIN_CODES = ["SESSION_EXPIRED"];
4854
+ var IMAGE_RELOGIN_HINT = "\u8BF7\u91CD\u65B0\u767B\u5F55";
4855
+ function needsRelogin(code) {
4856
+ return IMAGE_RELOGIN_CODES.includes(code);
4857
+ }
4858
+ function imageErrorText(code, message = "") {
4859
+ const known = IMAGE_ERROR_TEXT[code];
4860
+ if (known !== void 0) return known;
4861
+ return message.trim() === "" ? IMAGE_CARD_GENERIC_ERROR : message;
4862
+ }
4863
+ function isRetryableImageError(code) {
4864
+ return IMAGE_RETRYABLE_CODES.includes(code);
4865
+ }
4866
+ function asRecord(value) {
4867
+ return typeof value === "object" && value !== null ? value : {};
4868
+ }
4869
+ function str2(value) {
4870
+ return typeof value === "string" ? value : "";
4871
+ }
4872
+ function num2(value) {
4873
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4874
+ }
4875
+ function parseImageArgs(argsRaw) {
4876
+ if (typeof argsRaw !== "string" || argsRaw.trim() === "") return {};
4877
+ try {
4878
+ return asRecord(JSON.parse(argsRaw));
4879
+ } catch {
4880
+ return {};
4881
+ }
4882
+ }
4883
+ function parseImageOutcome(content) {
4884
+ if (!Array.isArray(content)) return null;
4885
+ for (const block of content) {
4886
+ const text6 = typeof block?.text === "string" ? block.text : "";
4887
+ if (text6 === "") continue;
4888
+ const trimmed = text6.trim();
4889
+ if (!trimmed.startsWith("{")) continue;
4890
+ let parsed;
4891
+ try {
4892
+ parsed = JSON.parse(trimmed);
4893
+ } catch {
4894
+ continue;
4895
+ }
4896
+ const record = asRecord(parsed);
4897
+ if (typeof record["ok"] !== "boolean") continue;
4898
+ return record;
4899
+ }
4900
+ return null;
4901
+ }
4902
+ function parseImagePathsFromText(content) {
4903
+ if (!Array.isArray(content)) return [];
4904
+ const paths = [];
4905
+ for (const block of content) {
4906
+ const text6 = typeof block?.text === "string" ? block.text : "";
4907
+ for (const match of text6.matchAll(/<path>([^<>]+)<\/path>/g)) {
4908
+ const path = (match[1] ?? "").trim();
4909
+ if (path !== "" && !paths.includes(path)) paths.push(path);
4910
+ }
4911
+ }
4912
+ return paths;
4913
+ }
4914
+ function isAbsoluteImagePath(path) {
4915
+ return path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path);
4916
+ }
4917
+ function imageFileUrl(path, baseURI) {
4918
+ if (!isAbsoluteImagePath(path)) return "";
4919
+ return new URL(`api/file?path=${encodeURIComponent(path)}`, baseURI).href;
4920
+ }
4921
+ function abbreviateHome(path, home) {
4922
+ if (home === void 0 || home === "") return path;
4923
+ const trimmed = home.replace(/[\\/]+$/, "");
4924
+ if (trimmed === "") return path;
4925
+ if (path === trimmed) return "~";
4926
+ if (path.startsWith(`${trimmed}/`) || path.startsWith(`${trimmed}\\`)) return `~${path.slice(trimmed.length)}`;
4927
+ return path;
4928
+ }
4929
+ function formatGenerationMs(ms) {
4930
+ if (ms === void 0 || ms < 0) return "";
4931
+ if (ms < 1e3) return `${ms} \u6BEB\u79D2`;
4932
+ return `${(ms / 1e3).toFixed(1)} \u79D2`;
4933
+ }
4934
+ function formatByteSize(bytes) {
4935
+ if (bytes === void 0 || bytes < 0) return "";
4936
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4937
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
4938
+ return `${bytes} B`;
4939
+ }
4940
+ function toImageCardModel(block, baseURI) {
4941
+ const settled = block?.kind === "tool-result" || block?.phase === "result";
4942
+ const errorRecord = asRecord(block?.error);
4943
+ const errorCode = str2(errorRecord["code"]);
4944
+ const state = settled ? errorCode === "interrupted" ? "stopped" : block?.isError === true ? "error" : "ok" : "running";
4945
+ const args = parseImageArgs(settled ? block?.call?.argsRaw : block?.argsRaw);
4946
+ const outcome = settled ? parseImageOutcome(block?.content) : null;
4947
+ const prompt = str2(outcome?.["prompt"]) || str2(args["prompt"]);
4948
+ const size = str2(outcome?.["size"]) || str2(args["size"]);
4949
+ const model = str2(outcome?.["model"]);
4950
+ const generationMs = num2(outcome?.["generationMs"]);
4951
+ const alt = prompt.trim() === "" ? "\u751F\u6210\u7684\u56FE\u7247" : prompt.trim();
4952
+ const outcomeFailed = outcome !== null && outcome["ok"] === false;
4953
+ const transportFailed = !outcomeFailed && (state === "error" || state === "stopped");
4954
+ const rawFailureCode = outcomeFailed ? str2(outcome["code"]) : errorCode;
4955
+ const failureCode = rawFailureCode === "" && transportFailed ? "IMAGE_UPSTREAM_ERROR" : rawFailureCode;
4956
+ const failure = outcomeFailed ? imageErrorText(failureCode, str2(outcome["message"])) : transportFailed ? imageErrorText(failureCode, str2(errorRecord["reason"]) || str2(errorRecord["name"])) : null;
4957
+ const files = [];
4958
+ if (settled && failure === null) {
4959
+ const rawFiles = Array.isArray(outcome?.["files"]) ? outcome["files"] : void 0;
4960
+ if (rawFiles !== void 0) {
4961
+ for (const entry of rawFiles) {
4962
+ const record = asRecord(entry);
4963
+ const path = str2(record["path"]);
4964
+ const url = imageFileUrl(path, baseURI);
4965
+ if (url === "") continue;
4966
+ files.push({
4967
+ path,
4968
+ url,
4969
+ alt,
4970
+ byteSize: num2(record["byteSize"]),
4971
+ mimeType: str2(record["mimeType"]),
4972
+ revisedPrompt: str2(record["revisedPrompt"]) === "" ? void 0 : str2(record["revisedPrompt"])
4973
+ });
4974
+ }
4975
+ } else {
4976
+ for (const path of parseImagePathsFromText(block?.content)) {
4977
+ const url = imageFileUrl(path, baseURI);
4978
+ if (url !== "") {
4979
+ files.push({ path, url, alt, byteSize: void 0, mimeType: "", revisedPrompt: void 0 });
4980
+ }
4981
+ }
4982
+ }
4983
+ }
4984
+ return {
4985
+ state,
4986
+ prompt,
4987
+ size,
4988
+ model,
4989
+ generationMs,
4990
+ files,
4991
+ error: failure,
4992
+ errorCode: failureCode,
4993
+ retryable: failure !== null && isRetryableImageError(failureCode)
4994
+ };
4995
+ }
4996
+ var COPY_HINTS2 = { idle: "", copied: "\u5DF2\u590D\u5236", failed: "\u590D\u5236\u5931\u8D25" };
4997
+ async function copyPromptText(text6) {
4998
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
4999
+ if (clipboard === void 0 || typeof clipboard.writeText !== "function") return false;
5000
+ try {
5001
+ await clipboard.writeText(text6);
5002
+ return true;
5003
+ } catch {
5004
+ return false;
5005
+ }
5006
+ }
5007
+ var COPY_HINT_REVERT_MS2 = 1800;
5008
+ function currentBaseURI() {
5009
+ return typeof document === "undefined" ? "" : document.baseURI;
5010
+ }
5011
+ function ImageToolCard(props) {
5012
+ const { block, toolName = IMAGE_GENERATE_TOOL_NAME, home, openFile, inspect } = props;
5013
+ const model = toImageCardModel(block, currentBaseURI());
5014
+ const [zoom, setZoom] = (0, import_react10.useState)(null);
5015
+ const [copyHint, setCopyHint] = (0, import_react10.useState)(COPY_HINTS2.idle);
5016
+ const revertTimer = (0, import_react10.useRef)(void 0);
5017
+ (0, import_react10.useEffect)(() => {
5018
+ if (typeof document === "undefined") return;
5019
+ ensureImagesStyle(document);
5020
+ }, []);
5021
+ (0, import_react10.useEffect)(() => () => {
5022
+ if (revertTimer.current !== void 0) clearTimeout(revertTimer.current);
5023
+ }, []);
5024
+ const copy = (0, import_react10.useCallback)(() => {
5025
+ void copyPromptText(model.prompt).then((ok) => {
5026
+ setCopyHint(ok ? COPY_HINTS2.copied : COPY_HINTS2.failed);
5027
+ if (revertTimer.current !== void 0) clearTimeout(revertTimer.current);
5028
+ revertTimer.current = setTimeout(() => {
5029
+ setCopyHint(COPY_HINTS2.idle);
5030
+ }, COPY_HINT_REVERT_MS2);
5031
+ });
5032
+ }, [model.prompt]);
5033
+ const summary = () => {
5034
+ if (model.state === "running") return "\u6B63\u5728\u751F\u6210\u56FE\u7247\u2026";
5035
+ if (model.error !== null && model.files.length === 0) return "\u5931\u8D25";
5036
+ const count = model.files.length === 0 ? "" : `\u5DF2\u751F\u6210 ${model.files.length} \u5F20`;
5037
+ const elapsed = formatGenerationMs(model.generationMs);
5038
+ return [count, elapsed].filter((part) => part !== "").join(" \xB7 ");
5039
+ };
5040
+ const meta = () => {
5041
+ const parts = [model.size, formatGenerationMs(model.generationMs), model.model].filter((part) => part !== "");
5042
+ return parts.join(" \xB7 ");
5043
+ };
5044
+ return (0, import_react10.createElement)(
5045
+ "div",
5046
+ {
5047
+ className: "hiwork-image-card",
5048
+ "data-hiwork-image-card": toolName,
5049
+ "data-state": model.state
5050
+ },
5051
+ (0, import_react10.createElement)(
5052
+ "div",
5053
+ { className: "hiwork-image-card-head" },
5054
+ (0, import_react10.createElement)("span", { className: "hiwork-image-card-title" }, IMAGE_CARD_TITLE),
5055
+ (0, import_react10.createElement)("span", { className: "hiwork-image-card-summary" }, summary())
5056
+ ),
5057
+ // 提示词:running 阶段(start)就要显示——那是用户唯一的「它在按我说的画」凭据。
5058
+ model.prompt === "" ? null : (0, import_react10.createElement)("p", { className: "hiwork-image-card-prompt", "data-hiwork-image-prompt": "" }, model.prompt),
5059
+ ...model.files.map((file) => (0, import_react10.createElement)(
5060
+ "figure",
5061
+ { key: file.path, className: "hiwork-image-card-figure" },
5062
+ (0, import_react10.createElement)("img", {
5063
+ className: "hiwork-image-card-img",
5064
+ // 只画绝对路径(相对路径与远端 URL 保持惰性,与平台同规则):地址为空时不渲染本块。
5065
+ src: file.url,
5066
+ alt: file.alt,
5067
+ title: file.alt,
5068
+ "data-hiwork-image-file": file.path,
5069
+ onClick: () => {
5070
+ setZoom(file);
5071
+ }
5072
+ }),
5073
+ (0, import_react10.createElement)(
5074
+ "figcaption",
5075
+ { className: "hiwork-image-card-actions" },
5076
+ (0, import_react10.createElement)("span", { className: "hiwork-image-card-path", title: file.path, "data-hiwork-image-path": "" }, abbreviateHome(file.path, home)),
5077
+ 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 ")),
5078
+ // 可选能力:宿主没给 openFile 就不渲染按钮(渲染一个点了没反应的控件更差)。
5079
+ typeof openFile === "function" ? (0, import_react10.createElement)(
5080
+ "button",
5081
+ {
5082
+ type: "button",
5083
+ className: "hiwork-image-card-action",
5084
+ "data-hiwork-image-open": file.path,
5085
+ onClick: () => {
5086
+ openFile(file.path);
5087
+ }
5088
+ },
5089
+ "\u6253\u5F00"
5090
+ ) : null
5091
+ )
5092
+ )),
5093
+ // 卡片级动作:复制提示词(提示词是卡片级信息,每张图复制一次即可)。
5094
+ model.prompt === "" ? null : (0, import_react10.createElement)(
5095
+ "div",
5096
+ { className: "hiwork-image-card-actions" },
5097
+ (0, import_react10.createElement)(
5098
+ "button",
5099
+ { type: "button", className: "hiwork-image-card-action", "data-hiwork-image-copy": "", onClick: copy },
5100
+ "\u590D\u5236\u63D0\u793A\u8BCD"
5101
+ ),
5102
+ copyHint === "" ? null : (0, import_react10.createElement)("span", { className: "hiwork-image-card-copy-state", "data-hiwork-image-copy-state": copyHint }, copyHint),
5103
+ inspect === void 0 ? null : (0, import_react10.createElement)(
5104
+ "button",
5105
+ { type: "button", className: "hiwork-image-card-action", "data-hiwork-image-inspect": "", onClick: inspect },
5106
+ "\u67E5\u770B\u8F68\u8FF9"
5107
+ )
5108
+ ),
5109
+ model.error === null ? null : (0, import_react10.createElement)(
5110
+ "p",
5111
+ { className: "hiwork-image-card-error", role: "alert", "data-hiwork-image-error": model.errorCode },
5112
+ model.error,
5113
+ 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,
5114
+ needsRelogin(model.errorCode) ? (0, import_react10.createElement)("span", { className: "hiwork-image-card-retry-hint", "data-hiwork-image-relogin-hint": "" }, IMAGE_RELOGIN_HINT) : null
5115
+ ),
5116
+ zoom === null ? null : (0, import_react10.createElement)(
5117
+ "div",
5118
+ {
5119
+ className: "hiwork-image-zoom",
5120
+ role: "dialog",
5121
+ "aria-label": zoom.alt,
5122
+ "data-hiwork-image-zoom": "",
5123
+ onClick: () => {
5124
+ setZoom(null);
5125
+ }
5126
+ },
5127
+ (0, import_react10.createElement)("img", { src: zoom.url, alt: zoom.alt }),
5128
+ (0, import_react10.createElement)(
5129
+ "button",
5130
+ {
5131
+ type: "button",
5132
+ className: "hiwork-image-zoom-close",
5133
+ "data-hiwork-image-zoom-close": "",
5134
+ onClick: () => {
5135
+ setZoom(null);
5136
+ }
5137
+ },
5138
+ "\u5173\u95ED"
5139
+ )
5140
+ )
5141
+ );
5142
+ }
5143
+ function installImageToolCard(ctx) {
5144
+ const slots = ctx.slots;
5145
+ if (slots === void 0 || typeof slots.inject !== "function") return;
5146
+ slots.inject(TOOL_VIEW_SLOT, () => slots.register(
5147
+ { name: TOOL_VIEW_SLOT, key: IMAGE_GENERATE_TOOL_NAME },
5148
+ ImageToolCard
5149
+ ));
5150
+ }
5151
+
5152
+ // src/client/session-cost.tsx
5153
+ var import_react11 = require("react");
5154
+ var import_jsx_runtime8 = require("react/jsx-runtime");
5155
+ var SESSION_COST_ENDPOINT = "estimate";
5156
+ var SESSION_COST_CACHE_TTL_MS = 5 * 6e4;
5157
+ 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";
5158
+ var SESSION_COST_ESTIMATED_SUFFIX = "\uFF08\u4F30\u7B97\uFF09";
5159
+ function nonNegativeInteger(value) {
5160
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
5161
+ }
5162
+ function sessionCostMatchOf(data) {
5163
+ if (typeof data !== "object" || data === null) return null;
5164
+ const turn = nonNegativeInteger(data.turn);
5165
+ if (turn === void 0) return null;
5166
+ const usage = data.tokenUsage;
5167
+ if (typeof usage !== "object" || usage === null) return null;
5168
+ const input = nonNegativeInteger(usage.uncachedInputTokens);
5169
+ const output = nonNegativeInteger(usage.outputTokens);
5170
+ if (input === void 0 || output === void 0) return null;
5171
+ const routes = usage.routes;
5172
+ if (!Array.isArray(routes) || routes.length === 0) return null;
5173
+ const pairs = [];
5174
+ const seen = /* @__PURE__ */ new Set();
5175
+ for (const entry of routes) {
5176
+ if (typeof entry !== "object" || entry === null) return null;
5177
+ const { provider, model } = entry;
5178
+ if (typeof provider !== "string" || provider === "" || typeof model !== "string" || model === "") return null;
5179
+ const key = `${provider}\0${model}`;
5180
+ if (seen.has(key)) continue;
5181
+ seen.add(key);
5182
+ pairs.push({ route: provider, model });
5183
+ }
5184
+ if (pairs.length === 0) return null;
5185
+ const cacheRead = nonNegativeInteger(usage.cacheReadTokens);
5186
+ const cacheWrite = nonNegativeInteger(usage.cacheWriteTokens);
5187
+ if (input === 0 && output === 0 && (cacheRead ?? 0) === 0 && (cacheWrite ?? 0) === 0) return null;
5188
+ const first = pairs[0];
5189
+ if (first === void 0) return null;
5190
+ const at = rfc3339Of(data.time);
5191
+ const item = {
5192
+ route: first.route,
5193
+ model: first.model,
5194
+ input_tokens: input,
5195
+ output_tokens: output,
5196
+ // 桶缺失表示「并非每次尝试都上报了该桶」——原样省略,绝不猜 0。
5197
+ ...cacheRead === void 0 ? {} : { cache_read_tokens: cacheRead },
5198
+ ...cacheWrite === void 0 ? {} : { cache_write_tokens: cacheWrite },
5199
+ ...at === void 0 ? {} : { at }
5200
+ };
5201
+ const ambiguous = pairs.length > 1 || cacheRead === void 0 || cacheWrite === void 0;
5202
+ return { items: [item], turn, ambiguous };
5203
+ }
5204
+ function rfc3339Of(time) {
5205
+ if (typeof time !== "number" || !Number.isFinite(time) || time <= 0) return void 0;
5206
+ const date = new Date(time);
5207
+ return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
5208
+ }
5209
+ function closingTurnTailData(snapshot, messageId) {
5210
+ for (const key of snapshot.order) {
5211
+ const node = snapshot.nodes.get(key);
5212
+ if (node === void 0 || node.kind !== "turn-tail") continue;
5213
+ const data = node.data;
5214
+ if (data === void 0) continue;
5215
+ if (data.closing?.finalNode?.messageId === messageId) return data;
5216
+ }
5217
+ return void 0;
5218
+ }
5219
+ function sessionCostKey(sessionId, match) {
5220
+ return `${sessionId}\0${match.turn}\0${JSON.stringify(match.items)}`;
5221
+ }
5222
+ function normalizeSessionCost(value) {
5223
+ if (typeof value !== "object" || value === null) return void 0;
5224
+ const record = value;
5225
+ if (record.priced !== true) return void 0;
5226
+ const cost = record.cost;
5227
+ if (typeof cost !== "number" || !Number.isFinite(cost) || cost < 0) return void 0;
5228
+ return { cost, channelResolved: record.channelResolved === true };
5229
+ }
5230
+ var SessionCostStore = class {
5231
+ constructor(rpc, options = {}) {
5232
+ this.rpc = rpc;
5233
+ this.now = options.now ?? (() => Date.now());
5234
+ this.ttlMs = options.ttlMs ?? SESSION_COST_CACHE_TTL_MS;
5235
+ }
5236
+ entries = /* @__PURE__ */ new Map();
5237
+ pending = /* @__PURE__ */ new Map();
5238
+ now;
5239
+ ttlMs;
5240
+ /** 当前缓存条目数(测试断言用)。 */
5241
+ get size() {
5242
+ return this.entries.size;
5243
+ }
5244
+ /** 清空缓存(登出 / 切换账号)。 */
5245
+ clear() {
5246
+ this.entries.clear();
5247
+ }
5248
+ async estimate(input) {
5249
+ const key = sessionCostKey(input.sessionId, { turn: input.turn, items: [...input.items] });
5250
+ const cached = this.entries.get(key);
5251
+ if (cached !== void 0 && this.now() - cached.fetchedAt < this.ttlMs) return cached.result;
5252
+ const inflight = this.pending.get(key);
5253
+ if (inflight !== void 0) return await inflight;
5254
+ const task = this.request(key, input);
5255
+ this.pending.set(key, task);
5256
+ try {
5257
+ return await task;
5258
+ } finally {
5259
+ this.pending.delete(key);
3318
5260
  }
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) {
5261
+ }
5262
+ async request(key, input) {
3352
5263
  try {
3353
- unsubscribeLocale = options.subscribeLocale(() => {
3354
- menuSignature = "";
3355
- rebuildMenuItems();
3356
- });
3357
- } catch (error) {
3358
- console.warn("hiwork feature center: locale subscription unavailable", error);
5264
+ const raw = await this.rpc.call(USAGE_CHANNEL, SESSION_COST_ENDPOINT, { items: input.items }, input.signal);
5265
+ const result = normalizeSessionCost(unwrapUsageResult(raw));
5266
+ if (result !== void 0) this.entries.set(key, { result, fetchedAt: this.now() });
5267
+ return result;
5268
+ } catch {
5269
+ return void 0;
3359
5270
  }
3360
5271
  }
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;
5272
+ };
5273
+ var costStyle = {
5274
+ // 字号/颜色与同一行的「用量」「用时」胶囊同源变量,保证在动作行里不显得突兀。
5275
+ color: textTertiary,
5276
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
5277
+ lineHeight: "18px",
5278
+ whiteSpace: "nowrap",
5279
+ cursor: "default"
5280
+ };
5281
+ function SessionCost({ messageId, sessionId, useChat, store }) {
5282
+ const data = useChat((snapshot) => closingTurnTailData(snapshot, messageId));
5283
+ const match = sessionCostMatchOf(data);
5284
+ const key = match === null ? null : sessionCostKey(sessionId, match);
5285
+ const [shown, setShown] = (0, import_react11.useState)(void 0);
5286
+ (0, import_react11.useEffect)(() => {
5287
+ if (match === null || key === null) return;
5288
+ let disposed = false;
5289
+ const controller = new AbortController();
5290
+ void store.estimate({ sessionId, turn: match.turn, items: match.items, signal: controller.signal }).then((value) => {
5291
+ if (!disposed) setShown({ key, result: value });
5292
+ }).catch(() => {
5293
+ if (!disposed) setShown({ key, result: void 0 });
5294
+ });
5295
+ return () => {
3376
5296
  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
- }
5297
+ controller.abort();
5298
+ };
5299
+ }, [store, key]);
5300
+ if (match === null || key === null || shown === void 0 || shown.key !== key || shown.result === void 0) return null;
5301
+ const result = shown.result;
5302
+ const estimated = !result.channelResolved || match.ambiguous;
5303
+ 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 : ""}` });
5304
+ }
5305
+
5306
+ // src/client/account-cache.ts
5307
+ function accountIdentityOf(state) {
5308
+ return `${state.username ?? ""}\0${state.activeKeyId ?? ""}`;
5309
+ }
5310
+ function observeAccountChange(source, onChange) {
5311
+ let identity = accountIdentityOf(source.getSnapshot());
5312
+ const unsubscribe = source.subscribe(() => {
5313
+ const next = accountIdentityOf(source.getSnapshot());
5314
+ if (next === identity) return;
5315
+ identity = next;
5316
+ onChange();
5317
+ });
5318
+ return () => {
5319
+ unsubscribe();
3405
5320
  };
3406
5321
  }
3407
5322
 
@@ -4405,17 +6320,17 @@ var PmsTodoStore = class {
4405
6320
  };
4406
6321
 
4407
6322
  // src/client/voice/MicButton.tsx
4408
- var import_react8 = require("react");
4409
- var import_jsx_runtime8 = require("react/jsx-runtime");
6323
+ var import_react12 = require("react");
6324
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4410
6325
  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" })
6326
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
6327
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { x: "9", y: "2.75", width: "6", height: "11.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
6328
+ /* @__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" }),
6329
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 18v3.25", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
4415
6330
  ] });
4416
6331
  }
4417
6332
  function MicButton({ open }) {
4418
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
6333
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4419
6334
  "button",
4420
6335
  {
4421
6336
  type: "button",
@@ -4424,13 +6339,13 @@ function MicButton({ open }) {
4424
6339
  "aria-label": "\u8BED\u97F3\u8F93\u5165",
4425
6340
  "aria-haspopup": "dialog",
4426
6341
  onClick: () => open(),
4427
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MicIcon, { size: 18 })
6342
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MicIcon, { size: 18 })
4428
6343
  }
4429
6344
  );
4430
6345
  }
4431
6346
 
4432
6347
  // src/client/voice/entry.ts
4433
- var import_react10 = require("react");
6348
+ var import_react14 = require("react");
4434
6349
  var import_client3 = require("react-dom/client");
4435
6350
 
4436
6351
  // src/client/voice/composer.ts
@@ -4486,7 +6401,7 @@ function insertDraftText(sessions, text6, sessionId) {
4486
6401
  }
4487
6402
 
4488
6403
  // src/client/voice/VoiceOverlay.tsx
4489
- var import_react9 = require("react");
6404
+ var import_react13 = require("react");
4490
6405
 
4491
6406
  // src/client/voice/recording.ts
4492
6407
  var RecordingError = class extends Error {
@@ -4769,7 +6684,7 @@ function createVoiceRuntime(rpc) {
4769
6684
  }
4770
6685
 
4771
6686
  // src/client/voice/VoiceOverlay.tsx
4772
- var import_jsx_runtime9 = require("react/jsx-runtime");
6687
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4773
6688
  var BAR_COUNT = 28;
4774
6689
  var LEVEL_INTERVAL_MS = 80;
4775
6690
  var TICK_INTERVAL_MS = 200;
@@ -4783,25 +6698,25 @@ function rmsToLevel(rms) {
4783
6698
  return Math.min(1, Math.max(0, rms) * 6);
4784
6699
  }
4785
6700
  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" })
6701
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
6702
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { x: "9", y: "2.75", width: "6", height: "11.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
6703
+ /* @__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" }),
6704
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 18v3.25", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
4790
6705
  ] });
4791
6706
  }
4792
6707
  function VoiceOverlay(props) {
4793
6708
  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);
6709
+ const [phase, setPhase] = (0, import_react13.useState)("checking");
6710
+ const [error, setError] = (0, import_react13.useState)(void 0);
6711
+ const [levels, setLevels] = (0, import_react13.useState)(() => Array.from({ length: BAR_COUNT }, () => 0.06));
6712
+ const [elapsed, setElapsed] = (0, import_react13.useState)(0);
6713
+ const [maxDuration, setMaxDuration] = (0, import_react13.useState)(60);
6714
+ const [copied, setCopied] = (0, import_react13.useState)(false);
6715
+ const recordingRef = (0, import_react13.useRef)(void 0);
6716
+ const wavRef = (0, import_react13.useRef)(void 0);
6717
+ const startedAtRef = (0, import_react13.useRef)(0);
6718
+ const abortRef = (0, import_react13.useRef)(void 0);
6719
+ const disposedRef = (0, import_react13.useRef)(false);
4805
6720
  const failWith = (message, opts = {}) => {
4806
6721
  if (disposedRef.current) return;
4807
6722
  setPhase("error");
@@ -4898,7 +6813,7 @@ function VoiceOverlay(props) {
4898
6813
  failWith(err instanceof Error ? err.message : "\u5F55\u97F3\u5904\u7406\u5931\u8D25\u3002", { holdWav: false });
4899
6814
  });
4900
6815
  };
4901
- (0, import_react9.useEffect)(() => {
6816
+ (0, import_react13.useEffect)(() => {
4902
6817
  disposedRef.current = false;
4903
6818
  let cancelled = false;
4904
6819
  runtime.status().then((status) => {
@@ -4921,7 +6836,7 @@ function VoiceOverlay(props) {
4921
6836
  recordingRef.current = void 0;
4922
6837
  };
4923
6838
  }, []);
4924
- (0, import_react9.useEffect)(() => {
6839
+ (0, import_react13.useEffect)(() => {
4925
6840
  if (phase !== "recording") return;
4926
6841
  const levelTimer = window.setInterval(() => {
4927
6842
  const rms = recordingRef.current?.amplitude() ?? 0;
@@ -4935,7 +6850,7 @@ function VoiceOverlay(props) {
4935
6850
  window.clearInterval(clockTimer);
4936
6851
  };
4937
6852
  }, [phase]);
4938
- (0, import_react9.useEffect)(() => {
6853
+ (0, import_react13.useEffect)(() => {
4939
6854
  if (phase !== "recording") return;
4940
6855
  if (elapsed < maxDuration) return;
4941
6856
  finish();
@@ -4944,7 +6859,7 @@ function VoiceOverlay(props) {
4944
6859
  abortRef.current?.abort();
4945
6860
  onClose();
4946
6861
  };
4947
- (0, import_react9.useEffect)(() => {
6862
+ (0, import_react13.useEffect)(() => {
4948
6863
  const onKey = (event) => {
4949
6864
  if (event.key !== "Escape") return;
4950
6865
  event.preventDefault();
@@ -4962,21 +6877,21 @@ function VoiceOverlay(props) {
4962
6877
  const idle = phase === "checking" || phase === "requesting" || phase === "transcribing";
4963
6878
  const canRetryTranscribe = error?.holdWav === true && error.recognizedText === void 0;
4964
6879
  const canRestart = error?.canRestart === true;
4965
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6880
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4966
6881
  "div",
4967
6882
  {
4968
6883
  className: "hiwv-backdrop",
4969
6884
  onMouseDown: (event) => {
4970
6885
  if (event.target === event.currentTarget && !idle) handleClose();
4971
6886
  },
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)}` })
6887
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-panel", role: "dialog", "aria-modal": "true", "aria-label": "\u8BED\u97F3\u8F93\u5165", children: [
6888
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-head", children: [
6889
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "hiwv-title", children: "\u8BED\u97F3\u8F93\u5165" }),
6890
+ /* @__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
6891
  ] }),
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)(
6892
+ /* @__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: [
6893
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: `hiwv-orb${phase === "transcribing" ? " hiwv-orbBusy" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MicIcon2, { size: 30 }) }),
6894
+ /* @__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
6895
  "i",
4981
6896
  {
4982
6897
  style: { height: `${Math.round(8 + level * 46)}px` },
@@ -4984,19 +6899,19 @@ function VoiceOverlay(props) {
4984
6899
  },
4985
6900
  index
4986
6901
  )) }),
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" })
6902
+ /* @__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" })
6903
+ ] }) : 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: [
6904
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "hiwv-errorMsg", children: error?.message ?? "\u51FA\u4E86\u70B9\u95EE\u9898\u3002" }),
6905
+ error?.recognizedText !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-recognized", children: [
6906
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "hiwv-recognizedText", children: error.recognizedText }),
6907
+ /* @__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
6908
  ] })
4994
6909
  ] }) }),
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)(
6910
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "hiwv-actions", children: [
6911
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnSecondary", onClick: handleClose, children: "\u53D6\u6D88" }),
6912
+ recording && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnPrimary", onClick: finish, children: "\u5B8C\u6210" }),
6913
+ phase === "error" && canRetryTranscribe && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "hiwv-btn hiwv-btnPrimary", onClick: transcribeWav, children: "\u91CD\u8BD5\u8BC6\u522B" }),
6914
+ phase === "error" && canRestart && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5000
6915
  "button",
5001
6916
  {
5002
6917
  type: "button",
@@ -5060,7 +6975,7 @@ function createOverlayController(deps) {
5060
6975
  doc.body.appendChild(container);
5061
6976
  root = (0, import_client3.createRoot)(container);
5062
6977
  root.render(
5063
- (0, import_react10.createElement)(VoiceOverlay, {
6978
+ (0, import_react14.createElement)(VoiceOverlay, {
5064
6979
  runtime: deps.runtime,
5065
6980
  onClose: close,
5066
6981
  // 会话 id 缺省(拿不到槽位注入)时由 insertDraftText 退到当前打开会话;
@@ -5133,7 +7048,7 @@ async function installVoiceInput(deps) {
5133
7048
  }
5134
7049
 
5135
7050
  // src/client/index.ts
5136
- var inject = ["slots", "locale", "remote", "remote.credentials", "settingsScope", "connection"];
7051
+ var inject = ["slots", "locale", "remote", "remote.credentials", "configForms", "connection", "layout"];
5137
7052
  var INTERACTION_STYLE_ID = "hiwork-core-interaction-styles";
5138
7053
  function injectInteractionStyles() {
5139
7054
  if (typeof document === "undefined") return;
@@ -5146,12 +7061,12 @@ function injectInteractionStyles() {
5146
7061
  }
5147
7062
  function apply(ctx) {
5148
7063
  injectInteractionStyles();
5149
- const scope = ctx.settingsScope.bind({ namespace: AI_ROUTER_NS });
7064
+ const form = ctx.configForms.get(AI_ROUTER_NS);
5150
7065
  const storeCredential = async (ref, value) => {
5151
7066
  const response = await ctx.remote.credentials.set(ref, value);
5152
7067
  if (!response.ok) throw new Error(response.error.message);
5153
7068
  };
5154
- const controller = new AiRouterCardController(scope, storeCredential);
7069
+ const controller = new AiRouterCardController(form, storeCredential);
5155
7070
  ctx.slots.inject("settings.section", () => ctx.slots.register({
5156
7071
  name: "settings.section",
5157
7072
  id: AI_ROUTER_NS,
@@ -5159,12 +7074,6 @@ function apply(ctx) {
5159
7074
  label: () => AI_ROUTER_SECTION_LABEL,
5160
7075
  inject: () => controller.inject()
5161
7076
  }, 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
7077
  const connection = ctx.get("connection");
5169
7078
  if (connection === void 0) throw new Error("connection service unavailable");
5170
7079
  const usage = new UsageDataStore(connection.rpc);
@@ -5175,6 +7084,13 @@ function apply(ctx) {
5175
7084
  const managedWorkbench = new ManagedWorkbenchStore(connection.rpc);
5176
7085
  const pmsTodo = new PmsTodoStore(connection.rpc);
5177
7086
  const appVersion = createAppVersion(connection.rpc);
7087
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
7088
+ name: "settings.section",
7089
+ id: "hiwork-about",
7090
+ order: Number.MAX_SAFE_INTEGER,
7091
+ label: () => "\u5173\u4E8E",
7092
+ inject: () => ({ appVersion })
7093
+ }, AboutSection));
5178
7094
  const pluginUpdates = createPluginUpdates(connection.rpc);
5179
7095
  ctx.slots.inject("settings.section", () => ctx.slots.register({
5180
7096
  name: "settings.section",
@@ -5183,6 +7099,57 @@ function apply(ctx) {
5183
7099
  label: () => "\u7528\u91CF\u4E0E\u6210\u672C",
5184
7100
  inject: () => ({ hooks: controller.inject().hooks, usage })
5185
7101
  }, UsageSection));
7102
+ const featureRegistry = createFeatureRegistry();
7103
+ const images = new ImageGallerySource(connection.rpc);
7104
+ installImagesFeature(ctx, featureRegistry, images);
7105
+ installImageToolCard(ctx);
7106
+ const PLUGINS_PANEL_ID = "plugins";
7107
+ const asPanelId = (id) => id;
7108
+ const layoutFace = () => {
7109
+ try {
7110
+ return ctx.layout ?? void 0;
7111
+ } catch {
7112
+ return void 0;
7113
+ }
7114
+ };
7115
+ const nativePanel = {
7116
+ pluginsAvailable: () => {
7117
+ const layout = layoutFace();
7118
+ if (layout === void 0) return false;
7119
+ try {
7120
+ return ctx.slots.entries("main").some((entry) => entry.options.key === PLUGINS_PANEL_ID);
7121
+ } catch {
7122
+ return false;
7123
+ }
7124
+ },
7125
+ anyActive: () => (layoutFace()?.panelInfo.getSnapshot().activePanelId ?? null) !== null,
7126
+ pluginsActive: () => layoutFace()?.panelInfo.getSnapshot().activePanelId === PLUGINS_PANEL_ID,
7127
+ openPlugins: () => {
7128
+ layoutFace()?.selectPanel(asPanelId(PLUGINS_PANEL_ID));
7129
+ },
7130
+ returnToConversation: () => {
7131
+ layoutFace()?.selectPanel(null);
7132
+ },
7133
+ subscribe: (listener) => {
7134
+ const layout = layoutFace();
7135
+ if (layout === void 0) return () => void 0;
7136
+ const offSlots = ctx.slots.subscribe("main", listener);
7137
+ const offPanel = layout.panelInfo.subscribe(listener);
7138
+ return () => {
7139
+ offSlots();
7140
+ offPanel();
7141
+ };
7142
+ }
7143
+ };
7144
+ const additionalFeatures = () => nativePanel.pluginsAvailable() ? [nativePluginsMenuItem()] : [];
7145
+ const menuStore = new MenuStore(form, featureRegistry, additionalFeatures);
7146
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
7147
+ name: "settings.section",
7148
+ id: HIWORK_MENU_NS,
7149
+ order: 3,
7150
+ label: () => MENU_SECTION_LABEL,
7151
+ inject: () => menuStore.inject()
7152
+ }, MenuSection));
5186
7153
  ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
5187
7154
  name: "conversation.chat.assistant-actions",
5188
7155
  id: "hiwork-session-cost",
@@ -5248,12 +7215,11 @@ function apply(ctx) {
5248
7215
  id: AI_ROUTER_NS,
5249
7216
  inject: () => ({ ...controller.inject(), pluginUpdates })
5250
7217
  }, UserBadge));
5251
- ctx.effect(() => mountLoginGate(scope, storeCredential), "hiwork-core: login gate");
7218
+ ctx.effect(() => mountLoginGate(form, storeCredential), "hiwork-core: login gate");
5252
7219
  ctx.effect(() => observeHeroDecorations(document, dailyQuotes), "hiwork-core: hero decorations");
5253
- installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench, pmsTodo);
7220
+ installFeatureCenter(ctx, featureRegistry, menuStore, managedMcp, managedSkills, managedWorkbench, pmsTodo, additionalFeatures, nativePanel);
5254
7221
  }
5255
- function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench, pmsTodo) {
5256
- const featureRegistry = createFeatureRegistry();
7222
+ function installFeatureCenter(ctx, featureRegistry, menuStore, managedMcp, managedSkills, managedWorkbench, pmsTodo, additionalFeatures, nativePanel) {
5257
7223
  let center;
5258
7224
  const resolveSessions = () => ctx.get("sessions");
5259
7225
  const locale = ctx.get("locale");
@@ -5332,12 +7298,20 @@ function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench,
5332
7298
  openNativeSession: (sessionId) => {
5333
7299
  resolveSessions()?.open(sessionId);
5334
7300
  },
5335
- ...locale === void 0 ? {} : { subscribeLocale: (listener) => locale.subscribe(listener) }
7301
+ additionalFeatures,
7302
+ nativePanel,
7303
+ ...locale === void 0 ? {} : { subscribeLocale: (listener) => locale.subscribe(listener) },
7304
+ // 侧栏分组与顺序(`hiwork-menu`,见 menu-preferences.ts / menu-store.ts):
7305
+ // `preferences` 每次重建菜单时现取,`subscribePreferences` 让设置页里的一次改动
7306
+ // 立刻反映到侧栏,不需要重启。
7307
+ preferences: () => menuStore.preferences(),
7308
+ subscribePreferences: (listener) => menuStore.subscribePreferences(listener)
5336
7309
  });
5337
7310
  return () => {
5338
7311
  center?.dispose();
5339
7312
  center = void 0;
5340
7313
  };
5341
7314
  }, "hiwork-core: feature center");
7315
+ ctx.effect(() => () => menuStore.dispose(), "hiwork-core: menu preferences");
5342
7316
  }
5343
7317
  return module.exports; } });