@openspecui/web 3.11.0 → 3.11.1

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 (69) hide show
  1. package/dist/assets/CanvasRenderer-Bs7UxSWT.js +1 -0
  2. package/dist/assets/WebGLRenderer-l71uR9MI.js +1 -0
  3. package/dist/assets/WebGPURenderer-tjCbVxRm.js +1 -0
  4. package/dist/assets/browserAll-BDY91DxA.js +1 -0
  5. package/dist/assets/{dist-CcLC6JdJ.js → dist--JzlUmDP.js} +1 -1
  6. package/dist/assets/{dist-D0kk4OnI.js → dist-B09y0Ph7.js} +1 -1
  7. package/dist/assets/{dist-Co2y3Dp-.js → dist-BttXL4wz.js} +1 -1
  8. package/dist/assets/{dist-CRlC9_e7.js → dist-C9BN4134.js} +1 -1
  9. package/dist/assets/dist-CCD9CqTu.js +1 -0
  10. package/dist/assets/{dist-DSHRZu7v.js → dist-DdsOxRxJ.js} +1 -1
  11. package/dist/assets/dist-DjM_Envn.js +1 -0
  12. package/dist/assets/{dist-Deq3d3tg.js → dist-Dx00xLFT.js} +1 -1
  13. package/dist/assets/{dist-B_uzjHQN.js → dist-DxmYOUGn.js} +1 -1
  14. package/dist/assets/{dist-D7t0ICkd.js → dist-F0lgtwe2.js} +1 -1
  15. package/dist/assets/dist-UpX5-9sH.js +1 -0
  16. package/dist/assets/{dist-CjSC6Iz-.js → dist-rGlPGEMw.js} +1 -1
  17. package/dist/assets/{init-CXvIgPy6.js → init-C46YDZ2H.js} +1 -1
  18. package/dist/assets/main-B2c4pZCT.css +1 -0
  19. package/dist/assets/{main-DjDAttzm.js → main-Wj4p5q80.js} +175 -167
  20. package/dist/assets/trpc-1XzKnuJv.js +1 -0
  21. package/dist/assets/webworkerAll-CitYd6ji.js +1 -0
  22. package/dist/index.html +2 -2
  23. package/dist-ssg/client/.vite/ssr-manifest.json +33 -15
  24. package/dist-ssg/client/assets/CanvasRenderer-FPbXzOhu.js +1 -0
  25. package/dist-ssg/client/assets/WebGLRenderer-DTbkQ-Tc.js +1 -0
  26. package/dist-ssg/client/assets/WebGPURenderer-jYEtkBkX.js +1 -0
  27. package/dist-ssg/client/assets/browserAll-D2rErTpO.js +1 -0
  28. package/dist-ssg/client/assets/dist-03Q-XuhL.js +1 -0
  29. package/dist-ssg/client/assets/{dist-xlAWlzHU.js → dist-9ZjKnAh1.js} +1 -1
  30. package/dist-ssg/client/assets/{dist-zooxroSS.js → dist-BPn14NhJ.js} +1 -1
  31. package/dist-ssg/client/assets/{dist-C8h2jqYd.js → dist-BRxWvz6j.js} +1 -1
  32. package/dist-ssg/client/assets/dist-CNXuEBuZ.js +1 -0
  33. package/dist-ssg/client/assets/{dist-Ct0osEG9.js → dist-DOwxbW7B.js} +1 -1
  34. package/dist-ssg/client/assets/{dist-DYA7aYgJ.js → dist-D_7Z5apg.js} +1 -1
  35. package/dist-ssg/client/assets/{dist-B0rNAJNr.js → dist-Dg-rGFsy.js} +1 -1
  36. package/dist-ssg/client/assets/dist-Dj9a_D4b.js +1 -0
  37. package/dist-ssg/client/assets/{dist-nDVKH9rz.js → dist-DjZdWHNx.js} +1 -1
  38. package/dist-ssg/client/assets/{dist-CIDagwl8.js → dist-hWAvow10.js} +1 -1
  39. package/dist-ssg/client/assets/{dist-B_e3vKVI.js → dist-yriBIRgD.js} +1 -1
  40. package/dist-ssg/client/assets/{ghostty-web-MvkhtIaS.js → ghostty-web-BVM0FkWD.js} +1 -1
  41. package/dist-ssg/client/assets/index-5OettMZv.css +1 -0
  42. package/dist-ssg/client/assets/index.ssg-C4D0iHIp.js +1624 -0
  43. package/dist-ssg/client/assets/{init-CbwghP3k.js → init-DBoYmUC7.js} +1 -1
  44. package/dist-ssg/client/assets/trpc-J2T_izXe.js +1 -0
  45. package/dist-ssg/client/assets/webworkerAll-CLzsuMst.js +1 -0
  46. package/dist-ssg/client/index.ssg.html +2 -2
  47. package/dist-ssg/server/entry-server.js +1093 -457
  48. package/package.json +3 -3
  49. package/dist/assets/CanvasRenderer-Cntd8ry7.js +0 -1
  50. package/dist/assets/WebGLRenderer-DZEgkU7P.js +0 -1
  51. package/dist/assets/WebGPURenderer-DtnLusyN.js +0 -1
  52. package/dist/assets/browserAll-C0q2DkVO.js +0 -1
  53. package/dist/assets/dist-CfJPknqn.js +0 -1
  54. package/dist/assets/dist-DqB_uiSZ.js +0 -1
  55. package/dist/assets/dist-jEMUT-cO.js +0 -1
  56. package/dist/assets/main-Dyvh_8NL.css +0 -1
  57. package/dist/assets/trpc-CSBEzIgb.js +0 -1
  58. package/dist/assets/webworkerAll-DhUUBZ9J.js +0 -1
  59. package/dist-ssg/client/assets/CanvasRenderer-D3Q5_w9A.js +0 -1
  60. package/dist-ssg/client/assets/WebGLRenderer-D9oc2qKY.js +0 -1
  61. package/dist-ssg/client/assets/WebGPURenderer-C5zSqQBE.js +0 -1
  62. package/dist-ssg/client/assets/browserAll-CIBhnrSW.js +0 -1
  63. package/dist-ssg/client/assets/dist-BSCnmcZh.js +0 -1
  64. package/dist-ssg/client/assets/dist-C1zSmOmx.js +0 -1
  65. package/dist-ssg/client/assets/dist-CXNNfjwS.js +0 -1
  66. package/dist-ssg/client/assets/index-D2aBuJqY.css +0 -1
  67. package/dist-ssg/client/assets/index.ssg-Dp15bsQU.js +0 -1616
  68. package/dist-ssg/client/assets/trpc-BccI0xyZ.js +0 -1
  69. package/dist-ssg/client/assets/webworkerAll-CYURuxHF.js +0 -1
@@ -32268,7 +32268,7 @@ ZodSymbol.create;
32268
32268
  ZodUndefined.create;
32269
32269
  ZodNull.create;
32270
32270
  ZodAny.create;
32271
- ZodUnknown.create;
32271
+ var unknownType = ZodUnknown.create;
32272
32272
  ZodNever.create;
32273
32273
  ZodVoid.create;
32274
32274
  var arrayType = ZodArray.create;
@@ -32311,7 +32311,11 @@ var LocalModelDownloadStatusSchema = enumType([
32311
32311
  var TranslationDownloadFilePlanSchema = objectType({
32312
32312
  path: stringType().min(1),
32313
32313
  sizeBytes: numberType().int().nonnegative().optional(),
32314
- required: booleanType()
32314
+ required: booleanType(),
32315
+ etag: stringType().min(1).optional(),
32316
+ revision: stringType().min(1).optional(),
32317
+ sourceUrl: stringType().min(1).optional(),
32318
+ raw: unknownType().optional()
32315
32319
  });
32316
32320
  var TranslationDownloadGroupPlanSchema = objectType({
32317
32321
  id: stringType().min(1),
@@ -32320,14 +32324,97 @@ var TranslationDownloadGroupPlanSchema = objectType({
32320
32324
  profile: stringType().min(1).optional(),
32321
32325
  dtype: stringType().min(1).optional(),
32322
32326
  estimatedTotalBytes: numberType().int().nonnegative().optional(),
32327
+ baseGroupId: stringType().min(1).optional(),
32328
+ commitHash: stringType().min(1).optional(),
32329
+ shortCommitHash: stringType().min(1).optional(),
32330
+ rootDir: stringType().min(1).optional(),
32331
+ status: LocalModelDownloadStatusSchema.optional(),
32332
+ progress: numberType().min(0).max(1).optional(),
32333
+ bytesDownloaded: numberType().int().nonnegative().optional(),
32334
+ totalBytes: numberType().int().nonnegative().optional(),
32335
+ resumable: booleanType().optional(),
32336
+ error: stringType().optional(),
32323
32337
  selectable: booleanType(),
32324
32338
  selected: booleanType(),
32325
32339
  files: arrayType(TranslationDownloadFilePlanSchema)
32326
32340
  });
32341
+ var LocalModelProfileStatusSchema = enumType([
32342
+ "idle",
32343
+ "loading",
32344
+ "ready",
32345
+ "error"
32346
+ ]);
32347
+ var LocalModelProfileManifestFileSchema = objectType({
32348
+ path: stringType().min(1),
32349
+ sizeBytes: numberType().int().nonnegative().optional(),
32350
+ required: booleanType(),
32351
+ etag: stringType().min(1).optional(),
32352
+ revision: stringType().min(1).optional(),
32353
+ sourceUrl: stringType().min(1).optional(),
32354
+ raw: unknownType().optional()
32355
+ });
32356
+ var LocalModelProfileManifestGroupSchema = objectType({
32357
+ id: stringType().min(1),
32358
+ baseGroupId: stringType().min(1),
32359
+ label: stringType().min(1),
32360
+ displayLabel: stringType().min(1),
32361
+ description: stringType().optional(),
32362
+ profile: stringType().min(1).optional(),
32363
+ dtype: stringType().min(1).optional(),
32364
+ commitHash: stringType().min(1),
32365
+ shortCommitHash: stringType().min(1),
32366
+ rootDir: stringType().min(1),
32367
+ estimatedTotalBytes: numberType().int().nonnegative().optional(),
32368
+ selectable: booleanType(),
32369
+ files: arrayType(LocalModelProfileManifestFileSchema)
32370
+ });
32371
+ var LocalModelProfileManifestSchema = objectType({
32372
+ modelId: stringType().min(1),
32373
+ source: literalType("huggingface"),
32374
+ endpoint: stringType().default(""),
32375
+ revision: stringType().min(1),
32376
+ commitHash: stringType().min(1),
32377
+ shortCommitHash: stringType().min(1),
32378
+ fetchedAt: numberType().int().nonnegative(),
32379
+ updatedAt: numberType().int().nonnegative(),
32380
+ raw: unknownType().optional(),
32381
+ groups: recordType(stringType(), LocalModelProfileManifestGroupSchema).default({}),
32382
+ groupOrder: arrayType(stringType().min(1)).default([])
32383
+ });
32384
+ var LocalModelLifecycleFileStateSchema = objectType({
32385
+ path: stringType().min(1),
32386
+ sizeBytes: numberType().int().nonnegative().optional(),
32387
+ downloadedBytes: numberType().int().nonnegative().optional(),
32388
+ required: booleanType().default(true),
32389
+ status: LocalModelDownloadStatusSchema.default("not-downloaded"),
32390
+ updatedAt: numberType().int().nonnegative().optional(),
32391
+ error: stringType().optional()
32392
+ });
32393
+ var LocalModelLifecycleGroupStateSchema = objectType({
32394
+ groupId: stringType().min(1),
32395
+ baseGroupId: stringType().min(1).optional(),
32396
+ status: LocalModelDownloadStatusSchema.default("not-downloaded"),
32397
+ rootDir: stringType().min(1).optional(),
32398
+ bytesDownloaded: numberType().int().nonnegative().optional(),
32399
+ totalBytes: numberType().int().nonnegative().optional(),
32400
+ progress: numberType().min(0).max(1).optional(),
32401
+ resumable: booleanType().default(false),
32402
+ error: stringType().optional(),
32403
+ installedAt: numberType().int().nonnegative().optional(),
32404
+ updatedAt: numberType().int().nonnegative().optional(),
32405
+ files: arrayType(LocalModelLifecycleFileStateSchema).default([])
32406
+ });
32407
+ var LocalModelProfileLoadStateSchema = objectType({
32408
+ status: LocalModelProfileStatusSchema.default("idle"),
32409
+ message: stringType().optional(),
32410
+ error: stringType().optional(),
32411
+ updatedAt: numberType().int().nonnegative().optional()
32412
+ });
32327
32413
  objectType({
32328
32414
  engineId: literalType("local"),
32329
32415
  modelId: stringType().min(1),
32330
32416
  selectedGroupId: stringType().min(1).optional(),
32417
+ groupId: stringType().min(1).optional(),
32331
32418
  status: LocalModelDownloadStatusSchema,
32332
32419
  message: stringType(),
32333
32420
  progress: numberType().min(0).max(1).optional(),
@@ -32352,8 +32439,10 @@ var LocalModelAssetPlanSnapshotSchema = objectType({
32352
32439
  });
32353
32440
  objectType({
32354
32441
  modelId: stringType().min(1),
32442
+ version: literalType(2).default(2),
32355
32443
  status: LocalModelDownloadStatusSchema.default("not-downloaded"),
32356
32444
  selected: booleanType().default(false),
32445
+ selectedGroupId: stringType().min(1).optional(),
32357
32446
  installedAt: numberType().int().nonnegative().optional(),
32358
32447
  updatedAt: numberType().int().nonnegative().optional(),
32359
32448
  bytesDownloaded: numberType().int().nonnegative().optional(),
@@ -32361,6 +32450,9 @@ objectType({
32361
32450
  progress: numberType().min(0).max(1).optional(),
32362
32451
  resumable: booleanType().default(false),
32363
32452
  error: stringType().optional(),
32453
+ profileLoad: LocalModelProfileLoadStateSchema.default(LocalModelProfileLoadStateSchema.parse({})),
32454
+ profileManifest: LocalModelProfileManifestSchema.optional(),
32455
+ groupsState: recordType(stringType(), LocalModelLifecycleGroupStateSchema).default({}),
32364
32456
  plan: LocalModelAssetPlanSnapshotSchema.optional(),
32365
32457
  files: arrayType(objectType({
32366
32458
  path: stringType().min(1),
@@ -42699,6 +42791,12 @@ function useConfigSubscription() {
42699
42791
  onError: callbacks.onError
42700
42792
  }), getConfig$1, [], "config.subscribe");
42701
42793
  }
42794
+ function useGlobalSettingsSubscription() {
42795
+ return useSubscription((callbacks) => trpcClient.globalSettings.subscribe.subscribe(void 0, {
42796
+ onData: callbacks.onData,
42797
+ onError: callbacks.onError
42798
+ }), void 0, [], "globalSettings.subscribe");
42799
+ }
42702
42800
  //#endregion
42703
42801
  //#region src/lib/use-opsx.ts
42704
42802
  function getOpsxStatusSubscriptionCacheKey(input) {
@@ -44002,6 +44100,15 @@ var Expand = createLucideIcon("Expand", [
44002
44100
  key: "u9ee12"
44003
44101
  }]
44004
44102
  ]);
44103
+ var Eye = createLucideIcon("Eye", [["path", {
44104
+ d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",
44105
+ key: "1nclc0"
44106
+ }], ["circle", {
44107
+ cx: "12",
44108
+ cy: "12",
44109
+ r: "3",
44110
+ key: "1v7zrd"
44111
+ }]]);
44005
44112
  var FileCode2 = createLucideIcon("FileCode2", [
44006
44113
  ["path", {
44007
44114
  d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4",
@@ -44020,6 +44127,20 @@ var FileCode2 = createLucideIcon("FileCode2", [
44020
44127
  key: "112psh"
44021
44128
  }]
44022
44129
  ]);
44130
+ var FilePenLine = createLucideIcon("FilePenLine", [
44131
+ ["path", {
44132
+ d: "m18 5-2.414-2.414A2 2 0 0 0 14.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2",
44133
+ key: "142zxg"
44134
+ }],
44135
+ ["path", {
44136
+ d: "M21.378 12.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z",
44137
+ key: "2t3380"
44138
+ }],
44139
+ ["path", {
44140
+ d: "M8 18h1",
44141
+ key: "13wk12"
44142
+ }]
44143
+ ]);
44023
44144
  var FilePlus = createLucideIcon("FilePlus", [
44024
44145
  ["path", {
44025
44146
  d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",
@@ -44519,6 +44640,24 @@ var Save = createLucideIcon("Save", [
44519
44640
  key: "t51u73"
44520
44641
  }]
44521
44642
  ]);
44643
+ var ScrollText = createLucideIcon("ScrollText", [
44644
+ ["path", {
44645
+ d: "M15 12h-5",
44646
+ key: "r7krc0"
44647
+ }],
44648
+ ["path", {
44649
+ d: "M15 8h-5",
44650
+ key: "1khuty"
44651
+ }],
44652
+ ["path", {
44653
+ d: "M19 17V5a2 2 0 0 0-2-2H4",
44654
+ key: "zz82l3"
44655
+ }],
44656
+ ["path", {
44657
+ d: "M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3",
44658
+ key: "1ph1d7"
44659
+ }]
44660
+ ]);
44522
44661
  var Search = createLucideIcon("Search", [["circle", {
44523
44662
  cx: "11",
44524
44663
  cy: "11",
@@ -55153,8 +55292,12 @@ function inertValue(value) {
55153
55292
  //#region src/components/tooltip.tsx
55154
55293
  function Tooltip({ content, children, delay = 180, sideOffset = 8, className }) {
55155
55294
  if (!content) return children;
55295
+ const trigger = typeof children.props === "object" && children.props !== null && "disabled" in children.props && Boolean(children.props.disabled) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
55296
+ className: "inline-flex max-w-full",
55297
+ children
55298
+ }) : children;
55156
55299
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TooltipRoot, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
55157
- render: children,
55300
+ render: trigger,
55158
55301
  delay
55159
55302
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipPositioner, {
55160
55303
  sideOffset,
@@ -96609,20 +96752,29 @@ var Button = (0, import_react.forwardRef)(function Button({ variant = "primary",
96609
96752
  /**
96610
96753
  * Compact segmented buttons with single-select behavior.
96611
96754
  */
96612
- function ButtonGroup({ value, options, onChange, className = "", tone = "default" }) {
96755
+ function ButtonGroup({ value, options, onChange, className = "", tone = "default", presentation = "label" }) {
96613
96756
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
96614
96757
  className: `inline-flex w-fit max-w-full shrink-0 self-start overflow-hidden rounded-md border ${tone === "terminal" ? "border-terminal-foreground/25 bg-terminal/70 text-terminal-foreground" : "border-border bg-card"} ${className}`,
96615
96758
  children: options.map((option, index) => {
96616
96759
  const active = option.value === value;
96617
96760
  const stateClassName = active ? "bg-primary text-primary-foreground" : tone === "terminal" ? "text-terminal-foreground/72 hover:bg-terminal-foreground/10 hover:text-terminal-foreground" : "text-muted-foreground hover:bg-muted/60 hover:text-foreground";
96618
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
96761
+ const accessibleLabel = option.ariaLabel ?? (typeof option.label === "string" ? option.label : void 0);
96762
+ const tooltipContent = option.tooltip ?? accessibleLabel;
96763
+ const content = presentation === "icon-only" ? option.icon ?? option.label : presentation === "icon-label" && option.icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [option.icon, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: option.label })] }) : option.label;
96764
+ const button = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
96619
96765
  type: "button",
96620
96766
  disabled: option.disabled,
96621
96767
  onClick: () => onChange(option.value),
96622
96768
  "aria-pressed": active,
96623
- className: `inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${index > 0 ? tone === "terminal" ? "border-terminal-foreground/20 border-l" : "border-border border-l" : ""} ${stateClassName}`,
96624
- children: option.label
96769
+ "aria-label": presentation === "icon-only" ? accessibleLabel : void 0,
96770
+ title: presentation === "icon-only" && typeof accessibleLabel === "string" ? accessibleLabel : void 0,
96771
+ className: `inline-flex items-center justify-center gap-1.5 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${presentation === "icon-only" ? "h-8 w-8 p-0" : "px-3 py-1.5"} ${index > 0 ? tone === "terminal" ? "border-terminal-foreground/20 border-l" : "border-border border-l" : ""} ${stateClassName}`,
96772
+ children: content
96625
96773
  }, option.value);
96774
+ return tooltipContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
96775
+ content: tooltipContent,
96776
+ children: button
96777
+ }, option.value) : button;
96626
96778
  })
96627
96779
  });
96628
96780
  }
@@ -114041,6 +114193,7 @@ var layoutStyles = String.raw`
114041
114193
  display: flex;
114042
114194
  flex-direction: column;
114043
114195
  height: 100%;
114196
+ min-height: 0;
114044
114197
  gap: 0.75rem;
114045
114198
  }
114046
114199
  .fev-sidebar-tabs {
@@ -114048,6 +114201,7 @@ var layoutStyles = String.raw`
114048
114201
  }
114049
114202
  .fev-sidebar-tree {
114050
114203
  display: none;
114204
+ min-height: 0;
114051
114205
  }
114052
114206
  .fev-editor-wrapper {
114053
114207
  display: flex;
@@ -114061,8 +114215,10 @@ var layoutStyles = String.raw`
114061
114215
  .fev-layout {
114062
114216
  display: grid;
114063
114217
  grid-template-columns: minmax(0, 1fr) minmax(240px, clamp(240px, 30%, 420px));
114218
+ grid-template-rows: minmax(0, 1fr);
114064
114219
  gap: 1rem;
114065
114220
  min-height: 0;
114221
+ overflow: hidden;
114066
114222
  }
114067
114223
  .fev-sidebar-tabs {
114068
114224
  display: none;
@@ -114070,9 +114226,13 @@ var layoutStyles = String.raw`
114070
114226
  .fev-sidebar-tree {
114071
114227
  display: block;
114072
114228
  order: 2;
114229
+ height: 100%;
114230
+ min-height: 0;
114231
+ overflow: hidden;
114073
114232
  }
114074
114233
  .fev-editor-wrapper {
114075
114234
  order: 1;
114235
+ min-height: 0;
114076
114236
  }
114077
114237
  }
114078
114238
  .CodeMirror {
@@ -114250,7 +114410,8 @@ function FileTree({ entries, selectedPath, onSelect, headerLabel, headerActions,
114250
114410
  const closeMenu = () => setMenuState(null);
114251
114411
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuWrapper, {
114252
114412
  ref: wrapperRef,
114253
- className: "border-border bg-muted/30 flex h-full flex-col rounded-md border",
114413
+ className: "border-border bg-muted/30 flex h-full min-h-0 flex-col rounded-md border",
114414
+ "data-file-explorer-tree": "",
114254
114415
  children: [
114255
114416
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
114256
114417
  className: "border-border/50 text-muted-foreground flex items-center justify-between border-b px-3 py-2 text-xs font-medium",
@@ -114260,7 +114421,8 @@ function FileTree({ entries, selectedPath, onSelect, headerLabel, headerActions,
114260
114421
  }), headerActions]
114261
114422
  }),
114262
114423
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
114263
- className: "scrollbar-thin scrollbar-track-transparent flex-1 overflow-y-auto",
114424
+ "data-file-explorer-tree-scroll": "",
114425
+ className: "scrollbar-thin scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto",
114264
114426
  children: entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
114265
114427
  className: "text-muted-foreground px-3 py-2 text-xs",
114266
114428
  children: "No files yet."
@@ -114327,7 +114489,7 @@ function FileExplorer({ entries, selectedPath, onSelect, breadcrumbRoot, headerL
114327
114489
  return sortedEntries.find((entry) => entry.path === selectedPath && entry.type === "file") ?? null;
114328
114490
  }, [sortedEntries, selectedPath]);
114329
114491
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
114330
- className: "@container-[size] h-full",
114492
+ className: "@container-[size] h-full min-h-0 overflow-hidden",
114331
114493
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: layoutStyles }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
114332
114494
  className: "fev-layout",
114333
114495
  children: [
@@ -114487,6 +114649,30 @@ function useDocumentTranslationActivation() {
114487
114649
  };
114488
114650
  }
114489
114651
  //#endregion
114652
+ //#region src/lib/resolve-document-translation-config.ts
114653
+ function resolveDocumentTranslationConfig(translationConfig, globalSettings) {
114654
+ if (!translationConfig) return void 0;
114655
+ const local = translationConfig.engines?.local ?? {};
114656
+ const openai = translationConfig.engines?.openai ?? {};
114657
+ const resolvedLocalModel = local.model ?? globalSettings?.translationEngines.local.model;
114658
+ const resolvedLocalSelectedGroupId = local.selectedGroupId ?? globalSettings?.translationEngines.local.selectedGroupId;
114659
+ const resolvedOpenAIModel = openai.model ?? globalSettings?.translationEngines.openai.model;
114660
+ return {
114661
+ ...translationConfig,
114662
+ engines: {
114663
+ local: {
114664
+ ...local,
114665
+ ...resolvedLocalModel ? { model: resolvedLocalModel } : {},
114666
+ ...resolvedLocalSelectedGroupId ? { selectedGroupId: resolvedLocalSelectedGroupId } : {}
114667
+ },
114668
+ openai: {
114669
+ ...openai,
114670
+ ...resolvedOpenAIModel ? { model: resolvedOpenAIModel } : {}
114671
+ }
114672
+ }
114673
+ };
114674
+ }
114675
+ //#endregion
114490
114676
  //#region ../browser-translator/dist/index.mjs
114491
114677
  async function scanBrowserTranslationSupportTable(options) {
114492
114678
  const translator = (options.win ?? window).Translator;
@@ -131170,6 +131356,55 @@ function createProjectionContext(lookup, annotations, projections) {
131170
131356
  };
131171
131357
  }
131172
131358
  //#endregion
131359
+ //#region ../core/src/translation-language-pair.ts
131360
+ var OPUS_MT_DIRECTION_PATTERN = /^opus-mt-([a-z]{2,3})-([a-z]{2,3})$/i;
131361
+ function inferLocalDirectionalModelLanguagePair(model) {
131362
+ const modelName = model?.trim().split("/").pop();
131363
+ if (!modelName) return null;
131364
+ const match = OPUS_MT_DIRECTION_PATTERN.exec(modelName);
131365
+ if (!match) return null;
131366
+ const [, sourceLanguage, targetLanguage] = match;
131367
+ if (!sourceLanguage || !targetLanguage) return null;
131368
+ return {
131369
+ sourceLanguage: normalizeLanguageCode(sourceLanguage),
131370
+ targetLanguage: normalizeLanguageCode(targetLanguage)
131371
+ };
131372
+ }
131373
+ function checkLocalDirectionalModelLanguagePair(input) {
131374
+ const expected = inferLocalDirectionalModelLanguagePair(input.model);
131375
+ if (!expected) return { supported: true };
131376
+ if (!areCompatibleLanguageTags(expected.targetLanguage, input.targetLanguage)) return {
131377
+ supported: false,
131378
+ expected,
131379
+ message: `Selected local model supports ${formatLanguagePair(expected)}, but document translation is configured for target ${input.targetLanguage}.`
131380
+ };
131381
+ if (input.sourceLanguage && !areCompatibleLanguageTags(expected.sourceLanguage, input.sourceLanguage)) return {
131382
+ supported: false,
131383
+ expected,
131384
+ message: `Selected local model supports ${formatLanguagePair(expected)}, but document segment was detected as ${input.sourceLanguage} -> ${input.targetLanguage}.`
131385
+ };
131386
+ return {
131387
+ supported: true,
131388
+ expected
131389
+ };
131390
+ }
131391
+ function areCompatibleLanguageTags(expected, actual) {
131392
+ const expectedNormalized = normalizeLanguageTag$1(expected);
131393
+ const actualNormalized = normalizeLanguageTag$1(actual);
131394
+ if (!expectedNormalized || !actualNormalized) return false;
131395
+ if (expectedNormalized === actualNormalized) return true;
131396
+ return expectedNormalized.split("-")[0] === actualNormalized.split("-")[0];
131397
+ }
131398
+ function normalizeLanguageCode(language) {
131399
+ return language.trim().toLowerCase().replace(/_/g, "-");
131400
+ }
131401
+ function normalizeLanguageTag$1(language) {
131402
+ return normalizeLanguageCode(language);
131403
+ }
131404
+ function formatLanguagePair(pair) {
131405
+ return `${pair.sourceLanguage} -> ${pair.targetLanguage}`;
131406
+ }
131407
+ //#endregion
131173
131408
  //#region ../../node_modules/.pnpm/remark-gfm@4.0.1/node_modules/remark-gfm/lib/index.js
131174
131409
  /**
131175
131410
  * @import {Root} from 'mdast'
@@ -135698,6 +135933,43 @@ function escapeAttributeValue(value) {
135698
135933
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
135699
135934
  }
135700
135935
  //#endregion
135936
+ //#region src/lib/translation-adaptive-concurrency-log.ts
135937
+ var GLOBAL_STORAGE_KEY = "__OPENSPECUI_TRANSLATION_ADAPTIVE_CONCURRENCY_LOGS__";
135938
+ var MAX_LOG_AGE_MS = 1800 * 1e3;
135939
+ var MAX_LOG_ENTRIES = 256;
135940
+ var DEFAULT_SAMPLE_SIZE = 8;
135941
+ function createTranslationAdaptiveConcurrencyScopeKey(input) {
135942
+ return JSON.stringify({
135943
+ engineId: input.engineId,
135944
+ engineVersion: input.engineVersion ?? null,
135945
+ model: input.model ?? null,
135946
+ selectedGroupId: input.selectedGroupId ?? null,
135947
+ sourceLanguage: input.sourceLanguage.trim().toLowerCase(),
135948
+ targetLanguage: input.targetLanguage.trim().toLowerCase(),
135949
+ translatorContractVersion: input.translatorContractVersion
135950
+ });
135951
+ }
135952
+ function appendTranslationAdaptiveConcurrencyLog(entry) {
135953
+ const store = getTranslationAdaptiveConcurrencyLogStore();
135954
+ store.entries.push(entry);
135955
+ cleanupTranslationAdaptiveConcurrencyLogs(store);
135956
+ }
135957
+ function readRecentTranslationAdaptiveConcurrencyLogs(input = {}) {
135958
+ const store = getTranslationAdaptiveConcurrencyLogStore();
135959
+ cleanupTranslationAdaptiveConcurrencyLogs(store);
135960
+ return (input.scopeKey ? store.entries.filter((entry) => entry.scopeKey === input.scopeKey) : store.entries).slice(-Math.max(1, input.limit ?? DEFAULT_SAMPLE_SIZE));
135961
+ }
135962
+ function getTranslationAdaptiveConcurrencyLogStore() {
135963
+ const globalScope = globalThis;
135964
+ if (!globalScope[GLOBAL_STORAGE_KEY]) globalScope[GLOBAL_STORAGE_KEY] = { entries: [] };
135965
+ return globalScope[GLOBAL_STORAGE_KEY];
135966
+ }
135967
+ function cleanupTranslationAdaptiveConcurrencyLogs(store) {
135968
+ const cutoff = Date.now() - MAX_LOG_AGE_MS;
135969
+ if (store.entries.length === 0) return;
135970
+ store.entries = store.entries.filter((entry) => entry.recordedAt >= cutoff).slice(-MAX_LOG_ENTRIES);
135971
+ }
135972
+ //#endregion
135701
135973
  //#region ../search/src/engine.ts
135702
135974
  function normalizeText(input) {
135703
135975
  return input.toLowerCase().replace(/\s+/g, " ").trim();
@@ -136157,6 +136429,7 @@ var DOCUMENT_LANGUAGE_CONFIDENCE_THRESHOLD = .45;
136157
136429
  var SEGMENT_LANGUAGE_CONFIDENCE_THRESHOLD = .62;
136158
136430
  var TRANSLATION_DISPLAY_POLICY_VERSION = 2;
136159
136431
  var BROWSER_SOURCE_LANGUAGE_ORDER = new Map(SUPPORTED_TRANSLATION_LANGUAGES.map((language, index) => [language.code, index]));
136432
+ var SUPPORTED_TRANSLATION_LANGUAGE_CODES = new Set(SUPPORTED_TRANSLATION_LANGUAGES.map((language) => language.code));
136160
136433
  function isBrowserTranslationSupported() {
136161
136434
  return typeof window !== "undefined" && !!window.Translator;
136162
136435
  }
@@ -136374,14 +136647,20 @@ function mergeBrowserSupportRows(rows, row) {
136374
136647
  }
136375
136648
  function sortBrowserSupportRows(rows) {
136376
136649
  return [...rows].sort((left, right) => {
136377
- const leftOrder = BROWSER_SOURCE_LANGUAGE_ORDER.get(left.sourceLanguage) ?? Number.MAX_SAFE_INTEGER;
136378
- const rightOrder = BROWSER_SOURCE_LANGUAGE_ORDER.get(right.sourceLanguage) ?? Number.MAX_SAFE_INTEGER;
136650
+ const leftOrder = getBrowserSourceLanguageOrder(left.sourceLanguage) ?? Number.MAX_SAFE_INTEGER;
136651
+ const rightOrder = getBrowserSourceLanguageOrder(right.sourceLanguage) ?? Number.MAX_SAFE_INTEGER;
136379
136652
  if (leftOrder !== rightOrder) return leftOrder - rightOrder;
136380
136653
  const targetDelta = left.targetLanguage.localeCompare(right.targetLanguage);
136381
136654
  if (targetDelta !== 0) return targetDelta;
136382
136655
  return left.sourceLanguage.localeCompare(right.sourceLanguage);
136383
136656
  });
136384
136657
  }
136658
+ function getBrowserSourceLanguageOrder(sourceLanguage) {
136659
+ return isSupportedTranslationLanguageCode(sourceLanguage) ? BROWSER_SOURCE_LANGUAGE_ORDER.get(sourceLanguage) : void 0;
136660
+ }
136661
+ function isSupportedTranslationLanguageCode(language) {
136662
+ return SUPPORTED_TRANSLATION_LANGUAGE_CODES.has(language);
136663
+ }
136385
136664
  async function translateMarkdownDocumentProgressively(args, onPatch) {
136386
136665
  const segments = extractTranslatableSegments(args.markdown);
136387
136666
  if (segments.length === 0) return {
@@ -136391,9 +136670,9 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136391
136670
  };
136392
136671
  const engine = args.engine ?? createBrowserTranslationExecution();
136393
136672
  const languageDetection = await createSourceLanguageDetectionSession(args.markdown, args.signal);
136394
- const translatorBySourceLanguage = /* @__PURE__ */ new Map();
136673
+ const translatedSegments = [...segments];
136674
+ const pendingJobsBySourceLanguage = /* @__PURE__ */ new Map();
136395
136675
  try {
136396
- const translatedSegments = [];
136397
136676
  for (const [segmentIndex, segment] of segments.entries()) {
136398
136677
  throwIfAborted(args.signal);
136399
136678
  const sourceLanguage = await languageDetection.detectSegmentLanguage(segment.translatorInput, args.signal);
@@ -136407,7 +136686,7 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136407
136686
  targetLanguage: args.targetLanguage,
136408
136687
  status: "translated"
136409
136688
  };
136410
- translatedSegments.push(translatedSegment);
136689
+ translatedSegments[segmentIndex] = translatedSegment;
136411
136690
  onPatch({
136412
136691
  segmentIndex,
136413
136692
  segment: translatedSegment
@@ -136420,33 +136699,27 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136420
136699
  targetLanguage: args.targetLanguage
136421
136700
  }) : null;
136422
136701
  if (cachedSegment) {
136423
- translatedSegments.push(cachedSegment);
136702
+ translatedSegments[segmentIndex] = cachedSegment;
136424
136703
  onPatch({
136425
136704
  segmentIndex,
136426
136705
  segment: cachedSegment
136427
136706
  });
136428
136707
  continue;
136429
136708
  }
136430
- const translator = await getPooledTranslator(engine, translatorBySourceLanguage, sourceLanguage, args.targetLanguage, args.signal);
136431
- const protectedInput = segment.placeholderProtocol ? {
136432
- text: segment.translatorInput,
136433
- restore: (output) => output
136434
- } : protectTranslatorInput(segment.translatorInput);
136435
- const target = await raceAbort(readSingleBatchOutput(translator.batchTranslate([protectedInput.text], { signal: args.signal })), args.signal);
136436
- const restoredTarget = segment.placeholderProtocol ? restoreTranslatedPlaceholderFragment(target, segment.placeholderProtocol) : { target: protectedInput.restore(target).trim() };
136437
- const translatedSegment = {
136438
- ...segment,
136439
- ...restoredTarget,
136709
+ const pendingJob = {
136710
+ segmentIndex,
136711
+ segment,
136440
136712
  sourceLanguage,
136441
- targetLanguage: args.targetLanguage,
136442
- status: "translated"
136713
+ cacheKey,
136714
+ protectedInput: segment.placeholderProtocol ? {
136715
+ text: segment.translatorInput,
136716
+ restore: (output) => output
136717
+ } : protectTranslatorInput(segment.translatorInput),
136718
+ estimatedTokens: estimateTranslationTokens(segment.translatorInput)
136443
136719
  };
136444
- if (cacheKey) writeCachedTranslationSegment(args.cache, cacheKey, translatedSegment);
136445
- translatedSegments.push(translatedSegment);
136446
- onPatch({
136447
- segmentIndex,
136448
- segment: translatedSegment
136449
- });
136720
+ const pendingJobs = pendingJobsBySourceLanguage.get(sourceLanguage) ?? [];
136721
+ pendingJobs.push(pendingJob);
136722
+ pendingJobsBySourceLanguage.set(sourceLanguage, pendingJobs);
136450
136723
  } catch (error) {
136451
136724
  if (args.signal.aborted) throw error;
136452
136725
  const failedSegment = {
@@ -136456,13 +136729,23 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136456
136729
  status: "error",
136457
136730
  error: getErrorMessage(error)
136458
136731
  };
136459
- translatedSegments.push(failedSegment);
136732
+ translatedSegments[segmentIndex] = failedSegment;
136460
136733
  onPatch({
136461
136734
  segmentIndex,
136462
136735
  segment: failedSegment
136463
136736
  });
136464
136737
  }
136465
136738
  }
136739
+ await Promise.all([...pendingJobsBySourceLanguage.entries()].map(([sourceLanguage, jobs]) => translatePendingJobsBySourceLanguage({
136740
+ engine,
136741
+ sourceLanguage,
136742
+ targetLanguage: args.targetLanguage,
136743
+ signal: args.signal,
136744
+ cache: args.cache,
136745
+ jobs,
136746
+ translatedSegments,
136747
+ onPatch
136748
+ })));
136466
136749
  return {
136467
136750
  segments: translatedSegments,
136468
136751
  displayMode: args.displayMode,
@@ -136470,24 +136753,247 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136470
136753
  targetLanguage: args.targetLanguage
136471
136754
  };
136472
136755
  } finally {
136473
- translatorBySourceLanguage.forEach((translator) => translator.destroy?.());
136474
136756
  languageDetection.destroy();
136475
136757
  }
136476
136758
  }
136477
- async function getPooledTranslator(engine, translatorBySourceLanguage, sourceLanguage, targetLanguage, signal) {
136478
- const existing = translatorBySourceLanguage.get(sourceLanguage);
136479
- if (existing) return existing;
136480
- const translator = await engine.factory.create({
136481
- sourceLanguage,
136482
- targetLanguage,
136483
- signal
136759
+ async function translatePendingJobsBySourceLanguage(input) {
136760
+ const markJobsError = (jobs, message) => {
136761
+ for (const job of jobs) {
136762
+ const failedSegment = {
136763
+ ...job.segment,
136764
+ sourceLanguage: job.sourceLanguage,
136765
+ targetLanguage: input.targetLanguage,
136766
+ status: "error",
136767
+ error: message
136768
+ };
136769
+ input.translatedSegments[job.segmentIndex] = failedSegment;
136770
+ input.onPatch({
136771
+ segmentIndex: job.segmentIndex,
136772
+ segment: failedSegment
136773
+ });
136774
+ }
136775
+ };
136776
+ const unsupportedLanguagePairMessage = getUnsupportedEngineLanguagePairMessage({
136777
+ engine: input.engine,
136778
+ sourceLanguage: input.sourceLanguage,
136779
+ targetLanguage: input.targetLanguage
136484
136780
  });
136485
- translatorBySourceLanguage.set(sourceLanguage, translator);
136486
- return translator;
136781
+ if (unsupportedLanguagePairMessage) {
136782
+ markJobsError(input.jobs, unsupportedLanguagePairMessage);
136783
+ return;
136784
+ }
136785
+ const batches = packTranslationJobs(input.jobs);
136786
+ if (batches.length === 0) return;
136787
+ const maxConcurrency = Math.min(6, batches.length);
136788
+ const scopeKey = createTranslationAdaptiveConcurrencyScopeKey({
136789
+ engineId: input.engine.cacheIdentity.engineId,
136790
+ engineVersion: input.engine.cacheIdentity.engineVersion,
136791
+ model: input.engine.cacheIdentity.model,
136792
+ selectedGroupId: input.engine.cacheIdentity.selectedGroupId,
136793
+ sourceLanguage: input.sourceLanguage,
136794
+ targetLanguage: input.targetLanguage,
136795
+ translatorContractVersion: input.engine.cacheIdentity.translatorContractVersion
136796
+ });
136797
+ let desiredConcurrency = 1;
136798
+ let nextBatchIndex = 0;
136799
+ let activeWorkers = 0;
136800
+ let completedBatches = 0;
136801
+ const workerPromises = /* @__PURE__ */ new Set();
136802
+ const startWorkersToDesired = () => {
136803
+ while (activeWorkers < desiredConcurrency && nextBatchIndex < batches.length && !input.signal.aborted) startWorker();
136804
+ };
136805
+ const maybeGrowConcurrency = () => {
136806
+ if (desiredConcurrency >= maxConcurrency) return;
136807
+ const recentLogs = readRecentTranslationAdaptiveConcurrencyLogs({
136808
+ scopeKey,
136809
+ limit: Math.max(4, desiredConcurrency * 2)
136810
+ });
136811
+ if (desiredConcurrency === 1) {
136812
+ if (completedBatches >= 1 && batches.length > 1 && recentLogs.length > 0) {
136813
+ desiredConcurrency = 2;
136814
+ startWorkersToDesired();
136815
+ }
136816
+ return;
136817
+ }
136818
+ if (completedBatches < desiredConcurrency) return;
136819
+ if (recentLogs.length < desiredConcurrency * 2) return;
136820
+ const window = recentLogs.slice(-desiredConcurrency * 2);
136821
+ const split = Math.max(1, Math.floor(window.length / 2));
136822
+ const earlierThroughput = summarizeTranslationLogThroughput(window.slice(0, split));
136823
+ const laterThroughput = summarizeTranslationLogThroughput(window.slice(split));
136824
+ if (earlierThroughput > 0 && laterThroughput >= earlierThroughput * 1.08) {
136825
+ desiredConcurrency = Math.min(maxConcurrency, desiredConcurrency + 1);
136826
+ startWorkersToDesired();
136827
+ }
136828
+ };
136829
+ const applyBatchResult = async (batch, outputs) => {
136830
+ for (const [offset, job] of batch.jobs.entries()) {
136831
+ const target = outputs[offset] ?? "";
136832
+ const restoredTarget = job.segment.placeholderProtocol ? restoreTranslatedPlaceholderFragment(target, job.segment.placeholderProtocol) : { target: job.protectedInput.restore(target).trim() };
136833
+ const translatedSegment = {
136834
+ ...job.segment,
136835
+ ...restoredTarget,
136836
+ sourceLanguage: job.sourceLanguage,
136837
+ targetLanguage: input.targetLanguage,
136838
+ status: "translated"
136839
+ };
136840
+ input.translatedSegments[job.segmentIndex] = translatedSegment;
136841
+ if (job.cacheKey) writeCachedTranslationSegment(input.cache, job.cacheKey, translatedSegment);
136842
+ input.onPatch({
136843
+ segmentIndex: job.segmentIndex,
136844
+ segment: translatedSegment
136845
+ });
136846
+ }
136847
+ };
136848
+ const markBatchError = (batch, error) => {
136849
+ markJobsError(batch.jobs, getErrorMessage(error));
136850
+ };
136851
+ const startWorker = () => {
136852
+ if (input.signal.aborted || nextBatchIndex >= batches.length) return;
136853
+ activeWorkers += 1;
136854
+ let workerPromise;
136855
+ workerPromise = (async () => {
136856
+ let translator = null;
136857
+ try {
136858
+ translator = await input.engine.factory.create({
136859
+ sourceLanguage: input.sourceLanguage,
136860
+ targetLanguage: input.targetLanguage,
136861
+ signal: input.signal
136862
+ });
136863
+ while (!input.signal.aborted) {
136864
+ const batchIndex = nextBatchIndex;
136865
+ if (batchIndex >= batches.length) break;
136866
+ nextBatchIndex += 1;
136867
+ const batch = batches[batchIndex];
136868
+ const startedAt = getCurrentTimeMs();
136869
+ try {
136870
+ await applyBatchResult(batch, await collectBatchTranslationOutputs(translator.batchTranslate(batch.jobs.map((job) => job.protectedInput.text), { signal: input.signal }), batch.jobs.length));
136871
+ completedBatches += 1;
136872
+ const elapsedMs = Math.max(1, getCurrentTimeMs() - startedAt);
136873
+ appendTranslationAdaptiveConcurrencyLog({
136874
+ scopeKey,
136875
+ recordedAt: Date.now(),
136876
+ engineId: input.engine.cacheIdentity.engineId,
136877
+ engineVersion: input.engine.cacheIdentity.engineVersion,
136878
+ model: input.engine.cacheIdentity.model,
136879
+ selectedGroupId: input.engine.cacheIdentity.selectedGroupId,
136880
+ sourceLanguage: input.sourceLanguage,
136881
+ targetLanguage: input.targetLanguage,
136882
+ batchIndex,
136883
+ batchSize: batch.jobs.length,
136884
+ estimatedTokens: batch.estimatedTokens,
136885
+ elapsedMs,
136886
+ throughputTokensPerMs: batch.estimatedTokens / elapsedMs,
136887
+ desiredConcurrency,
136888
+ activeWorkers,
136889
+ maxConcurrency
136890
+ });
136891
+ maybeGrowConcurrency();
136892
+ } catch (error) {
136893
+ if (input.signal.aborted) throw error;
136894
+ markBatchError(batch, error);
136895
+ }
136896
+ }
136897
+ } finally {
136898
+ translator?.destroy?.();
136899
+ }
136900
+ })().finally(() => {
136901
+ activeWorkers -= 1;
136902
+ workerPromises.delete(workerPromise);
136903
+ startWorkersToDesired();
136904
+ });
136905
+ workerPromises.add(workerPromise);
136906
+ };
136907
+ startWorkersToDesired();
136908
+ while (workerPromises.size > 0) await Promise.race(workerPromises);
136909
+ }
136910
+ function getUnsupportedEngineLanguagePairMessage(input) {
136911
+ if (input.engine.cacheIdentity.engineId !== "local") return null;
136912
+ const directionCheck = checkLocalDirectionalModelLanguagePair({
136913
+ model: input.engine.cacheIdentity.model,
136914
+ sourceLanguage: input.sourceLanguage,
136915
+ targetLanguage: input.targetLanguage
136916
+ });
136917
+ if (directionCheck.supported) return null;
136918
+ return directionCheck.message ?? "Selected local model does not support the detected translation direction.";
136919
+ }
136920
+ function summarizeTranslationLogThroughput(logs) {
136921
+ if (logs.length === 0) return 0;
136922
+ const totalTokens = logs.reduce((total, log) => total + log.estimatedTokens, 0);
136923
+ const totalElapsedMs = logs.reduce((total, log) => total + log.elapsedMs, 0);
136924
+ if (totalTokens <= 0 || totalElapsedMs <= 0) return 0;
136925
+ return totalTokens / totalElapsedMs;
136926
+ }
136927
+ function packTranslationJobs(jobs) {
136928
+ if (jobs.length === 0) return [];
136929
+ const averageTokens = jobs.reduce((total, job) => total + job.estimatedTokens, 0) / Math.max(1, jobs.length);
136930
+ const targetTokens = Math.max(1, Math.round(averageTokens * 6));
136931
+ const batches = [];
136932
+ let currentJobs = [];
136933
+ let currentTokens = 0;
136934
+ const flush = () => {
136935
+ if (currentJobs.length === 0) return;
136936
+ batches.push({
136937
+ jobs: currentJobs,
136938
+ estimatedTokens: currentTokens
136939
+ });
136940
+ currentJobs = [];
136941
+ currentTokens = 0;
136942
+ };
136943
+ for (const job of jobs) {
136944
+ if (currentJobs.length === 0) {
136945
+ currentJobs = [job];
136946
+ currentTokens = job.estimatedTokens;
136947
+ continue;
136948
+ }
136949
+ const nextTokens = currentTokens + job.estimatedTokens;
136950
+ if (nextTokens <= targetTokens) {
136951
+ currentJobs.push(job);
136952
+ currentTokens = nextTokens;
136953
+ continue;
136954
+ }
136955
+ const withoutDelta = Math.abs(currentTokens - targetTokens);
136956
+ if (Math.abs(nextTokens - targetTokens) <= withoutDelta) {
136957
+ currentJobs.push(job);
136958
+ currentTokens = nextTokens;
136959
+ flush();
136960
+ continue;
136961
+ }
136962
+ flush();
136963
+ currentJobs = [job];
136964
+ currentTokens = job.estimatedTokens;
136965
+ }
136966
+ flush();
136967
+ return batches;
136968
+ }
136969
+ async function collectBatchTranslationOutputs(stream, expectedCount) {
136970
+ const outputs = /* @__PURE__ */ new Map();
136971
+ for await (const item of stream) {
136972
+ if (item.index < 0 || item.index >= expectedCount) throw new Error(`Translator yielded output for unexpected index ${item.index}.`);
136973
+ if (!outputs.has(item.index)) outputs.set(item.index, item.output);
136974
+ }
136975
+ if (outputs.size !== expectedCount) throw new Error(`Translator returned ${outputs.size} outputs for ${expectedCount} inputs.`);
136976
+ return Array.from({ length: expectedCount }, (_, index) => outputs.get(index) ?? "");
136977
+ }
136978
+ function estimateTranslationTokens(input) {
136979
+ const trimmed = input.trim();
136980
+ if (!trimmed) return 1;
136981
+ const segmenter = getTokenSegmenter();
136982
+ if (!segmenter) return Math.max(1, trimmed.split(/\s+/).filter(Boolean).length);
136983
+ let count = 0;
136984
+ for (const segment of segmenter.segment(trimmed)) if (segment.isWordLike ?? segment.segment.trim().length > 0) count += 1;
136985
+ return Math.max(1, count);
136487
136986
  }
136488
- async function readSingleBatchOutput(stream) {
136489
- for await (const item of stream) return item.output;
136490
- throw new Error("Translator returned no batch output.");
136987
+ function getTokenSegmenter() {
136988
+ if (typeof Intl === "undefined" || typeof Intl.Segmenter !== "function") return null;
136989
+ try {
136990
+ return new Intl.Segmenter(void 0, { granularity: "word" });
136991
+ } catch {
136992
+ return null;
136993
+ }
136994
+ }
136995
+ function getCurrentTimeMs() {
136996
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
136491
136997
  }
136492
136998
  async function createSourceLanguageDetectionSession(markdown, signal) {
136493
136999
  const detectorFactory = window.LanguageDetector;
@@ -136963,87 +137469,10 @@ function getErrorMessage(error) {
136963
137469
  return error instanceof Error ? error.message : "Unknown translation error.";
136964
137470
  }
136965
137471
  //#endregion
136966
- //#region src/lib/translate-service.ts
136967
- function createTranslationEngineExecution(config) {
136968
- if (config.engineId === "browser" || isStaticMode()) return createBrowserTranslationExecution();
136969
- const model = config.engineId === "openai" ? config.engines.openai.model : config.engines.local.model;
136970
- return {
136971
- factory: new TrpcTranslatorFactory(config.engineId, model, config.engineId === "local" ? config.engines.local.selectedGroupId : void 0),
136972
- cacheIdentity: {
136973
- engineId: config.engineId,
136974
- model,
136975
- selectedGroupId: config.engineId === "local" ? config.engines.local.selectedGroupId : void 0,
136976
- translatorContractVersion: 2
136977
- }
136978
- };
136979
- }
136980
- var TrpcTranslatorFactory = class {
136981
- constructor(engineId, model, selectedGroupId) {
136982
- this.engineId = engineId;
136983
- this.model = model;
136984
- this.selectedGroupId = selectedGroupId;
136985
- }
136986
- async create(options) {
136987
- return new TrpcTranslator({
136988
- engineId: this.engineId,
136989
- sourceLanguage: options.sourceLanguage,
136990
- targetLanguage: options.targetLanguage,
136991
- model: options.model ?? this.model,
136992
- selectedGroupId: this.engineId === "local" ? this.selectedGroupId : void 0
136993
- });
136994
- }
136995
- };
136996
- var TrpcTranslator = class {
136997
- constructor(options) {
136998
- this.options = options;
136999
- }
137000
- async *batchTranslate(inputs, options) {
137001
- if (options?.signal?.aborted) throw new DOMException("Translation cancelled.", "AbortError");
137002
- const queue = [];
137003
- let completed = false;
137004
- let thrown = null;
137005
- const subscription = trpcClient.translationEngines.batchTranslate.subscribe({
137006
- engineId: this.options.engineId,
137007
- sourceLanguage: this.options.sourceLanguage,
137008
- targetLanguage: this.options.targetLanguage,
137009
- model: this.options.model,
137010
- selectedGroupId: this.options.selectedGroupId,
137011
- inputs,
137012
- instructions: options?.instructions,
137013
- context: options?.context
137014
- }, {
137015
- onData(event) {
137016
- queue.push(event);
137017
- },
137018
- onError(error) {
137019
- thrown = error instanceof Error ? error : new Error(String(error));
137020
- completed = true;
137021
- },
137022
- onComplete() {
137023
- completed = true;
137024
- }
137025
- });
137026
- try {
137027
- while (!completed || queue.length > 0) {
137028
- if (options?.signal?.aborted) throw new DOMException("Translation cancelled.", "AbortError");
137029
- const item = queue.shift();
137030
- if (item) {
137031
- yield item;
137032
- continue;
137033
- }
137034
- await new Promise((resolve) => setTimeout(resolve, 0));
137035
- }
137036
- if (thrown) throw thrown;
137037
- } finally {
137038
- subscription.unsubscribe();
137039
- }
137040
- }
137041
- };
137042
- //#endregion
137043
137472
  //#region ../core/src/local-download-profiles.ts
137044
137473
  function selectLocalDownloadGroup(plan, selectedGroupId) {
137045
137474
  if (!plan?.groups?.length) return null;
137046
- return (selectedGroupId ? plan.groups.find((group) => group.id === selectedGroupId && group.selectable) : void 0) ?? plan.groups.find((group) => group.selected) ?? selectSmallestSelectableGroup(plan.groups);
137475
+ return (selectedGroupId ? plan.groups.find((group) => group.selectable && (group.id === selectedGroupId || group.baseGroupId === selectedGroupId)) : void 0) ?? plan.groups.find((group) => group.selected) ?? selectSmallestSelectableGroup(plan.groups);
137047
137476
  }
137048
137477
  function selectSmallestSelectableGroup(groups) {
137049
137478
  return groups.filter((group) => group.selectable && group.estimatedTotalBytes !== void 0).sort((left, right) => (left.estimatedTotalBytes ?? 0) - (right.estimatedTotalBytes ?? 0))[0] ?? null;
@@ -137142,6 +137571,253 @@ function isLocalAssetReady(asset, selectedGroupId) {
137142
137571
  return allRequiredFilesReady;
137143
137572
  }
137144
137573
  //#endregion
137574
+ //#region src/lib/translate-service.ts
137575
+ async function resolveTranslateServiceState(input) {
137576
+ const config = input.config;
137577
+ if (!config?.enabled || !input.hasSource) return emitTranslateServiceState(input.onUpdate, { status: projectTranslateServiceStatus({
137578
+ enabled: config?.enabled ?? false,
137579
+ hasSource: input.hasSource,
137580
+ engineId: config?.engineId ?? "browser"
137581
+ }) });
137582
+ if (config.engineId === "local") {
137583
+ const model = config.engines.local.model?.trim();
137584
+ if (!model) return emitTranslateServiceState(input.onUpdate, { status: projectTranslateServiceStatus({
137585
+ enabled: config.enabled,
137586
+ hasSource: input.hasSource,
137587
+ engineId: "local",
137588
+ localModel: model,
137589
+ localSelectedGroupId: config.engines.local.selectedGroupId
137590
+ }) });
137591
+ const directionCheck = checkLocalDirectionalModelLanguagePair({
137592
+ model,
137593
+ targetLanguage: config.targetLanguage
137594
+ });
137595
+ if (!directionCheck.supported) return emitTranslateServiceState(input.onUpdate, { status: {
137596
+ state: "unavailable",
137597
+ engineId: "local",
137598
+ message: directionCheck.message ?? "Selected local model does not support the configured target language."
137599
+ } });
137600
+ input.onUpdate?.(createTranslateServiceState({ status: projectTranslateServiceStatus({
137601
+ enabled: config.enabled,
137602
+ hasSource: input.hasSource,
137603
+ engineId: "local",
137604
+ localModel: model,
137605
+ localSelectedGroupId: config.engines.local.selectedGroupId,
137606
+ localAssetLoading: true
137607
+ }) }));
137608
+ try {
137609
+ const panelState = await trpcClient.localModels.panelState.query({
137610
+ modelId: model,
137611
+ selectedGroupId: config.engines.local.selectedGroupId
137612
+ });
137613
+ const selectedGroupId = panelState.selectedGroupId ?? config.engines.local.selectedGroupId;
137614
+ return createTranslateServiceState({ status: projectTranslateServiceStatus({
137615
+ enabled: config.enabled,
137616
+ hasSource: input.hasSource,
137617
+ engineId: "local",
137618
+ localModel: model,
137619
+ localSelectedGroupId: selectedGroupId,
137620
+ localAsset: panelState.asset
137621
+ }) });
137622
+ } catch (assetError) {
137623
+ return createTranslateServiceState({ status: {
137624
+ state: "unavailable",
137625
+ engineId: "local",
137626
+ message: assetError instanceof Error ? assetError.message : "Unable to check local model files."
137627
+ } });
137628
+ }
137629
+ }
137630
+ if (config.engineId === "openai") return emitTranslateServiceState(input.onUpdate, { status: projectTranslateServiceStatus({
137631
+ enabled: config.enabled,
137632
+ hasSource: input.hasSource,
137633
+ engineId: "openai"
137634
+ }) });
137635
+ const cachedTable = getBrowserSupportTableState(config.targetLanguage);
137636
+ if (cachedTable) return emitTranslateServiceState(input.onUpdate, {
137637
+ browserSupportTable: cachedTable,
137638
+ status: projectTranslateServiceStatus({
137639
+ enabled: config.enabled,
137640
+ hasSource: input.hasSource,
137641
+ engineId: "browser",
137642
+ browserSupportTable: cachedTable
137643
+ })
137644
+ });
137645
+ const checkingTable = {
137646
+ state: "checking",
137647
+ table: null,
137648
+ message: "Checking browser translation pairs…"
137649
+ };
137650
+ input.onUpdate?.(createTranslateServiceState({
137651
+ browserSupportTable: checkingTable,
137652
+ status: projectTranslateServiceStatus({
137653
+ enabled: config.enabled,
137654
+ hasSource: input.hasSource,
137655
+ engineId: "browser",
137656
+ browserSupportTable: checkingTable
137657
+ })
137658
+ }));
137659
+ try {
137660
+ const nextTable = await scanBrowserTranslationPairs(config.targetLanguage, {
137661
+ signal: input.signal ?? new AbortController().signal,
137662
+ onProgress: (progressState) => {
137663
+ input.onUpdate?.(createTranslateServiceState({
137664
+ browserSupportTable: progressState,
137665
+ status: projectTranslateServiceStatus({
137666
+ enabled: config.enabled,
137667
+ hasSource: input.hasSource,
137668
+ engineId: "browser",
137669
+ browserSupportTable: progressState
137670
+ })
137671
+ }));
137672
+ }
137673
+ });
137674
+ return createTranslateServiceState({
137675
+ browserSupportTable: nextTable,
137676
+ status: projectTranslateServiceStatus({
137677
+ enabled: config.enabled,
137678
+ hasSource: input.hasSource,
137679
+ engineId: "browser",
137680
+ browserSupportTable: nextTable
137681
+ })
137682
+ });
137683
+ } catch (probeError) {
137684
+ const nextCapability = {
137685
+ availability: "error",
137686
+ message: probeError instanceof Error ? probeError.message : "Unable to check translation support."
137687
+ };
137688
+ return createTranslateServiceState({
137689
+ capability: nextCapability,
137690
+ status: projectTranslateServiceStatus({
137691
+ enabled: config.enabled,
137692
+ hasSource: input.hasSource,
137693
+ engineId: "browser",
137694
+ browserCapability: nextCapability
137695
+ })
137696
+ });
137697
+ }
137698
+ }
137699
+ function prepareTranslateServiceRun(input) {
137700
+ if (input.config.engineId !== "browser") return createTranslateServiceState({ status: projectTranslateServiceStatus({
137701
+ enabled: input.config.enabled,
137702
+ hasSource: input.hasSource,
137703
+ engineId: input.config.engineId
137704
+ }) });
137705
+ const preferredRow = input.browserSupportTable?.table?.rows.find((row) => row.availability === "available") ?? input.browserSupportTable?.table?.rows.find((row) => row.availability === "downloading") ?? input.browserSupportTable?.table?.rows.find((row) => row.availability === "downloadable") ?? null;
137706
+ if (!preferredRow) return createTranslateServiceState({
137707
+ browserSupportTable: input.browserSupportTable,
137708
+ status: projectTranslateServiceStatus({
137709
+ enabled: input.config.enabled,
137710
+ hasSource: input.hasSource,
137711
+ engineId: "browser",
137712
+ browserSupportTable: input.browserSupportTable
137713
+ })
137714
+ });
137715
+ const nextCapability = {
137716
+ availability: preferredRow.availability,
137717
+ progress: preferredRow.progress,
137718
+ message: preferredRow.message
137719
+ };
137720
+ const nextTable = patchBrowserSupportTableRow(input.config.targetLanguage, preferredRow, { message: void 0 });
137721
+ return createTranslateServiceState({
137722
+ capability: nextCapability,
137723
+ browserSupportTable: nextTable,
137724
+ status: projectTranslateServiceStatus({
137725
+ enabled: input.config.enabled,
137726
+ hasSource: input.hasSource,
137727
+ engineId: "browser",
137728
+ browserSupportTable: nextTable,
137729
+ browserCapability: nextCapability
137730
+ })
137731
+ });
137732
+ }
137733
+ function createTranslationEngineExecution(config) {
137734
+ if (config.engineId === "browser" || isStaticMode()) return createBrowserTranslationExecution();
137735
+ const model = config.engineId === "openai" ? config.engines.openai.model : config.engines.local.model;
137736
+ return {
137737
+ factory: new TrpcTranslatorFactory(config.engineId, model, config.engineId === "local" ? config.engines.local.selectedGroupId : void 0),
137738
+ cacheIdentity: {
137739
+ engineId: config.engineId,
137740
+ model,
137741
+ selectedGroupId: config.engineId === "local" ? config.engines.local.selectedGroupId : void 0,
137742
+ translatorContractVersion: 2
137743
+ }
137744
+ };
137745
+ }
137746
+ var TrpcTranslatorFactory = class {
137747
+ constructor(engineId, model, selectedGroupId) {
137748
+ this.engineId = engineId;
137749
+ this.model = model;
137750
+ this.selectedGroupId = selectedGroupId;
137751
+ }
137752
+ async create(options) {
137753
+ return new TrpcTranslator({
137754
+ engineId: this.engineId,
137755
+ sourceLanguage: options.sourceLanguage,
137756
+ targetLanguage: options.targetLanguage,
137757
+ model: options.model ?? this.model,
137758
+ selectedGroupId: this.engineId === "local" ? this.selectedGroupId : void 0
137759
+ });
137760
+ }
137761
+ };
137762
+ var TrpcTranslator = class {
137763
+ constructor(options) {
137764
+ this.options = options;
137765
+ }
137766
+ async *batchTranslate(inputs, options) {
137767
+ if (options?.signal?.aborted) throw new DOMException("Translation cancelled.", "AbortError");
137768
+ const queue = [];
137769
+ let completed = false;
137770
+ let thrown = null;
137771
+ const subscription = trpcClient.translationEngines.batchTranslate.subscribe({
137772
+ engineId: this.options.engineId,
137773
+ sourceLanguage: this.options.sourceLanguage,
137774
+ targetLanguage: this.options.targetLanguage,
137775
+ model: this.options.model,
137776
+ selectedGroupId: this.options.selectedGroupId,
137777
+ inputs,
137778
+ instructions: options?.instructions,
137779
+ context: options?.context
137780
+ }, {
137781
+ onData(event) {
137782
+ queue.push(event);
137783
+ },
137784
+ onError(error) {
137785
+ thrown = error instanceof Error ? error : new Error(String(error));
137786
+ completed = true;
137787
+ },
137788
+ onComplete() {
137789
+ completed = true;
137790
+ }
137791
+ });
137792
+ try {
137793
+ while (!completed || queue.length > 0) {
137794
+ if (options?.signal?.aborted) throw new DOMException("Translation cancelled.", "AbortError");
137795
+ const item = queue.shift();
137796
+ if (item) {
137797
+ yield item;
137798
+ continue;
137799
+ }
137800
+ await new Promise((resolve) => setTimeout(resolve, 0));
137801
+ }
137802
+ if (thrown) throw thrown;
137803
+ } finally {
137804
+ subscription.unsubscribe();
137805
+ }
137806
+ }
137807
+ };
137808
+ function createTranslateServiceState(input) {
137809
+ return {
137810
+ capability: input.capability ?? null,
137811
+ browserSupportTable: input.browserSupportTable ?? null,
137812
+ status: input.status
137813
+ };
137814
+ }
137815
+ function emitTranslateServiceState(onUpdate, input) {
137816
+ const state = createTranslateServiceState(input);
137817
+ onUpdate?.(state);
137818
+ return state;
137819
+ }
137820
+ //#endregion
137145
137821
  //#region src/lib/use-document-translation.ts
137146
137822
  function useDocumentTranslation(markdown, config) {
137147
137823
  const [status, setStatus] = (0, import_react.useState)("source");
@@ -137154,9 +137830,11 @@ function useDocumentTranslation(markdown, config) {
137154
137830
  const [error, setError] = (0, import_react.useState)(null);
137155
137831
  const [result, setResult] = (0, import_react.useState)(null);
137156
137832
  const abortRef = (0, import_react.useRef)(null);
137833
+ const generationRef = (0, import_react.useRef)(0);
137157
137834
  const latestStartRef = (0, import_react.useRef)(null);
137158
137835
  const { activation } = useDocumentTranslationActivation();
137159
137836
  const cancel = (0, import_react.useCallback)(() => {
137837
+ generationRef.current += 1;
137160
137838
  abortRef.current?.abort();
137161
137839
  abortRef.current = null;
137162
137840
  setStatus("source");
@@ -137164,6 +137842,7 @@ function useDocumentTranslation(markdown, config) {
137164
137842
  setError(null);
137165
137843
  }, []);
137166
137844
  const reset = (0, import_react.useCallback)(() => {
137845
+ generationRef.current += 1;
137167
137846
  abortRef.current?.abort();
137168
137847
  abortRef.current = null;
137169
137848
  setStatus("source");
@@ -137172,6 +137851,7 @@ function useDocumentTranslation(markdown, config) {
137172
137851
  }, []);
137173
137852
  (0, import_react.useEffect)(() => reset, [reset]);
137174
137853
  (0, import_react.useEffect)(() => {
137854
+ generationRef.current += 1;
137175
137855
  setCapability(null);
137176
137856
  setBrowserSupportTable(null);
137177
137857
  setResult(null);
@@ -137181,141 +137861,39 @@ function useDocumentTranslation(markdown, config) {
137181
137861
  markdown,
137182
137862
  config?.displayMode,
137183
137863
  config?.enabled,
137864
+ config?.engineId,
137865
+ config?.engines.local.model,
137866
+ config?.engines.local.selectedGroupId,
137867
+ config?.engines.openai.model,
137184
137868
  config?.targetLanguage
137185
137869
  ]);
137186
137870
  (0, import_react.useEffect)(() => {
137187
137871
  let disposed = false;
137188
- if (!config?.enabled || markdown.length === 0) {
137189
- setCapability(null);
137190
- setBrowserSupportTable(null);
137191
- setServiceStatus(projectTranslateServiceStatus({
137192
- enabled: config?.enabled ?? false,
137193
- hasSource: markdown.length > 0,
137194
- engineId: config?.engineId ?? "browser"
137195
- }));
137196
- return () => {
137197
- disposed = true;
137198
- };
137199
- }
137200
- if (config.engineId === "local") {
137201
- setCapability(null);
137202
- setBrowserSupportTable(null);
137203
- setServiceStatus(projectTranslateServiceStatus({
137204
- enabled: config.enabled,
137205
- hasSource: markdown.length > 0,
137206
- engineId: "local",
137207
- localModel: config.engines.local.model,
137208
- localSelectedGroupId: config.engines.local.selectedGroupId,
137209
- localAssetLoading: true
137210
- }));
137211
- const model = config.engines.local.model?.trim();
137212
- if (!model) {
137213
- setServiceStatus(projectTranslateServiceStatus({
137214
- enabled: config.enabled,
137215
- hasSource: markdown.length > 0,
137216
- engineId: "local",
137217
- localModel: model,
137218
- localSelectedGroupId: config.engines.local.selectedGroupId
137219
- }));
137220
- return () => {
137221
- disposed = true;
137222
- };
137223
- }
137224
- trpcClient.localModels.state.query({
137225
- modelId: model,
137226
- selectedGroupId: config.engines.local.selectedGroupId
137227
- }).then((localAsset) => {
137228
- if (disposed) return;
137229
- setServiceStatus(projectTranslateServiceStatus({
137230
- enabled: config.enabled,
137231
- hasSource: markdown.length > 0,
137232
- engineId: "local",
137233
- localModel: model,
137234
- localSelectedGroupId: config.engines.local.selectedGroupId,
137235
- localAsset
137236
- }));
137237
- }).catch((assetError) => {
137238
- if (disposed) return;
137239
- setServiceStatus({
137240
- state: "unavailable",
137241
- engineId: "local",
137242
- message: assetError instanceof Error ? assetError.message : "Unable to check local model files."
137243
- });
137244
- });
137245
- return () => {
137246
- disposed = true;
137247
- };
137248
- }
137249
- if (config.engineId === "openai") {
137250
- setCapability(null);
137251
- setBrowserSupportTable(null);
137252
- setServiceStatus(projectTranslateServiceStatus({
137253
- enabled: config.enabled,
137254
- hasSource: markdown.length > 0,
137255
- engineId: "openai"
137256
- }));
137257
- return () => {
137258
- disposed = true;
137259
- };
137260
- }
137261
- const cachedTable = getBrowserSupportTableState(config.targetLanguage);
137262
- if (cachedTable) {
137263
- setBrowserSupportTable(cachedTable);
137264
- setServiceStatus(projectTranslateServiceStatus({
137265
- enabled: config.enabled,
137266
- hasSource: markdown.length > 0,
137267
- engineId: "browser",
137268
- browserSupportTable: cachedTable
137269
- }));
137270
- return () => {
137271
- disposed = true;
137272
- };
137273
- }
137274
- setServiceStatus(projectTranslateServiceStatus({
137275
- enabled: config.enabled,
137276
- hasSource: markdown.length > 0,
137277
- engineId: "browser",
137278
- browserSupportTable: {
137279
- state: "checking",
137280
- table: null,
137281
- message: "Checking browser translation pairs…"
137282
- }
137283
- }));
137284
137872
  const controller = new AbortController();
137285
- scanBrowserTranslationPairs(config.targetLanguage, {
137873
+ resolveTranslateServiceState({
137874
+ config,
137875
+ hasSource: markdown.length > 0,
137286
137876
  signal: controller.signal,
137287
- onProgress: (nextState) => {
137877
+ onUpdate: (nextState) => {
137288
137878
  if (disposed) return;
137289
- setBrowserSupportTable(nextState);
137290
- setServiceStatus(projectTranslateServiceStatus({
137291
- enabled: config.enabled,
137292
- hasSource: markdown.length > 0,
137293
- engineId: "browser",
137294
- browserSupportTable: nextState
137295
- }));
137879
+ setCapability(nextState.capability);
137880
+ setBrowserSupportTable(nextState.browserSupportTable);
137881
+ setServiceStatus(nextState.status);
137296
137882
  }
137297
137883
  }).then((nextState) => {
137298
137884
  if (disposed) return;
137299
- setBrowserSupportTable(nextState);
137300
- setServiceStatus(projectTranslateServiceStatus({
137301
- enabled: config.enabled,
137302
- hasSource: markdown.length > 0,
137303
- engineId: "browser",
137304
- browserSupportTable: nextState
137305
- }));
137306
- }).catch((probeError) => {
137885
+ setCapability(nextState.capability);
137886
+ setBrowserSupportTable(nextState.browserSupportTable);
137887
+ setServiceStatus(nextState.status);
137888
+ }).catch((stateError) => {
137307
137889
  if (disposed) return;
137308
- const nextCapability = {
137309
- availability: "error",
137310
- message: probeError instanceof Error ? probeError.message : "Unable to check translation support."
137311
- };
137312
- setCapability(nextCapability);
137313
- setServiceStatus(projectTranslateServiceStatus({
137314
- enabled: config.enabled,
137315
- hasSource: markdown.length > 0,
137316
- engineId: "browser",
137317
- browserCapability: nextCapability
137318
- }));
137890
+ setCapability(null);
137891
+ setBrowserSupportTable(null);
137892
+ setServiceStatus({
137893
+ state: "unavailable",
137894
+ engineId: config?.engineId ?? "browser",
137895
+ message: stateError instanceof Error ? stateError.message : "Unable to check translation service."
137896
+ });
137319
137897
  });
137320
137898
  return () => {
137321
137899
  disposed = true;
@@ -137333,6 +137911,8 @@ function useDocumentTranslation(markdown, config) {
137333
137911
  if (!config?.enabled) return;
137334
137912
  abortRef.current?.abort();
137335
137913
  const controller = new AbortController();
137914
+ const generationId = generationRef.current + 1;
137915
+ generationRef.current = generationId;
137336
137916
  abortRef.current = controller;
137337
137917
  setError(null);
137338
137918
  setStatus("initializing");
@@ -137343,27 +137923,19 @@ function useDocumentTranslation(markdown, config) {
137343
137923
  return;
137344
137924
  }
137345
137925
  if (config.engineId === "browser") {
137346
- const preferredRow = browserSupportTable?.table?.rows.find((row) => row.availability === "available") ?? browserSupportTable?.table?.rows.find((row) => row.availability === "downloading") ?? browserSupportTable?.table?.rows.find((row) => row.availability === "downloadable") ?? null;
137347
- if (!preferredRow) {
137348
- setError(serviceStatus.message);
137926
+ const nextState = prepareTranslateServiceRun({
137927
+ config,
137928
+ hasSource: markdown.length > 0,
137929
+ browserSupportTable
137930
+ });
137931
+ setCapability(nextState.capability);
137932
+ setBrowserSupportTable(nextState.browserSupportTable);
137933
+ setServiceStatus(nextState.status);
137934
+ if (nextState.status.state !== "ready") {
137935
+ setError(nextState.status.message);
137349
137936
  setStatus("unavailable");
137350
137937
  return;
137351
137938
  }
137352
- const nextCapability = {
137353
- availability: preferredRow.availability,
137354
- progress: preferredRow.progress,
137355
- message: preferredRow.message
137356
- };
137357
- setCapability(nextCapability);
137358
- const nextTable = patchBrowserSupportTableRow(config.targetLanguage, preferredRow, { message: void 0 });
137359
- setBrowserSupportTable(nextTable);
137360
- setServiceStatus(projectTranslateServiceStatus({
137361
- enabled: config.enabled,
137362
- hasSource: markdown.length > 0,
137363
- engineId: "browser",
137364
- browserSupportTable: nextTable,
137365
- browserCapability: nextCapability
137366
- }));
137367
137939
  }
137368
137940
  setStatus("translating");
137369
137941
  setResult({
@@ -137382,21 +137954,27 @@ function useDocumentTranslation(markdown, config) {
137382
137954
  write: (input) => trpcClient.translationCache.write.mutate(input)
137383
137955
  } : void 0
137384
137956
  }, (patch) => {
137385
- if (controller.signal.aborted || abortRef.current !== controller) return;
137957
+ if (controller.signal.aborted || abortRef.current !== controller || generationRef.current !== generationId) return;
137386
137958
  setResult((current) => applyDocumentTranslationPatch(current, patch, {
137387
137959
  displayMode: config.displayMode,
137388
137960
  targetLanguage: config.targetLanguage
137389
137961
  }));
137390
137962
  });
137391
- if (controller.signal.aborted) return;
137963
+ if (controller.signal.aborted || abortRef.current !== controller || generationRef.current !== generationId) return;
137964
+ const documentFailure = getDocumentTranslationFailureMessage(nextResult);
137392
137965
  setResult(nextResult);
137966
+ if (documentFailure) {
137967
+ setError(documentFailure);
137968
+ setStatus("error");
137969
+ return;
137970
+ }
137393
137971
  setStatus("translated");
137394
137972
  } catch (translationError) {
137395
- if (controller.signal.aborted) return;
137973
+ if (controller.signal.aborted || abortRef.current !== controller || generationRef.current !== generationId) return;
137396
137974
  setError(translationError instanceof Error ? translationError.message : "Translation failed.");
137397
137975
  setStatus("error");
137398
137976
  } finally {
137399
- if (abortRef.current === controller) abortRef.current = null;
137977
+ if (abortRef.current === controller && generationRef.current === generationId) abortRef.current = null;
137400
137978
  }
137401
137979
  }, [
137402
137980
  browserSupportTable,
@@ -137437,6 +138015,14 @@ function useDocumentTranslation(markdown, config) {
137437
138015
  reset
137438
138016
  };
137439
138017
  }
138018
+ function getDocumentTranslationFailureMessage(result) {
138019
+ const segments = (Array.isArray(result.segments) ? result.segments : []).filter((segment) => segment !== void 0);
138020
+ if (segments.length === 0) return null;
138021
+ if (segments.some((segment) => segment.status !== "error" && typeof segment.target === "string")) return null;
138022
+ const errors = segments.map((segment) => segment.status === "error" ? segment.error : void 0).filter((message) => typeof message === "string" && message.length > 0);
138023
+ if (errors.length === 0) return null;
138024
+ return errors[0] ?? "Translation failed.";
138025
+ }
137440
138026
  function applyDocumentTranslationPatch(current, patch, fallback) {
137441
138027
  const segments = [...current?.segments ?? []];
137442
138028
  segments[patch.segmentIndex] = patch.segment;
@@ -148221,7 +148807,8 @@ function stripOpenSpecHeadingKindFromTextNode(node, kind, state) {
148221
148807
  //#endregion
148222
148808
  //#region src/components/document-translation-action.tsx
148223
148809
  function useDocumentTranslationRenderPlugin({ markdown, translationConfig }) {
148224
- const resolvedTranslationConfig = (0, import_react.useMemo)(() => translationConfig === void 0 ? void 0 : DocumentTranslationConfigSchema.parse(translationConfig), [translationConfig]);
148810
+ const { data: globalSettings } = useGlobalSettingsSubscription();
148811
+ const resolvedTranslationConfig = (0, import_react.useMemo)(() => translationConfig === void 0 ? void 0 : DocumentTranslationConfigSchema.parse(resolveDocumentTranslationConfig(translationConfig, globalSettings)), [globalSettings, translationConfig]);
148225
148812
  const session = useDocumentTranslation(markdown ?? "", resolvedTranslationConfig);
148226
148813
  const canTranslate = resolvedTranslationConfig !== void 0 && typeof markdown === "string" && markdown.length > 0;
148227
148814
  const translationProjection = (0, import_react.useMemo)(() => createTranslationProjection(session.result), [session.result]);
@@ -148261,6 +148848,7 @@ function DocumentTranslationAction({ enabled, session }) {
148261
148848
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DocumentTranslationButton, {
148262
148849
  capability: session.capability,
148263
148850
  enabled,
148851
+ error: session.error,
148264
148852
  serviceStatus: session.serviceStatus,
148265
148853
  status: session.status,
148266
148854
  onActivate: () => {
@@ -148294,7 +148882,8 @@ function hashString(value) {
148294
148882
  }
148295
148883
  function createTranslationProjection(result) {
148296
148884
  if (!result) return { blockAnnotations: [] };
148297
- const segmentByOffset = new Map(result.segments.filter((segment) => segment.target).map((segment) => [segment.sourceStartOffset, segment]));
148885
+ const segments = Array.isArray(result.segments) ? result.segments : [];
148886
+ const segmentByOffset = new Map(segments.filter((segment) => segment.target).map((segment) => [segment.sourceStartOffset, segment]));
148298
148887
  return {
148299
148888
  headingProcessor: {
148300
148889
  name: "document-translation",
@@ -148316,7 +148905,7 @@ function createTranslationProjection(result) {
148316
148905
  return createTranslatedHeadingTransform(input, segment, result.displayMode);
148317
148906
  }
148318
148907
  },
148319
- blockAnnotations: result.segments.filter((segment) => segment.target && segment.kind !== "heading").map((segment) => ({
148908
+ blockAnnotations: segments.filter((segment) => segment.target && segment.kind !== "heading").map((segment) => ({
148320
148909
  sourceStartOffset: segment.sourceStartOffset,
148321
148910
  sourceKind: segment.sourceKind,
148322
148911
  className: result.displayMode === "direct" ? "document-translation-direct" : "document-translation-bilingual",
@@ -148415,28 +149004,31 @@ function sanitizeTranslatedProperties(properties) {
148415
149004
  }
148416
149005
  return nextProperties;
148417
149006
  }
148418
- function DocumentTranslationButton({ capability, enabled, serviceStatus, status, onActivate }) {
149007
+ function DocumentTranslationButton({ capability, enabled, error, serviceStatus, status, onActivate }) {
148419
149008
  const isServiceChecking = serviceStatus.state === "checking";
148420
149009
  const isServiceUnavailable = serviceStatus.state === "unavailable" || status === "unavailable";
148421
149010
  const isSettingsDisabled = !enabled;
148422
149011
  const isTranslated = status === "translated";
148423
149012
  const isBusy = status === "initializing" || status === "translating";
148424
- const ariaLabel = isServiceUnavailable ? "Translation unavailable" : isSettingsDisabled ? "Configure translation" : isServiceChecking ? "Checking translation" : isBusy ? "Cancel translation" : isTranslated ? "Show source" : "Translate";
148425
- const title = isServiceUnavailable ? serviceStatus.message ?? capability?.message ?? "Translation is unavailable." : isSettingsDisabled ? "Translation is disabled in settings." : ariaLabel;
148426
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
148427
- size: "icon-sm",
148428
- variant: "secondary",
148429
- disabled: isServiceUnavailable || isServiceChecking,
148430
- "aria-disabled": isSettingsDisabled ? true : void 0,
148431
- onClick: (event) => {
148432
- event.stopPropagation();
148433
- onActivate();
148434
- },
148435
- title,
148436
- "aria-label": ariaLabel,
148437
- "data-translation-action-state": isServiceUnavailable ? "unavailable" : isServiceChecking ? "checking" : isSettingsDisabled ? "settings-disabled" : isBusy ? "busy" : isTranslated ? "translated" : "ready",
148438
- className: isServiceUnavailable || isServiceChecking ? "border-border bg-muted text-muted-foreground disabled:border-border disabled:bg-muted disabled:text-muted-foreground" : isSettingsDisabled ? "border-border bg-muted/40 text-muted-foreground opacity-70" : isTranslated ? "border-primary bg-primary text-primary-foreground hover:bg-primary/90" : "border-primary text-primary hover:bg-primary/10",
148439
- children: isBusy || isServiceChecking ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : isServiceUnavailable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Languages, { className: "h-4 w-4" })
149013
+ const isError = status === "error";
149014
+ const ariaLabel = isServiceUnavailable ? "Translation unavailable" : isSettingsDisabled ? "Configure translation" : isServiceChecking ? "Checking translation" : isError ? "Retry translation" : isBusy ? "Cancel translation" : isTranslated ? "Show source" : "Translate";
149015
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
149016
+ content: isServiceUnavailable ? serviceStatus.message ?? capability?.message ?? "Translation is unavailable." : isSettingsDisabled ? "Translation is disabled in settings." : isError ? `${error ?? "Translation failed."} Click to retry.` : ariaLabel,
149017
+ delay: 0,
149018
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
149019
+ size: "icon-sm",
149020
+ variant: "secondary",
149021
+ disabled: isServiceUnavailable || isServiceChecking,
149022
+ "aria-disabled": isSettingsDisabled ? true : void 0,
149023
+ onClick: (event) => {
149024
+ event.stopPropagation();
149025
+ onActivate();
149026
+ },
149027
+ "aria-label": ariaLabel,
149028
+ "data-translation-action-state": isServiceUnavailable ? "unavailable" : isServiceChecking ? "checking" : isSettingsDisabled ? "settings-disabled" : isError ? "error" : isBusy ? "busy" : isTranslated ? "translated" : "ready",
149029
+ className: isServiceUnavailable || isServiceChecking ? "border-border bg-muted text-muted-foreground disabled:border-border disabled:bg-muted disabled:text-muted-foreground" : isSettingsDisabled ? "border-border bg-muted/40 text-muted-foreground opacity-70" : isError ? "border-amber-500/50 bg-amber-500/10 text-amber-600 hover:bg-amber-500/15 dark:text-amber-400" : isTranslated ? "border-primary bg-primary text-primary-foreground hover:bg-primary/90" : "border-primary text-primary hover:bg-primary/10",
149030
+ children: isBusy || isServiceChecking ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : isServiceUnavailable || isError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Languages, { className: "h-4 w-4" })
149031
+ })
148440
149032
  });
148441
149033
  }
148442
149034
  //#endregion
@@ -150398,89 +150990,6 @@ var viewerStyles = String.raw`
150398
150990
  /* MarkdownViewer keeps layout hooks local; shared ToC geometry lives in index.css. */
150399
150991
  `;
150400
150992
  //#endregion
150401
- //#region src/components/scroll-spy.ts
150402
- function hasVerticalScrollBehavior(overflowY) {
150403
- return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
150404
- }
150405
- function findVerticalScrollContainer(node, options = {}) {
150406
- const { allowNonScrollable = false } = options;
150407
- let current = node?.parentElement ?? null;
150408
- while (current) {
150409
- if (hasVerticalScrollBehavior(window.getComputedStyle(current).overflowY)) {
150410
- if (allowNonScrollable || current.scrollHeight > current.clientHeight) return current;
150411
- }
150412
- current = current.parentElement;
150413
- }
150414
- return null;
150415
- }
150416
- function scrollViewportBounds(root) {
150417
- if (root) {
150418
- const rect = root.getBoundingClientRect();
150419
- return {
150420
- top: rect.top,
150421
- bottom: rect.bottom
150422
- };
150423
- }
150424
- return {
150425
- top: 0,
150426
- bottom: window.innerHeight
150427
- };
150428
- }
150429
- function measureAvailableViewportHeight(node, root = findVerticalScrollContainer(node, { allowNonScrollable: true })) {
150430
- if (typeof window === "undefined" || !node) return null;
150431
- const nodeRect = node.getBoundingClientRect();
150432
- const viewport = scrollViewportBounds(root);
150433
- return Math.max(Math.floor(viewport.bottom - Math.max(nodeRect.top, viewport.top)), 0);
150434
- }
150435
- function useViewportConstrainedHeight({ target, enabled = true }) {
150436
- const [height, setHeight] = (0, import_react.useState)(null);
150437
- (0, import_react.useLayoutEffect)(() => {
150438
- if (!enabled || typeof window === "undefined") {
150439
- setHeight(null);
150440
- return;
150441
- }
150442
- if (!target) {
150443
- setHeight(null);
150444
- return;
150445
- }
150446
- let resizeObserver = null;
150447
- let scrollRoot = null;
150448
- let scrollTarget = window;
150449
- const setConstrainedHeight = (nextHeight) => {
150450
- setHeight((currentHeight) => currentHeight === nextHeight ? currentHeight : nextHeight);
150451
- };
150452
- const bindScrollRoot = (nextRoot) => {
150453
- if (scrollRoot === nextRoot) return;
150454
- scrollTarget.removeEventListener("scroll", handleUpdate);
150455
- if (resizeObserver && scrollRoot) resizeObserver.unobserve(scrollRoot);
150456
- scrollRoot = nextRoot;
150457
- scrollTarget = nextRoot ?? window;
150458
- scrollTarget.addEventListener("scroll", handleUpdate, { passive: true });
150459
- if (resizeObserver && scrollRoot) resizeObserver.observe(scrollRoot);
150460
- };
150461
- const handleUpdate = () => {
150462
- const nextRoot = findVerticalScrollContainer(target, { allowNonScrollable: true });
150463
- bindScrollRoot(nextRoot);
150464
- setConstrainedHeight(measureAvailableViewportHeight(target, nextRoot));
150465
- };
150466
- if (typeof ResizeObserver !== "undefined") {
150467
- resizeObserver = new ResizeObserver(() => {
150468
- handleUpdate();
150469
- });
150470
- resizeObserver.observe(target);
150471
- if (target.parentElement) resizeObserver.observe(target.parentElement);
150472
- }
150473
- handleUpdate();
150474
- window.addEventListener("resize", handleUpdate);
150475
- return () => {
150476
- window.removeEventListener("resize", handleUpdate);
150477
- scrollTarget.removeEventListener("scroll", handleUpdate);
150478
- resizeObserver?.disconnect();
150479
- };
150480
- }, [enabled, target]);
150481
- return height;
150482
- }
150483
- //#endregion
150484
150993
  //#region src/lib/file-preview.ts
150485
150994
  async function prepareEntityFilePreview(input) {
150486
150995
  if (isStaticMode()) return null;
@@ -150555,13 +151064,10 @@ function isPreviewOnlyFile(file) {
150555
151064
  }
150556
151065
  function resolveDefaultMode(file, inStaticMode) {
150557
151066
  if (!file) return "read";
151067
+ if (!inStaticMode && isFileEntry(file) && file.previewKind === "html") return "preview";
150558
151068
  if (!inStaticMode && isPreviewOnlyFile(file)) return "preview";
150559
151069
  return "read";
150560
151070
  }
150561
- function clampPreviewHeight(viewportHeight) {
150562
- if (viewportHeight == null) return 480;
150563
- return Math.max(320, Math.min(viewportHeight - 112, 920));
150564
- }
150565
151071
  function resolveRemotePreviewFrameStyle(frameHeight) {
150566
151072
  if (frameHeight == null) return void 0;
150567
151073
  return {
@@ -150601,7 +151107,7 @@ async function sharePreview(input) {
150601
151107
  }
150602
151108
  function PreviewPane({ file, preview, loading, error, className = "", frameHeight, isDarkMode }) {
150603
151109
  if (file.previewKind === "markdown") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150604
- className: `min-h-0 h-full flex-1 overflow-hidden ${className}`,
151110
+ className: `h-full min-h-0 flex-1 overflow-hidden ${className}`,
150605
151111
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownViewer, {
150606
151112
  markdown: file.content ?? "",
150607
151113
  path: file.path,
@@ -150621,7 +151127,7 @@ function PreviewPane({ file, preview, loading, error, className = "", frameHeigh
150621
151127
  children: "Preview unavailable."
150622
151128
  });
150623
151129
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150624
- className: `bg-background min-h-0 h-full overflow-hidden ${className}`,
151130
+ className: `bg-background h-full min-h-0 overflow-hidden ${className}`,
150625
151131
  style: resolveRemotePreviewFrameStyle(frameHeight),
150626
151132
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("iframe", {
150627
151133
  src: resolvePreviewFrameUrl(preview, isDarkMode),
@@ -150635,7 +151141,6 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150635
151141
  const isDarkMode = useDarkMode();
150636
151142
  const { data: files, isLoading, error } = archived ? useArchiveFilesSubscription(changeId) : useChangeFilesSubscription(changeId);
150637
151143
  const [selectedPath, setSelectedPath] = (0, import_react.useState)(null);
150638
- const [viewportNode, setViewportNode] = (0, import_react.useState)(null);
150639
151144
  const [mode, setMode] = (0, import_react.useState)("read");
150640
151145
  const [draftContent, setDraftContent] = (0, import_react.useState)({});
150641
151146
  const [savingPath, setSavingPath] = (0, import_react.useState)(null);
@@ -150644,10 +151149,6 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150644
151149
  const [previewErrorByPath, setPreviewErrorByPath] = (0, import_react.useState)({});
150645
151150
  const [previewMaximized, setPreviewMaximized] = (0, import_react.useState)(false);
150646
151151
  const [shareFeedback, setShareFeedback] = (0, import_react.useState)(null);
150647
- const viewportHeight = useViewportConstrainedHeight({
150648
- target: viewportNode,
150649
- enabled: viewportNode !== null
150650
- });
150651
151152
  const sortedEntries = (0, import_react.useMemo)(() => {
150652
151153
  if (providedFiles) return [...providedFiles];
150653
151154
  if (!files) return [];
@@ -150663,7 +151164,6 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150663
151164
  const readEnabled = !isPreviewOnlyFile(activeFile);
150664
151165
  const previewEnabled = !inStaticMode && canPreviewFile(activeFile);
150665
151166
  const hasDirtyDraft = !!activeFile && isTextLikeFile(activeFile) && activeDraft !== (activeFile.content ?? "");
150666
- const remotePreviewHeight = clampPreviewHeight(viewportHeight);
150667
151167
  (0, import_react.useEffect)(() => {
150668
151168
  if (!sortedEntries.length) {
150669
151169
  setSelectedPath(null);
@@ -150694,6 +151194,7 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150694
151194
  const nextDefaultMode = resolveDefaultMode(activeFile, inStaticMode);
150695
151195
  setMode((currentMode) => {
150696
151196
  if (currentMode === nextDefaultMode) return currentMode;
151197
+ if (currentMode === "read" && nextDefaultMode === "preview") return nextDefaultMode;
150697
151198
  if (currentMode === "edit" && editEnabled) return currentMode;
150698
151199
  if (currentMode === "preview" && previewEnabled) return currentMode;
150699
151200
  if (currentMode === "read" && readEnabled) return currentMode;
@@ -150788,13 +151289,12 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150788
151289
  };
150789
151290
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
150790
151291
  "data-tab-scroll-root": "true",
150791
- className: "scrollbar-thin scrollbar-track-transparent min-h-0 flex-1 overflow-auto",
151292
+ className: "min-h-0 flex-1 overflow-hidden",
150792
151293
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150793
- className: "pr-1",
151294
+ className: "h-full min-h-0 pr-1",
150794
151295
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150795
- ref: setViewportNode,
150796
- className: "flex min-h-0 flex-col",
150797
- style: viewportHeight != null ? { height: `${viewportHeight}px` } : void 0,
151296
+ "data-folder-viewport": "",
151297
+ className: "flex h-full min-h-0 flex-col overflow-hidden",
150798
151298
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileExplorer, {
150799
151299
  entries: sortedEntries,
150800
151300
  selectedPath,
@@ -150808,92 +151308,141 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150808
151308
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
150809
151309
  className: "flex min-h-0 flex-1 flex-col",
150810
151310
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
150811
- className: "border-border/60 bg-muted/20 flex flex-wrap items-center justify-between gap-3 border-b px-3 py-2",
151311
+ "data-folder-toolbar": "",
151312
+ className: "border-border/60 bg-muted/20 flex flex-wrap items-center gap-3 border-b px-3 py-2",
150812
151313
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonGroup, {
150813
151314
  value: mode,
150814
151315
  onChange: setMode,
151316
+ presentation: "icon-only",
151317
+ className: "min-w-0",
150815
151318
  options: [
150816
151319
  {
150817
151320
  value: "read",
150818
151321
  label: "Read",
151322
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollText, { className: "h-3.5 w-3.5" }),
151323
+ ariaLabel: "Read",
151324
+ tooltip: "Read",
150819
151325
  disabled: !readEnabled
150820
151326
  },
150821
151327
  {
150822
151328
  value: "edit",
150823
151329
  label: "Edit",
151330
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilePenLine, { className: "h-3.5 w-3.5" }),
151331
+ ariaLabel: "Edit",
151332
+ tooltip: "Edit",
150824
151333
  disabled: !editEnabled
150825
151334
  },
150826
151335
  {
150827
151336
  value: "preview",
150828
151337
  label: "Preview",
151338
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Eye, { className: "h-3.5 w-3.5" }),
151339
+ ariaLabel: "Preview",
151340
+ tooltip: "Preview",
150829
151341
  disabled: !previewEnabled
150830
151342
  }
150831
151343
  ]
150832
151344
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150833
- className: "flex items-center gap-2",
150834
- children: mode === "edit" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
150835
- variant: "secondary",
150836
- size: "sm",
150837
- disabled: !hasDirtyDraft,
150838
- onClick: () => {
150839
- if (!isTextLikeFile(activeFile)) return;
150840
- setDraftContent((current) => ({
150841
- ...current,
150842
- [activeFile.path]: activeFile.content ?? ""
150843
- }));
150844
- },
150845
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Undo2, { className: "h-3.5 w-3.5" }), "Revert"]
150846
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
150847
- variant: "primary",
150848
- size: "sm",
150849
- disabled: !hasDirtyDraft || savingPath === currentFile.path,
150850
- onClick: saveActiveDraft,
150851
- children: [savingPath === currentFile.path ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Save, { className: "h-3.5 w-3.5" }), "Save"]
150852
- })] }) : mode === "preview" && canPreviewRemote(activeFile) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
150853
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
151345
+ "data-folder-toolbar-actions": "",
151346
+ className: "ml-auto flex min-w-0 max-w-full flex-wrap items-center justify-end gap-2",
151347
+ children: mode === "edit" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151348
+ content: "Revert",
151349
+ delay: 0,
151350
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
150854
151351
  variant: "secondary",
150855
- size: "sm",
151352
+ size: "icon-sm",
151353
+ "aria-label": "Revert",
151354
+ title: "Revert",
151355
+ disabled: !hasDirtyDraft,
150856
151356
  onClick: () => {
150857
- setPreviewByPath((current) => {
150858
- const next = { ...current };
150859
- delete next[currentFile.path];
150860
- return next;
150861
- });
151357
+ if (!isTextLikeFile(activeFile)) return;
151358
+ setDraftContent((current) => ({
151359
+ ...current,
151360
+ [activeFile.path]: activeFile.content ?? ""
151361
+ }));
150862
151362
  },
150863
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCw, { className: "h-3.5 w-3.5" }), "Refresh"]
151363
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Undo2, { className: "h-3.5 w-3.5" })
151364
+ })
151365
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151366
+ content: "Save",
151367
+ delay: 0,
151368
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151369
+ variant: "primary",
151370
+ size: "icon-sm",
151371
+ "aria-label": "Save",
151372
+ title: "Save",
151373
+ disabled: !hasDirtyDraft || savingPath === currentFile.path,
151374
+ onClick: saveActiveDraft,
151375
+ children: savingPath === currentFile.path ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Save, { className: "h-3.5 w-3.5" })
151376
+ })
151377
+ })] }) : mode === "preview" && canPreviewRemote(activeFile) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
151378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151379
+ content: "Refresh",
151380
+ delay: 0,
151381
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151382
+ variant: "secondary",
151383
+ size: "icon-sm",
151384
+ "aria-label": "Refresh",
151385
+ title: "Refresh",
151386
+ onClick: () => {
151387
+ setPreviewByPath((current) => {
151388
+ const next = { ...current };
151389
+ delete next[currentFile.path];
151390
+ return next;
151391
+ });
151392
+ },
151393
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCw, { className: "h-3.5 w-3.5" })
151394
+ })
150864
151395
  }),
150865
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
150866
- variant: "secondary",
150867
- size: "sm",
150868
- onClick: () => {
150869
- setPreviewMaximized((current) => !current);
150870
- },
150871
- children: [previewMaximized ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minimize, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Expand, { className: "h-3.5 w-3.5" }), previewMaximized ? "Exit Maximize" : "Maximize"]
151396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151397
+ content: previewMaximized ? "Exit maximize" : "Maximize",
151398
+ delay: 0,
151399
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151400
+ variant: "secondary",
151401
+ size: "icon-sm",
151402
+ "aria-label": previewMaximized ? "Exit maximize" : "Maximize",
151403
+ title: previewMaximized ? "Exit maximize" : "Maximize",
151404
+ onClick: () => {
151405
+ setPreviewMaximized((current) => !current);
151406
+ },
151407
+ children: previewMaximized ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minimize, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Expand, { className: "h-3.5 w-3.5" })
151408
+ })
150872
151409
  }),
150873
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
150874
- variant: "secondary",
150875
- size: "sm",
150876
- disabled: !previewDownloadUrl,
150877
- onClick: () => {
150878
- if (!previewDownloadUrl) return;
150879
- triggerDownload(previewDownloadUrl, currentFile.path.split("/").pop() ?? "preview");
150880
- },
150881
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Download, { className: "h-3.5 w-3.5" }), "Download"]
151410
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151411
+ content: "Download",
151412
+ delay: 0,
151413
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151414
+ variant: "secondary",
151415
+ size: "icon-sm",
151416
+ "aria-label": "Download",
151417
+ title: "Download",
151418
+ disabled: !previewDownloadUrl,
151419
+ onClick: () => {
151420
+ if (!previewDownloadUrl) return;
151421
+ triggerDownload(previewDownloadUrl, currentFile.path.split("/").pop() ?? "preview");
151422
+ },
151423
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Download, { className: "h-3.5 w-3.5" })
151424
+ })
150882
151425
  }),
150883
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
150884
- variant: "secondary",
150885
- size: "sm",
150886
- disabled: !previewShareUrl,
150887
- onClick: () => {
150888
- if (!previewShareUrl) return;
150889
- sharePreview({
150890
- url: previewShareUrl,
150891
- title: currentFile.path
150892
- }).then((shared) => {
150893
- setShareFeedback(shared ? "shared" : "copied");
150894
- });
150895
- },
150896
- children: [shareFeedback === "shared" || shareFeedback === "copied" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Share2, { className: "h-3.5 w-3.5" }), shareFeedback === "shared" ? "Shared" : shareFeedback === "copied" ? "Copied" : "Share"]
151426
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151427
+ content: shareFeedback === "shared" ? "Shared" : shareFeedback === "copied" ? "Copied" : "Share",
151428
+ delay: 0,
151429
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151430
+ variant: "secondary",
151431
+ size: "icon-sm",
151432
+ "aria-label": shareFeedback === "shared" ? "Shared" : shareFeedback === "copied" ? "Copied" : "Share",
151433
+ title: shareFeedback === "shared" ? "Shared" : shareFeedback === "copied" ? "Copied" : "Share",
151434
+ disabled: !previewShareUrl,
151435
+ onClick: () => {
151436
+ if (!previewShareUrl) return;
151437
+ sharePreview({
151438
+ url: previewShareUrl,
151439
+ title: currentFile.path
151440
+ }).then((shared) => {
151441
+ setShareFeedback(shared ? "shared" : "copied");
151442
+ });
151443
+ },
151444
+ children: shareFeedback === "shared" || shareFeedback === "copied" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Share2, { className: "h-3.5 w-3.5" })
151445
+ })
150897
151446
  })
150898
151447
  ] }) : null
150899
151448
  })]
@@ -150907,7 +151456,6 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150907
151456
  preview,
150908
151457
  loading: previewLoadingPath === currentFile.path,
150909
151458
  error: previewError,
150910
- frameHeight: canPreviewRemote(currentFile) ? remotePreviewHeight : void 0,
150911
151459
  isDarkMode
150912
151460
  })
150913
151461
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileExplorerCodeEditor, {
@@ -150939,7 +151487,7 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150939
151487
  contentClassName: "px-3 py-3",
150940
151488
  maxHeight: "96vh",
150941
151489
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150942
- className: "flex h-[80vh] min-h-[420px] max-h-[88vh] min-w-0 flex-col overflow-hidden",
151490
+ className: "flex h-[80vh] max-h-[88vh] min-h-[420px] min-w-0 flex-col overflow-hidden",
150943
151491
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PreviewPane, {
150944
151492
  file: activeFile,
150945
151493
  preview,
@@ -151164,6 +151712,8 @@ function GlobContent({ changeId, artifact, translationConfig }) {
151164
151712
  }
151165
151713
  function ArtifactOutputViewer({ changeId, artifact }) {
151166
151714
  const { data: config } = useConfigSubscription();
151715
+ const { data: globalSettings } = useGlobalSettingsSubscription();
151716
+ const translationConfig = resolveDocumentTranslationConfig(config?.translation, globalSettings);
151167
151717
  if (artifact.files) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
151168
151718
  className: "flex min-h-0 flex-1 flex-col",
151169
151719
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -151171,25 +151721,26 @@ function ArtifactOutputViewer({ changeId, artifact }) {
151171
151721
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactFilesDocumentShell, {
151172
151722
  artifact,
151173
151723
  files: artifact.files,
151174
- translationConfig: config?.translation
151724
+ translationConfig
151175
151725
  })
151176
151726
  })
151177
151727
  });
151178
151728
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LiveArtifactOutputViewer, {
151179
151729
  changeId,
151180
151730
  artifact,
151181
- translationConfig: config?.translation
151731
+ translationConfig
151182
151732
  });
151183
151733
  }
151184
151734
  function ContentFallbackViewer({ fallback }) {
151185
151735
  const { data: config } = useConfigSubscription();
151736
+ const { data: globalSettings } = useGlobalSettingsSubscription();
151186
151737
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
151187
151738
  className: "flex min-h-0 flex-1 flex-col",
151188
151739
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
151189
151740
  className: "min-h-0 flex-1",
151190
151741
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FallbackDocumentShell, {
151191
151742
  fallback,
151192
- translationConfig: config?.translation
151743
+ translationConfig: resolveDocumentTranslationConfig(config?.translation, globalSettings)
151193
151744
  })
151194
151745
  })
151195
151746
  });
@@ -151786,6 +152337,89 @@ function renderAnsiLine(line) {
151786
152337
  return parts.length > 0 ? parts : line;
151787
152338
  }
151788
152339
  //#endregion
152340
+ //#region src/components/scroll-spy.ts
152341
+ function hasVerticalScrollBehavior(overflowY) {
152342
+ return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
152343
+ }
152344
+ function findVerticalScrollContainer(node, options = {}) {
152345
+ const { allowNonScrollable = false } = options;
152346
+ let current = node?.parentElement ?? null;
152347
+ while (current) {
152348
+ if (hasVerticalScrollBehavior(window.getComputedStyle(current).overflowY)) {
152349
+ if (allowNonScrollable || current.scrollHeight > current.clientHeight) return current;
152350
+ }
152351
+ current = current.parentElement;
152352
+ }
152353
+ return null;
152354
+ }
152355
+ function scrollViewportBounds(root) {
152356
+ if (root) {
152357
+ const rect = root.getBoundingClientRect();
152358
+ return {
152359
+ top: rect.top,
152360
+ bottom: rect.bottom
152361
+ };
152362
+ }
152363
+ return {
152364
+ top: 0,
152365
+ bottom: window.innerHeight
152366
+ };
152367
+ }
152368
+ function measureAvailableViewportHeight(node, root = findVerticalScrollContainer(node, { allowNonScrollable: true })) {
152369
+ if (typeof window === "undefined" || !node) return null;
152370
+ const nodeRect = node.getBoundingClientRect();
152371
+ const viewport = scrollViewportBounds(root);
152372
+ return Math.max(Math.floor(viewport.bottom - Math.max(nodeRect.top, viewport.top)), 0);
152373
+ }
152374
+ function useViewportConstrainedHeight({ target, enabled = true }) {
152375
+ const [height, setHeight] = (0, import_react.useState)(null);
152376
+ (0, import_react.useLayoutEffect)(() => {
152377
+ if (!enabled || typeof window === "undefined") {
152378
+ setHeight(null);
152379
+ return;
152380
+ }
152381
+ if (!target) {
152382
+ setHeight(null);
152383
+ return;
152384
+ }
152385
+ let resizeObserver = null;
152386
+ let scrollRoot = null;
152387
+ let scrollTarget = window;
152388
+ const setConstrainedHeight = (nextHeight) => {
152389
+ setHeight((currentHeight) => currentHeight === nextHeight ? currentHeight : nextHeight);
152390
+ };
152391
+ const bindScrollRoot = (nextRoot) => {
152392
+ if (scrollRoot === nextRoot) return;
152393
+ scrollTarget.removeEventListener("scroll", handleUpdate);
152394
+ if (resizeObserver && scrollRoot) resizeObserver.unobserve(scrollRoot);
152395
+ scrollRoot = nextRoot;
152396
+ scrollTarget = nextRoot ?? window;
152397
+ scrollTarget.addEventListener("scroll", handleUpdate, { passive: true });
152398
+ if (resizeObserver && scrollRoot) resizeObserver.observe(scrollRoot);
152399
+ };
152400
+ const handleUpdate = () => {
152401
+ const nextRoot = findVerticalScrollContainer(target, { allowNonScrollable: true });
152402
+ bindScrollRoot(nextRoot);
152403
+ setConstrainedHeight(measureAvailableViewportHeight(target, nextRoot));
152404
+ };
152405
+ if (typeof ResizeObserver !== "undefined") {
152406
+ resizeObserver = new ResizeObserver(() => {
152407
+ handleUpdate();
152408
+ });
152409
+ resizeObserver.observe(target);
152410
+ if (target.parentElement) resizeObserver.observe(target.parentElement);
152411
+ }
152412
+ handleUpdate();
152413
+ window.addEventListener("resize", handleUpdate);
152414
+ return () => {
152415
+ window.removeEventListener("resize", handleUpdate);
152416
+ scrollTarget.removeEventListener("scroll", handleUpdate);
152417
+ resizeObserver?.disconnect();
152418
+ };
152419
+ }, [enabled, target]);
152420
+ return height;
152421
+ }
152422
+ //#endregion
151789
152423
  //#region ../../node_modules/.pnpm/@base-ui+utils@0.2.6_@types+react@19.2.7_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@base-ui/utils/esm/useControlled.js
151790
152424
  function useControlled({ controlled, default: defaultProp, name, state = "value" }) {
151791
152425
  const { current: isControlled } = import_react.useRef(controlled !== void 0);
@@ -158633,6 +159267,8 @@ function SpecView() {
158633
159267
  const { data: spec, isLoading } = useSpecSubscription(specId);
158634
159268
  const { data: rawMarkdown, isLoading: isRawLoading } = useSpecRawSubscription(specId);
158635
159269
  const { data: config } = useConfigSubscription();
159270
+ const { data: globalSettings } = useGlobalSettingsSubscription();
159271
+ const translationConfig = (0, import_react.useMemo)(() => resolveDocumentTranslationConfig(config?.translation, globalSettings), [config?.translation, globalSettings]);
158636
159272
  const validation = null;
158637
159273
  if (isLoading && !spec || isRawLoading && !rawMarkdown) {
158638
159274
  if (handoff) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -158678,7 +159314,7 @@ function SpecView() {
158678
159314
  spec,
158679
159315
  rawMarkdown: rawMarkdown ?? "",
158680
159316
  validation,
158681
- translationConfig: config?.translation
159317
+ translationConfig
158682
159318
  });
158683
159319
  }
158684
159320
  function SpecContent({ spec, rawMarkdown, validation, translationConfig }) {