@openspecui/web 3.11.0 → 3.11.2

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 (70) hide show
  1. package/dist/assets/CanvasRenderer-C0z1_uAq.js +1 -0
  2. package/dist/assets/WebGLRenderer-BgB0b-_m.js +1 -0
  3. package/dist/assets/WebGPURenderer-DUOyTSOY.js +1 -0
  4. package/dist/assets/browserAll-DrkY7ahm.js +1 -0
  5. package/dist/assets/{dist-D0kk4OnI.js → dist-B9Ltz3Au.js} +1 -1
  6. package/dist/assets/dist-BLzDSe4i.js +1 -0
  7. package/dist/assets/{dist-D7t0ICkd.js → dist-B_UFDOda.js} +1 -1
  8. package/dist/assets/{dist-B_uzjHQN.js → dist-BggZ7cyQ.js} +1 -1
  9. package/dist/assets/dist-C2tzK2BJ.js +1 -0
  10. package/dist/assets/{dist-CRlC9_e7.js → dist-DHhbIMeO.js} +1 -1
  11. package/dist/assets/{dist-CcLC6JdJ.js → dist-DRc-odji.js} +1 -1
  12. package/dist/assets/{dist-CjSC6Iz-.js → dist-Drgp5ero.js} +1 -1
  13. package/dist/assets/{dist-Deq3d3tg.js → dist-HIi-Q3U-.js} +1 -1
  14. package/dist/assets/dist-LSzi3-xz.js +1 -0
  15. package/dist/assets/{dist-Co2y3Dp-.js → dist-j6cthCRX.js} +1 -1
  16. package/dist/assets/{dist-DSHRZu7v.js → dist-wEqYYKag.js} +1 -1
  17. package/dist/assets/{init-CXvIgPy6.js → init-D41cyy2z.js} +1 -1
  18. package/dist/assets/main-DpM-T-Pk.js +1654 -0
  19. package/dist/assets/main-fCQ7khWW.css +1 -0
  20. package/dist/assets/trpc-lJhbxi32.js +1 -0
  21. package/dist/assets/webworkerAll-DXrA-UNG.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-pScAgtDS.js +1 -0
  25. package/dist-ssg/client/assets/WebGLRenderer-BAJXWFrw.js +1 -0
  26. package/dist-ssg/client/assets/WebGPURenderer-BeNNGqxY.js +1 -0
  27. package/dist-ssg/client/assets/browserAll-CkiE7Jmg.js +1 -0
  28. package/dist-ssg/client/assets/{dist-DYA7aYgJ.js → dist-BJ0zzLee.js} +1 -1
  29. package/dist-ssg/client/assets/dist-BJBQISim.js +1 -0
  30. package/dist-ssg/client/assets/dist-BPi1lJ-H.js +1 -0
  31. package/dist-ssg/client/assets/{dist-B0rNAJNr.js → dist-BZ35ju6s.js} +1 -1
  32. package/dist-ssg/client/assets/{dist-nDVKH9rz.js → dist-Bg-C799P.js} +1 -1
  33. package/dist-ssg/client/assets/dist-CNPnfIVE.js +1 -0
  34. package/dist-ssg/client/assets/{dist-Ct0osEG9.js → dist-CrdkjkLH.js} +1 -1
  35. package/dist-ssg/client/assets/{dist-CIDagwl8.js → dist-CuFabXJ8.js} +1 -1
  36. package/dist-ssg/client/assets/{dist-B_e3vKVI.js → dist-CzScJNYz.js} +1 -1
  37. package/dist-ssg/client/assets/{dist-C8h2jqYd.js → dist-lNUMJW4A.js} +1 -1
  38. package/dist-ssg/client/assets/{dist-zooxroSS.js → dist-nDBVtvCj.js} +1 -1
  39. package/dist-ssg/client/assets/{dist-xlAWlzHU.js → dist-xprslEpB.js} +1 -1
  40. package/dist-ssg/client/assets/{ghostty-web-MvkhtIaS.js → ghostty-web-D9IXftv-.js} +1 -1
  41. package/dist-ssg/client/assets/index-BtGAsAtP.css +1 -0
  42. package/dist-ssg/client/assets/index.ssg-L1BBuH-7.js +1624 -0
  43. package/dist-ssg/client/assets/{init-CbwghP3k.js → init-C058TQcq.js} +1 -1
  44. package/dist-ssg/client/assets/trpc-BTuQHFNd.js +1 -0
  45. package/dist-ssg/client/assets/webworkerAll-ByOBLjhR.js +1 -0
  46. package/dist-ssg/client/index.ssg.html +2 -2
  47. package/dist-ssg/server/entry-server.js +1261 -468
  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-DjDAttzm.js +0 -1646
  57. package/dist/assets/main-Dyvh_8NL.css +0 -1
  58. package/dist/assets/trpc-CSBEzIgb.js +0 -1
  59. package/dist/assets/webworkerAll-DhUUBZ9J.js +0 -1
  60. package/dist-ssg/client/assets/CanvasRenderer-D3Q5_w9A.js +0 -1
  61. package/dist-ssg/client/assets/WebGLRenderer-D9oc2qKY.js +0 -1
  62. package/dist-ssg/client/assets/WebGPURenderer-C5zSqQBE.js +0 -1
  63. package/dist-ssg/client/assets/browserAll-CIBhnrSW.js +0 -1
  64. package/dist-ssg/client/assets/dist-BSCnmcZh.js +0 -1
  65. package/dist-ssg/client/assets/dist-C1zSmOmx.js +0 -1
  66. package/dist-ssg/client/assets/dist-CXNNfjwS.js +0 -1
  67. package/dist-ssg/client/assets/index-D2aBuJqY.css +0 -1
  68. package/dist-ssg/client/assets/index.ssg-Dp15bsQU.js +0 -1616
  69. package/dist-ssg/client/assets/trpc-BccI0xyZ.js +0 -1
  70. 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;
@@ -32295,10 +32295,18 @@ ZodPipeline.create;
32295
32295
  var TranslationEngineIdSchema = enumType([
32296
32296
  "browser",
32297
32297
  "local",
32298
+ "local-ct2",
32298
32299
  "openai"
32299
32300
  ]);
32300
32301
  var DEFAULT_TRANSLATION_ENGINE_ID = "browser";
32301
- enumType(["local", "openai"]);
32302
+ function isManagedLocalTranslationEngineId(engineId) {
32303
+ return engineId === "local" || engineId === "local-ct2";
32304
+ }
32305
+ enumType([
32306
+ "local",
32307
+ "local-ct2",
32308
+ "openai"
32309
+ ]);
32302
32310
  var LocalModelDownloadStatusSchema = enumType([
32303
32311
  "not-downloaded",
32304
32312
  "queued",
@@ -32311,7 +32319,11 @@ var LocalModelDownloadStatusSchema = enumType([
32311
32319
  var TranslationDownloadFilePlanSchema = objectType({
32312
32320
  path: stringType().min(1),
32313
32321
  sizeBytes: numberType().int().nonnegative().optional(),
32314
- required: booleanType()
32322
+ required: booleanType(),
32323
+ etag: stringType().min(1).optional(),
32324
+ revision: stringType().min(1).optional(),
32325
+ sourceUrl: stringType().min(1).optional(),
32326
+ raw: unknownType().optional()
32315
32327
  });
32316
32328
  var TranslationDownloadGroupPlanSchema = objectType({
32317
32329
  id: stringType().min(1),
@@ -32320,14 +32332,97 @@ var TranslationDownloadGroupPlanSchema = objectType({
32320
32332
  profile: stringType().min(1).optional(),
32321
32333
  dtype: stringType().min(1).optional(),
32322
32334
  estimatedTotalBytes: numberType().int().nonnegative().optional(),
32335
+ baseGroupId: stringType().min(1).optional(),
32336
+ commitHash: stringType().min(1).optional(),
32337
+ shortCommitHash: stringType().min(1).optional(),
32338
+ rootDir: stringType().min(1).optional(),
32339
+ status: LocalModelDownloadStatusSchema.optional(),
32340
+ progress: numberType().min(0).max(1).optional(),
32341
+ bytesDownloaded: numberType().int().nonnegative().optional(),
32342
+ totalBytes: numberType().int().nonnegative().optional(),
32343
+ resumable: booleanType().optional(),
32344
+ error: stringType().optional(),
32323
32345
  selectable: booleanType(),
32324
32346
  selected: booleanType(),
32325
32347
  files: arrayType(TranslationDownloadFilePlanSchema)
32326
32348
  });
32349
+ var LocalModelProfileStatusSchema = enumType([
32350
+ "idle",
32351
+ "loading",
32352
+ "ready",
32353
+ "error"
32354
+ ]);
32355
+ var LocalModelProfileManifestFileSchema = objectType({
32356
+ path: stringType().min(1),
32357
+ sizeBytes: numberType().int().nonnegative().optional(),
32358
+ required: booleanType(),
32359
+ etag: stringType().min(1).optional(),
32360
+ revision: stringType().min(1).optional(),
32361
+ sourceUrl: stringType().min(1).optional(),
32362
+ raw: unknownType().optional()
32363
+ });
32364
+ var LocalModelProfileManifestGroupSchema = objectType({
32365
+ id: stringType().min(1),
32366
+ baseGroupId: stringType().min(1),
32367
+ label: stringType().min(1),
32368
+ displayLabel: stringType().min(1),
32369
+ description: stringType().optional(),
32370
+ profile: stringType().min(1).optional(),
32371
+ dtype: stringType().min(1).optional(),
32372
+ commitHash: stringType().min(1),
32373
+ shortCommitHash: stringType().min(1),
32374
+ rootDir: stringType().min(1),
32375
+ estimatedTotalBytes: numberType().int().nonnegative().optional(),
32376
+ selectable: booleanType(),
32377
+ files: arrayType(LocalModelProfileManifestFileSchema)
32378
+ });
32379
+ var LocalModelProfileManifestSchema = objectType({
32380
+ modelId: stringType().min(1),
32381
+ source: literalType("huggingface"),
32382
+ endpoint: stringType().default(""),
32383
+ revision: stringType().min(1),
32384
+ commitHash: stringType().min(1),
32385
+ shortCommitHash: stringType().min(1),
32386
+ fetchedAt: numberType().int().nonnegative(),
32387
+ updatedAt: numberType().int().nonnegative(),
32388
+ raw: unknownType().optional(),
32389
+ groups: recordType(stringType(), LocalModelProfileManifestGroupSchema).default({}),
32390
+ groupOrder: arrayType(stringType().min(1)).default([])
32391
+ });
32392
+ var LocalModelLifecycleFileStateSchema = objectType({
32393
+ path: stringType().min(1),
32394
+ sizeBytes: numberType().int().nonnegative().optional(),
32395
+ downloadedBytes: numberType().int().nonnegative().optional(),
32396
+ required: booleanType().default(true),
32397
+ status: LocalModelDownloadStatusSchema.default("not-downloaded"),
32398
+ updatedAt: numberType().int().nonnegative().optional(),
32399
+ error: stringType().optional()
32400
+ });
32401
+ var LocalModelLifecycleGroupStateSchema = objectType({
32402
+ groupId: stringType().min(1),
32403
+ baseGroupId: stringType().min(1).optional(),
32404
+ status: LocalModelDownloadStatusSchema.default("not-downloaded"),
32405
+ rootDir: stringType().min(1).optional(),
32406
+ bytesDownloaded: numberType().int().nonnegative().optional(),
32407
+ totalBytes: numberType().int().nonnegative().optional(),
32408
+ progress: numberType().min(0).max(1).optional(),
32409
+ resumable: booleanType().default(false),
32410
+ error: stringType().optional(),
32411
+ installedAt: numberType().int().nonnegative().optional(),
32412
+ updatedAt: numberType().int().nonnegative().optional(),
32413
+ files: arrayType(LocalModelLifecycleFileStateSchema).default([])
32414
+ });
32415
+ var LocalModelProfileLoadStateSchema = objectType({
32416
+ status: LocalModelProfileStatusSchema.default("idle"),
32417
+ message: stringType().optional(),
32418
+ error: stringType().optional(),
32419
+ updatedAt: numberType().int().nonnegative().optional()
32420
+ });
32327
32421
  objectType({
32328
- engineId: literalType("local"),
32422
+ engineId: enumType(["local", "local-ct2"]),
32329
32423
  modelId: stringType().min(1),
32330
32424
  selectedGroupId: stringType().min(1).optional(),
32425
+ groupId: stringType().min(1).optional(),
32331
32426
  status: LocalModelDownloadStatusSchema,
32332
32427
  message: stringType(),
32333
32428
  progress: numberType().min(0).max(1).optional(),
@@ -32352,8 +32447,10 @@ var LocalModelAssetPlanSnapshotSchema = objectType({
32352
32447
  });
32353
32448
  objectType({
32354
32449
  modelId: stringType().min(1),
32450
+ version: literalType(2).default(2),
32355
32451
  status: LocalModelDownloadStatusSchema.default("not-downloaded"),
32356
32452
  selected: booleanType().default(false),
32453
+ selectedGroupId: stringType().min(1).optional(),
32357
32454
  installedAt: numberType().int().nonnegative().optional(),
32358
32455
  updatedAt: numberType().int().nonnegative().optional(),
32359
32456
  bytesDownloaded: numberType().int().nonnegative().optional(),
@@ -32361,6 +32458,9 @@ objectType({
32361
32458
  progress: numberType().min(0).max(1).optional(),
32362
32459
  resumable: booleanType().default(false),
32363
32460
  error: stringType().optional(),
32461
+ profileLoad: LocalModelProfileLoadStateSchema.default(LocalModelProfileLoadStateSchema.parse({})),
32462
+ profileManifest: LocalModelProfileManifestSchema.optional(),
32463
+ groupsState: recordType(stringType(), LocalModelLifecycleGroupStateSchema).default({}),
32364
32464
  plan: LocalModelAssetPlanSnapshotSchema.optional(),
32365
32465
  files: arrayType(objectType({
32366
32466
  path: stringType().min(1),
@@ -32368,6 +32468,72 @@ objectType({
32368
32468
  downloadedBytes: numberType().int().nonnegative().optional()
32369
32469
  })).default([])
32370
32470
  });
32471
+ var TranslationEngineDependencyStateSchema = enumType([
32472
+ "installed",
32473
+ "installing",
32474
+ "missing",
32475
+ "error",
32476
+ "not-applicable"
32477
+ ]);
32478
+ var TranslationEngineRuntimeStateSchema = enumType([
32479
+ "ready",
32480
+ "probing",
32481
+ "failed",
32482
+ "error",
32483
+ "not-applicable"
32484
+ ]);
32485
+ var TranslationEngineAssetStateSchema = enumType([
32486
+ "ready",
32487
+ "missing",
32488
+ "downloading",
32489
+ "error",
32490
+ "not-applicable"
32491
+ ]);
32492
+ var TranslationEngineLifecyclePhaseMetaSchema = objectType({
32493
+ message: stringType().optional(),
32494
+ progress: numberType().min(0).max(1).optional(),
32495
+ error: stringType().optional()
32496
+ });
32497
+ var TranslationEngineLifecycleStatusSchema = objectType({
32498
+ dependency: TranslationEngineLifecyclePhaseMetaSchema.extend({ state: TranslationEngineDependencyStateSchema }),
32499
+ runtime: TranslationEngineLifecyclePhaseMetaSchema.extend({ state: TranslationEngineRuntimeStateSchema }),
32500
+ assets: TranslationEngineLifecyclePhaseMetaSchema.extend({ state: TranslationEngineAssetStateSchema }),
32501
+ summary: stringType().optional()
32502
+ });
32503
+ function isTranslationEngineDependencyReady(status) {
32504
+ return status.dependency.state === "installed" || status.dependency.state === "not-applicable";
32505
+ }
32506
+ function isTranslationEngineRuntimeReady(status) {
32507
+ return status.runtime.state === "ready" || status.runtime.state === "not-applicable";
32508
+ }
32509
+ function shouldShowTranslationEngineInstallGate(status) {
32510
+ if (!status) return false;
32511
+ return !isTranslationEngineDependencyReady(status) || !isTranslationEngineRuntimeReady(status);
32512
+ }
32513
+ function getTranslationEngineLifecycleMessage(status) {
32514
+ if (!status) return void 0;
32515
+ return status.summary ?? status.runtime.error ?? status.runtime.message ?? status.dependency.error ?? status.dependency.message ?? status.assets.error ?? status.assets.message;
32516
+ }
32517
+ var TranslationEngineInstallLogStreamSchema = enumType(["stdout", "stderr"]);
32518
+ objectType({
32519
+ stream: TranslationEngineInstallLogStreamSchema,
32520
+ text: stringType()
32521
+ });
32522
+ discriminatedUnionType("type", [
32523
+ objectType({
32524
+ type: literalType("status"),
32525
+ lifecycle: TranslationEngineLifecycleStatusSchema
32526
+ }),
32527
+ objectType({
32528
+ type: literalType("log"),
32529
+ stream: TranslationEngineInstallLogStreamSchema,
32530
+ text: stringType()
32531
+ }),
32532
+ objectType({
32533
+ type: literalType("exit"),
32534
+ lifecycle: TranslationEngineLifecycleStatusSchema
32535
+ })
32536
+ ]);
32371
32537
  var TranslationOpenAISettingsSchema = objectType({
32372
32538
  baseUrl: stringType().default(""),
32373
32539
  token: stringType().default(""),
@@ -32378,9 +32544,15 @@ var TranslationLocalSettingsSchema = objectType({
32378
32544
  selectedGroupId: stringType().optional(),
32379
32545
  hfEndpoint: stringType().default("")
32380
32546
  });
32547
+ var TranslationLocalCt2SettingsSchema = objectType({
32548
+ model: stringType().default("ooeoeo/opus-mt-en-zh-ct2-float16"),
32549
+ selectedGroupId: stringType().optional(),
32550
+ hfEndpoint: stringType().default("")
32551
+ });
32381
32552
  objectType({
32382
32553
  openai: TranslationOpenAISettingsSchema.default(TranslationOpenAISettingsSchema.parse({})),
32383
- local: TranslationLocalSettingsSchema.default(TranslationLocalSettingsSchema.parse({}))
32554
+ local: TranslationLocalSettingsSchema.default(TranslationLocalSettingsSchema.parse({})),
32555
+ localCt2: TranslationLocalCt2SettingsSchema.default(TranslationLocalCt2SettingsSchema.parse({}))
32384
32556
  });
32385
32557
  objectType({
32386
32558
  engineId: TranslationEngineIdSchema,
@@ -32402,6 +32574,10 @@ var TranslationEngineProjectSettingsSchema = objectType({
32402
32574
  model: stringType().min(1).optional(),
32403
32575
  selectedGroupId: stringType().min(1).optional()
32404
32576
  }).default({}),
32577
+ localCt2: objectType({
32578
+ model: stringType().min(1).optional(),
32579
+ selectedGroupId: stringType().min(1).optional()
32580
+ }).default({}),
32405
32581
  openai: objectType({ model: stringType().min(1).optional() }).default({})
32406
32582
  }).default({});
32407
32583
  var DocumentTranslationConfigSchema = objectType({
@@ -42699,6 +42875,12 @@ function useConfigSubscription() {
42699
42875
  onError: callbacks.onError
42700
42876
  }), getConfig$1, [], "config.subscribe");
42701
42877
  }
42878
+ function useGlobalSettingsSubscription() {
42879
+ return useSubscription((callbacks) => trpcClient.globalSettings.subscribe.subscribe(void 0, {
42880
+ onData: callbacks.onData,
42881
+ onError: callbacks.onError
42882
+ }), void 0, [], "globalSettings.subscribe");
42883
+ }
42702
42884
  //#endregion
42703
42885
  //#region src/lib/use-opsx.ts
42704
42886
  function getOpsxStatusSubscriptionCacheKey(input) {
@@ -44002,6 +44184,15 @@ var Expand = createLucideIcon("Expand", [
44002
44184
  key: "u9ee12"
44003
44185
  }]
44004
44186
  ]);
44187
+ var Eye = createLucideIcon("Eye", [["path", {
44188
+ 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",
44189
+ key: "1nclc0"
44190
+ }], ["circle", {
44191
+ cx: "12",
44192
+ cy: "12",
44193
+ r: "3",
44194
+ key: "1v7zrd"
44195
+ }]]);
44005
44196
  var FileCode2 = createLucideIcon("FileCode2", [
44006
44197
  ["path", {
44007
44198
  d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4",
@@ -44020,6 +44211,20 @@ var FileCode2 = createLucideIcon("FileCode2", [
44020
44211
  key: "112psh"
44021
44212
  }]
44022
44213
  ]);
44214
+ var FilePenLine = createLucideIcon("FilePenLine", [
44215
+ ["path", {
44216
+ 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",
44217
+ key: "142zxg"
44218
+ }],
44219
+ ["path", {
44220
+ 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",
44221
+ key: "2t3380"
44222
+ }],
44223
+ ["path", {
44224
+ d: "M8 18h1",
44225
+ key: "13wk12"
44226
+ }]
44227
+ ]);
44023
44228
  var FilePlus = createLucideIcon("FilePlus", [
44024
44229
  ["path", {
44025
44230
  d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",
@@ -44519,6 +44724,24 @@ var Save = createLucideIcon("Save", [
44519
44724
  key: "t51u73"
44520
44725
  }]
44521
44726
  ]);
44727
+ var ScrollText = createLucideIcon("ScrollText", [
44728
+ ["path", {
44729
+ d: "M15 12h-5",
44730
+ key: "r7krc0"
44731
+ }],
44732
+ ["path", {
44733
+ d: "M15 8h-5",
44734
+ key: "1khuty"
44735
+ }],
44736
+ ["path", {
44737
+ d: "M19 17V5a2 2 0 0 0-2-2H4",
44738
+ key: "zz82l3"
44739
+ }],
44740
+ ["path", {
44741
+ 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",
44742
+ key: "1ph1d7"
44743
+ }]
44744
+ ]);
44522
44745
  var Search = createLucideIcon("Search", [["circle", {
44523
44746
  cx: "11",
44524
44747
  cy: "11",
@@ -55153,8 +55376,12 @@ function inertValue(value) {
55153
55376
  //#region src/components/tooltip.tsx
55154
55377
  function Tooltip({ content, children, delay = 180, sideOffset = 8, className }) {
55155
55378
  if (!content) return children;
55379
+ const trigger = typeof children.props === "object" && children.props !== null && "disabled" in children.props && Boolean(children.props.disabled) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
55380
+ className: "inline-flex max-w-full",
55381
+ children
55382
+ }) : children;
55156
55383
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TooltipRoot, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
55157
- render: children,
55384
+ render: trigger,
55158
55385
  delay
55159
55386
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipPositioner, {
55160
55387
  sideOffset,
@@ -96609,20 +96836,29 @@ var Button = (0, import_react.forwardRef)(function Button({ variant = "primary",
96609
96836
  /**
96610
96837
  * Compact segmented buttons with single-select behavior.
96611
96838
  */
96612
- function ButtonGroup({ value, options, onChange, className = "", tone = "default" }) {
96839
+ function ButtonGroup({ value, options, onChange, className = "", tone = "default", presentation = "label" }) {
96613
96840
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
96614
96841
  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
96842
  children: options.map((option, index) => {
96616
96843
  const active = option.value === value;
96617
96844
  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", {
96845
+ const accessibleLabel = option.ariaLabel ?? (typeof option.label === "string" ? option.label : void 0);
96846
+ const tooltipContent = option.tooltip ?? accessibleLabel;
96847
+ 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;
96848
+ const button = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
96619
96849
  type: "button",
96620
96850
  disabled: option.disabled,
96621
96851
  onClick: () => onChange(option.value),
96622
96852
  "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
96853
+ "aria-label": presentation === "icon-only" ? accessibleLabel : void 0,
96854
+ title: presentation === "icon-only" && typeof accessibleLabel === "string" ? accessibleLabel : void 0,
96855
+ 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}`,
96856
+ children: content
96625
96857
  }, option.value);
96858
+ return tooltipContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
96859
+ content: tooltipContent,
96860
+ children: button
96861
+ }, option.value) : button;
96626
96862
  })
96627
96863
  });
96628
96864
  }
@@ -114041,6 +114277,7 @@ var layoutStyles = String.raw`
114041
114277
  display: flex;
114042
114278
  flex-direction: column;
114043
114279
  height: 100%;
114280
+ min-height: 0;
114044
114281
  gap: 0.75rem;
114045
114282
  }
114046
114283
  .fev-sidebar-tabs {
@@ -114048,6 +114285,7 @@ var layoutStyles = String.raw`
114048
114285
  }
114049
114286
  .fev-sidebar-tree {
114050
114287
  display: none;
114288
+ min-height: 0;
114051
114289
  }
114052
114290
  .fev-editor-wrapper {
114053
114291
  display: flex;
@@ -114061,8 +114299,10 @@ var layoutStyles = String.raw`
114061
114299
  .fev-layout {
114062
114300
  display: grid;
114063
114301
  grid-template-columns: minmax(0, 1fr) minmax(240px, clamp(240px, 30%, 420px));
114302
+ grid-template-rows: minmax(0, 1fr);
114064
114303
  gap: 1rem;
114065
114304
  min-height: 0;
114305
+ overflow: hidden;
114066
114306
  }
114067
114307
  .fev-sidebar-tabs {
114068
114308
  display: none;
@@ -114070,9 +114310,13 @@ var layoutStyles = String.raw`
114070
114310
  .fev-sidebar-tree {
114071
114311
  display: block;
114072
114312
  order: 2;
114313
+ height: 100%;
114314
+ min-height: 0;
114315
+ overflow: hidden;
114073
114316
  }
114074
114317
  .fev-editor-wrapper {
114075
114318
  order: 1;
114319
+ min-height: 0;
114076
114320
  }
114077
114321
  }
114078
114322
  .CodeMirror {
@@ -114250,7 +114494,8 @@ function FileTree({ entries, selectedPath, onSelect, headerLabel, headerActions,
114250
114494
  const closeMenu = () => setMenuState(null);
114251
114495
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuWrapper, {
114252
114496
  ref: wrapperRef,
114253
- className: "border-border bg-muted/30 flex h-full flex-col rounded-md border",
114497
+ className: "border-border bg-muted/30 flex h-full min-h-0 flex-col rounded-md border",
114498
+ "data-file-explorer-tree": "",
114254
114499
  children: [
114255
114500
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
114256
114501
  className: "border-border/50 text-muted-foreground flex items-center justify-between border-b px-3 py-2 text-xs font-medium",
@@ -114260,7 +114505,8 @@ function FileTree({ entries, selectedPath, onSelect, headerLabel, headerActions,
114260
114505
  }), headerActions]
114261
114506
  }),
114262
114507
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
114263
- className: "scrollbar-thin scrollbar-track-transparent flex-1 overflow-y-auto",
114508
+ "data-file-explorer-tree-scroll": "",
114509
+ className: "scrollbar-thin scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto",
114264
114510
  children: entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
114265
114511
  className: "text-muted-foreground px-3 py-2 text-xs",
114266
114512
  children: "No files yet."
@@ -114327,7 +114573,7 @@ function FileExplorer({ entries, selectedPath, onSelect, breadcrumbRoot, headerL
114327
114573
  return sortedEntries.find((entry) => entry.path === selectedPath && entry.type === "file") ?? null;
114328
114574
  }, [sortedEntries, selectedPath]);
114329
114575
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
114330
- className: "@container-[size] h-full",
114576
+ className: "@container-[size] h-full min-h-0 overflow-hidden",
114331
114577
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: layoutStyles }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
114332
114578
  className: "fev-layout",
114333
114579
  children: [
@@ -114487,6 +114733,38 @@ function useDocumentTranslationActivation() {
114487
114733
  };
114488
114734
  }
114489
114735
  //#endregion
114736
+ //#region src/lib/resolve-document-translation-config.ts
114737
+ function resolveDocumentTranslationConfig(translationConfig, globalSettings) {
114738
+ if (!translationConfig) return void 0;
114739
+ const local = translationConfig.engines?.local ?? {};
114740
+ const localCt2 = translationConfig.engines?.localCt2 ?? {};
114741
+ const openai = translationConfig.engines?.openai ?? {};
114742
+ const resolvedLocalModel = local.model ?? globalSettings?.translationEngines.local?.model;
114743
+ const resolvedLocalSelectedGroupId = local.selectedGroupId ?? globalSettings?.translationEngines.local?.selectedGroupId;
114744
+ const resolvedLocalCt2Model = localCt2.model ?? globalSettings?.translationEngines.localCt2?.model;
114745
+ const resolvedLocalCt2SelectedGroupId = localCt2.selectedGroupId ?? globalSettings?.translationEngines.localCt2?.selectedGroupId;
114746
+ const resolvedOpenAIModel = openai.model ?? globalSettings?.translationEngines.openai?.model;
114747
+ return {
114748
+ ...translationConfig,
114749
+ engines: {
114750
+ local: {
114751
+ ...local,
114752
+ ...resolvedLocalModel ? { model: resolvedLocalModel } : {},
114753
+ ...resolvedLocalSelectedGroupId ? { selectedGroupId: resolvedLocalSelectedGroupId } : {}
114754
+ },
114755
+ localCt2: {
114756
+ ...localCt2,
114757
+ ...resolvedLocalCt2Model ? { model: resolvedLocalCt2Model } : {},
114758
+ ...resolvedLocalCt2SelectedGroupId ? { selectedGroupId: resolvedLocalCt2SelectedGroupId } : {}
114759
+ },
114760
+ openai: {
114761
+ ...openai,
114762
+ ...resolvedOpenAIModel ? { model: resolvedOpenAIModel } : {}
114763
+ }
114764
+ }
114765
+ };
114766
+ }
114767
+ //#endregion
114490
114768
  //#region ../browser-translator/dist/index.mjs
114491
114769
  async function scanBrowserTranslationSupportTable(options) {
114492
114770
  const translator = (options.win ?? window).Translator;
@@ -131170,6 +131448,56 @@ function createProjectionContext(lookup, annotations, projections) {
131170
131448
  };
131171
131449
  }
131172
131450
  //#endregion
131451
+ //#region ../core/src/translation-language-pair.ts
131452
+ var OPUS_MT_DIRECTION_PATTERN = /^opus-mt-([a-z]{2,3})-([a-z]{2,3})$/i;
131453
+ var OPUS_MT_CT2_SUFFIX_PATTERN = /-(?:ct2|ctranslate2)(?:-[a-z0-9]+)*$/i;
131454
+ function inferLocalDirectionalModelLanguagePair(model) {
131455
+ const modelName = model?.trim().split("/").pop()?.replace(OPUS_MT_CT2_SUFFIX_PATTERN, "");
131456
+ if (!modelName) return null;
131457
+ const match = OPUS_MT_DIRECTION_PATTERN.exec(modelName);
131458
+ if (!match) return null;
131459
+ const [, sourceLanguage, targetLanguage] = match;
131460
+ if (!sourceLanguage || !targetLanguage) return null;
131461
+ return {
131462
+ sourceLanguage: normalizeLanguageCode(sourceLanguage),
131463
+ targetLanguage: normalizeLanguageCode(targetLanguage)
131464
+ };
131465
+ }
131466
+ function checkLocalDirectionalModelLanguagePair(input) {
131467
+ const expected = inferLocalDirectionalModelLanguagePair(input.model);
131468
+ if (!expected) return { supported: true };
131469
+ if (!areCompatibleLanguageTags(expected.targetLanguage, input.targetLanguage)) return {
131470
+ supported: false,
131471
+ expected,
131472
+ message: `Selected local model supports ${formatLanguagePair(expected)}, but document translation is configured for target ${input.targetLanguage}.`
131473
+ };
131474
+ if (input.sourceLanguage && !areCompatibleLanguageTags(expected.sourceLanguage, input.sourceLanguage)) return {
131475
+ supported: false,
131476
+ expected,
131477
+ message: `Selected local model supports ${formatLanguagePair(expected)}, but document segment was detected as ${input.sourceLanguage} -> ${input.targetLanguage}.`
131478
+ };
131479
+ return {
131480
+ supported: true,
131481
+ expected
131482
+ };
131483
+ }
131484
+ function areCompatibleLanguageTags(expected, actual) {
131485
+ const expectedNormalized = normalizeLanguageTag$1(expected);
131486
+ const actualNormalized = normalizeLanguageTag$1(actual);
131487
+ if (!expectedNormalized || !actualNormalized) return false;
131488
+ if (expectedNormalized === actualNormalized) return true;
131489
+ return expectedNormalized.split("-")[0] === actualNormalized.split("-")[0];
131490
+ }
131491
+ function normalizeLanguageCode(language) {
131492
+ return language.trim().toLowerCase().replace(/_/g, "-");
131493
+ }
131494
+ function normalizeLanguageTag$1(language) {
131495
+ return normalizeLanguageCode(language);
131496
+ }
131497
+ function formatLanguagePair(pair) {
131498
+ return `${pair.sourceLanguage} -> ${pair.targetLanguage}`;
131499
+ }
131500
+ //#endregion
131173
131501
  //#region ../../node_modules/.pnpm/remark-gfm@4.0.1/node_modules/remark-gfm/lib/index.js
131174
131502
  /**
131175
131503
  * @import {Root} from 'mdast'
@@ -135698,6 +136026,43 @@ function escapeAttributeValue(value) {
135698
136026
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
135699
136027
  }
135700
136028
  //#endregion
136029
+ //#region src/lib/translation-adaptive-concurrency-log.ts
136030
+ var GLOBAL_STORAGE_KEY = "__OPENSPECUI_TRANSLATION_ADAPTIVE_CONCURRENCY_LOGS__";
136031
+ var MAX_LOG_AGE_MS = 1800 * 1e3;
136032
+ var MAX_LOG_ENTRIES = 256;
136033
+ var DEFAULT_SAMPLE_SIZE = 8;
136034
+ function createTranslationAdaptiveConcurrencyScopeKey(input) {
136035
+ return JSON.stringify({
136036
+ engineId: input.engineId,
136037
+ engineVersion: input.engineVersion ?? null,
136038
+ model: input.model ?? null,
136039
+ selectedGroupId: input.selectedGroupId ?? null,
136040
+ sourceLanguage: input.sourceLanguage.trim().toLowerCase(),
136041
+ targetLanguage: input.targetLanguage.trim().toLowerCase(),
136042
+ translatorContractVersion: input.translatorContractVersion
136043
+ });
136044
+ }
136045
+ function appendTranslationAdaptiveConcurrencyLog(entry) {
136046
+ const store = getTranslationAdaptiveConcurrencyLogStore();
136047
+ store.entries.push(entry);
136048
+ cleanupTranslationAdaptiveConcurrencyLogs(store);
136049
+ }
136050
+ function readRecentTranslationAdaptiveConcurrencyLogs(input = {}) {
136051
+ const store = getTranslationAdaptiveConcurrencyLogStore();
136052
+ cleanupTranslationAdaptiveConcurrencyLogs(store);
136053
+ return (input.scopeKey ? store.entries.filter((entry) => entry.scopeKey === input.scopeKey) : store.entries).slice(-Math.max(1, input.limit ?? DEFAULT_SAMPLE_SIZE));
136054
+ }
136055
+ function getTranslationAdaptiveConcurrencyLogStore() {
136056
+ const globalScope = globalThis;
136057
+ if (!globalScope[GLOBAL_STORAGE_KEY]) globalScope[GLOBAL_STORAGE_KEY] = { entries: [] };
136058
+ return globalScope[GLOBAL_STORAGE_KEY];
136059
+ }
136060
+ function cleanupTranslationAdaptiveConcurrencyLogs(store) {
136061
+ const cutoff = Date.now() - MAX_LOG_AGE_MS;
136062
+ if (store.entries.length === 0) return;
136063
+ store.entries = store.entries.filter((entry) => entry.recordedAt >= cutoff).slice(-MAX_LOG_ENTRIES);
136064
+ }
136065
+ //#endregion
135701
136066
  //#region ../search/src/engine.ts
135702
136067
  function normalizeText(input) {
135703
136068
  return input.toLowerCase().replace(/\s+/g, " ").trim();
@@ -136148,7 +136513,7 @@ function createBrowserTranslationExecution() {
136148
136513
  factory: createBrowserTranslatorFactory(),
136149
136514
  cacheIdentity: {
136150
136515
  engineId: DEFAULT_TRANSLATION_ENGINE_ID,
136151
- translatorContractVersion: 2
136516
+ translatorContractVersion: 3
136152
136517
  }
136153
136518
  };
136154
136519
  }
@@ -136157,6 +136522,7 @@ var DOCUMENT_LANGUAGE_CONFIDENCE_THRESHOLD = .45;
136157
136522
  var SEGMENT_LANGUAGE_CONFIDENCE_THRESHOLD = .62;
136158
136523
  var TRANSLATION_DISPLAY_POLICY_VERSION = 2;
136159
136524
  var BROWSER_SOURCE_LANGUAGE_ORDER = new Map(SUPPORTED_TRANSLATION_LANGUAGES.map((language, index) => [language.code, index]));
136525
+ var SUPPORTED_TRANSLATION_LANGUAGE_CODES = new Set(SUPPORTED_TRANSLATION_LANGUAGES.map((language) => language.code));
136160
136526
  function isBrowserTranslationSupported() {
136161
136527
  return typeof window !== "undefined" && !!window.Translator;
136162
136528
  }
@@ -136374,14 +136740,20 @@ function mergeBrowserSupportRows(rows, row) {
136374
136740
  }
136375
136741
  function sortBrowserSupportRows(rows) {
136376
136742
  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;
136743
+ const leftOrder = getBrowserSourceLanguageOrder(left.sourceLanguage) ?? Number.MAX_SAFE_INTEGER;
136744
+ const rightOrder = getBrowserSourceLanguageOrder(right.sourceLanguage) ?? Number.MAX_SAFE_INTEGER;
136379
136745
  if (leftOrder !== rightOrder) return leftOrder - rightOrder;
136380
136746
  const targetDelta = left.targetLanguage.localeCompare(right.targetLanguage);
136381
136747
  if (targetDelta !== 0) return targetDelta;
136382
136748
  return left.sourceLanguage.localeCompare(right.sourceLanguage);
136383
136749
  });
136384
136750
  }
136751
+ function getBrowserSourceLanguageOrder(sourceLanguage) {
136752
+ return isSupportedTranslationLanguageCode(sourceLanguage) ? BROWSER_SOURCE_LANGUAGE_ORDER.get(sourceLanguage) : void 0;
136753
+ }
136754
+ function isSupportedTranslationLanguageCode(language) {
136755
+ return SUPPORTED_TRANSLATION_LANGUAGE_CODES.has(language);
136756
+ }
136385
136757
  async function translateMarkdownDocumentProgressively(args, onPatch) {
136386
136758
  const segments = extractTranslatableSegments(args.markdown);
136387
136759
  if (segments.length === 0) return {
@@ -136391,9 +136763,9 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136391
136763
  };
136392
136764
  const engine = args.engine ?? createBrowserTranslationExecution();
136393
136765
  const languageDetection = await createSourceLanguageDetectionSession(args.markdown, args.signal);
136394
- const translatorBySourceLanguage = /* @__PURE__ */ new Map();
136766
+ const translatedSegments = [...segments];
136767
+ const pendingJobsBySourceLanguage = /* @__PURE__ */ new Map();
136395
136768
  try {
136396
- const translatedSegments = [];
136397
136769
  for (const [segmentIndex, segment] of segments.entries()) {
136398
136770
  throwIfAborted(args.signal);
136399
136771
  const sourceLanguage = await languageDetection.detectSegmentLanguage(segment.translatorInput, args.signal);
@@ -136407,7 +136779,7 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136407
136779
  targetLanguage: args.targetLanguage,
136408
136780
  status: "translated"
136409
136781
  };
136410
- translatedSegments.push(translatedSegment);
136782
+ translatedSegments[segmentIndex] = translatedSegment;
136411
136783
  onPatch({
136412
136784
  segmentIndex,
136413
136785
  segment: translatedSegment
@@ -136420,33 +136792,27 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136420
136792
  targetLanguage: args.targetLanguage
136421
136793
  }) : null;
136422
136794
  if (cachedSegment) {
136423
- translatedSegments.push(cachedSegment);
136795
+ translatedSegments[segmentIndex] = cachedSegment;
136424
136796
  onPatch({
136425
136797
  segmentIndex,
136426
136798
  segment: cachedSegment
136427
136799
  });
136428
136800
  continue;
136429
136801
  }
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,
136802
+ const pendingJob = {
136803
+ segmentIndex,
136804
+ segment,
136440
136805
  sourceLanguage,
136441
- targetLanguage: args.targetLanguage,
136442
- status: "translated"
136806
+ cacheKey,
136807
+ protectedInput: segment.placeholderProtocol ? {
136808
+ text: segment.translatorInput,
136809
+ restore: (output) => output
136810
+ } : protectTranslatorInput(segment.translatorInput),
136811
+ estimatedTokens: estimateTranslationTokens(segment.translatorInput)
136443
136812
  };
136444
- if (cacheKey) writeCachedTranslationSegment(args.cache, cacheKey, translatedSegment);
136445
- translatedSegments.push(translatedSegment);
136446
- onPatch({
136447
- segmentIndex,
136448
- segment: translatedSegment
136449
- });
136813
+ const pendingJobs = pendingJobsBySourceLanguage.get(sourceLanguage) ?? [];
136814
+ pendingJobs.push(pendingJob);
136815
+ pendingJobsBySourceLanguage.set(sourceLanguage, pendingJobs);
136450
136816
  } catch (error) {
136451
136817
  if (args.signal.aborted) throw error;
136452
136818
  const failedSegment = {
@@ -136456,13 +136822,23 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136456
136822
  status: "error",
136457
136823
  error: getErrorMessage(error)
136458
136824
  };
136459
- translatedSegments.push(failedSegment);
136825
+ translatedSegments[segmentIndex] = failedSegment;
136460
136826
  onPatch({
136461
136827
  segmentIndex,
136462
136828
  segment: failedSegment
136463
136829
  });
136464
136830
  }
136465
136831
  }
136832
+ await Promise.all([...pendingJobsBySourceLanguage.entries()].map(([sourceLanguage, jobs]) => translatePendingJobsBySourceLanguage({
136833
+ engine,
136834
+ sourceLanguage,
136835
+ targetLanguage: args.targetLanguage,
136836
+ signal: args.signal,
136837
+ cache: args.cache,
136838
+ jobs,
136839
+ translatedSegments,
136840
+ onPatch
136841
+ })));
136466
136842
  return {
136467
136843
  segments: translatedSegments,
136468
136844
  displayMode: args.displayMode,
@@ -136470,24 +136846,247 @@ async function translateMarkdownDocumentProgressively(args, onPatch) {
136470
136846
  targetLanguage: args.targetLanguage
136471
136847
  };
136472
136848
  } finally {
136473
- translatorBySourceLanguage.forEach((translator) => translator.destroy?.());
136474
136849
  languageDetection.destroy();
136475
136850
  }
136476
136851
  }
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
136852
+ async function translatePendingJobsBySourceLanguage(input) {
136853
+ const markJobsError = (jobs, message) => {
136854
+ for (const job of jobs) {
136855
+ const failedSegment = {
136856
+ ...job.segment,
136857
+ sourceLanguage: job.sourceLanguage,
136858
+ targetLanguage: input.targetLanguage,
136859
+ status: "error",
136860
+ error: message
136861
+ };
136862
+ input.translatedSegments[job.segmentIndex] = failedSegment;
136863
+ input.onPatch({
136864
+ segmentIndex: job.segmentIndex,
136865
+ segment: failedSegment
136866
+ });
136867
+ }
136868
+ };
136869
+ const unsupportedLanguagePairMessage = getUnsupportedEngineLanguagePairMessage({
136870
+ engine: input.engine,
136871
+ sourceLanguage: input.sourceLanguage,
136872
+ targetLanguage: input.targetLanguage
136484
136873
  });
136485
- translatorBySourceLanguage.set(sourceLanguage, translator);
136486
- return translator;
136874
+ if (unsupportedLanguagePairMessage) {
136875
+ markJobsError(input.jobs, unsupportedLanguagePairMessage);
136876
+ return;
136877
+ }
136878
+ const batches = packTranslationJobs(input.jobs);
136879
+ if (batches.length === 0) return;
136880
+ const maxConcurrency = Math.min(6, batches.length);
136881
+ const scopeKey = createTranslationAdaptiveConcurrencyScopeKey({
136882
+ engineId: input.engine.cacheIdentity.engineId,
136883
+ engineVersion: input.engine.cacheIdentity.engineVersion,
136884
+ model: input.engine.cacheIdentity.model,
136885
+ selectedGroupId: input.engine.cacheIdentity.selectedGroupId,
136886
+ sourceLanguage: input.sourceLanguage,
136887
+ targetLanguage: input.targetLanguage,
136888
+ translatorContractVersion: input.engine.cacheIdentity.translatorContractVersion
136889
+ });
136890
+ let desiredConcurrency = 1;
136891
+ let nextBatchIndex = 0;
136892
+ let activeWorkers = 0;
136893
+ let completedBatches = 0;
136894
+ const workerPromises = /* @__PURE__ */ new Set();
136895
+ const startWorkersToDesired = () => {
136896
+ while (activeWorkers < desiredConcurrency && nextBatchIndex < batches.length && !input.signal.aborted) startWorker();
136897
+ };
136898
+ const maybeGrowConcurrency = () => {
136899
+ if (desiredConcurrency >= maxConcurrency) return;
136900
+ const recentLogs = readRecentTranslationAdaptiveConcurrencyLogs({
136901
+ scopeKey,
136902
+ limit: Math.max(4, desiredConcurrency * 2)
136903
+ });
136904
+ if (desiredConcurrency === 1) {
136905
+ if (completedBatches >= 1 && batches.length > 1 && recentLogs.length > 0) {
136906
+ desiredConcurrency = 2;
136907
+ startWorkersToDesired();
136908
+ }
136909
+ return;
136910
+ }
136911
+ if (completedBatches < desiredConcurrency) return;
136912
+ if (recentLogs.length < desiredConcurrency * 2) return;
136913
+ const window = recentLogs.slice(-desiredConcurrency * 2);
136914
+ const split = Math.max(1, Math.floor(window.length / 2));
136915
+ const earlierThroughput = summarizeTranslationLogThroughput(window.slice(0, split));
136916
+ const laterThroughput = summarizeTranslationLogThroughput(window.slice(split));
136917
+ if (earlierThroughput > 0 && laterThroughput >= earlierThroughput * 1.08) {
136918
+ desiredConcurrency = Math.min(maxConcurrency, desiredConcurrency + 1);
136919
+ startWorkersToDesired();
136920
+ }
136921
+ };
136922
+ const applyBatchResult = async (batch, outputs) => {
136923
+ for (const [offset, job] of batch.jobs.entries()) {
136924
+ const target = outputs[offset] ?? "";
136925
+ const restoredTarget = job.segment.placeholderProtocol ? restoreTranslatedPlaceholderFragment(target, job.segment.placeholderProtocol) : { target: job.protectedInput.restore(target).trim() };
136926
+ const translatedSegment = {
136927
+ ...job.segment,
136928
+ ...restoredTarget,
136929
+ sourceLanguage: job.sourceLanguage,
136930
+ targetLanguage: input.targetLanguage,
136931
+ status: "translated"
136932
+ };
136933
+ input.translatedSegments[job.segmentIndex] = translatedSegment;
136934
+ if (job.cacheKey) writeCachedTranslationSegment(input.cache, job.cacheKey, translatedSegment);
136935
+ input.onPatch({
136936
+ segmentIndex: job.segmentIndex,
136937
+ segment: translatedSegment
136938
+ });
136939
+ }
136940
+ };
136941
+ const markBatchError = (batch, error) => {
136942
+ markJobsError(batch.jobs, getErrorMessage(error));
136943
+ };
136944
+ const startWorker = () => {
136945
+ if (input.signal.aborted || nextBatchIndex >= batches.length) return;
136946
+ activeWorkers += 1;
136947
+ let workerPromise;
136948
+ workerPromise = (async () => {
136949
+ let translator = null;
136950
+ try {
136951
+ translator = await input.engine.factory.create({
136952
+ sourceLanguage: input.sourceLanguage,
136953
+ targetLanguage: input.targetLanguage,
136954
+ signal: input.signal
136955
+ });
136956
+ while (!input.signal.aborted) {
136957
+ const batchIndex = nextBatchIndex;
136958
+ if (batchIndex >= batches.length) break;
136959
+ nextBatchIndex += 1;
136960
+ const batch = batches[batchIndex];
136961
+ const startedAt = getCurrentTimeMs();
136962
+ try {
136963
+ await applyBatchResult(batch, await collectBatchTranslationOutputs(translator.batchTranslate(batch.jobs.map((job) => job.protectedInput.text), { signal: input.signal }), batch.jobs.length));
136964
+ completedBatches += 1;
136965
+ const elapsedMs = Math.max(1, getCurrentTimeMs() - startedAt);
136966
+ appendTranslationAdaptiveConcurrencyLog({
136967
+ scopeKey,
136968
+ recordedAt: Date.now(),
136969
+ engineId: input.engine.cacheIdentity.engineId,
136970
+ engineVersion: input.engine.cacheIdentity.engineVersion,
136971
+ model: input.engine.cacheIdentity.model,
136972
+ selectedGroupId: input.engine.cacheIdentity.selectedGroupId,
136973
+ sourceLanguage: input.sourceLanguage,
136974
+ targetLanguage: input.targetLanguage,
136975
+ batchIndex,
136976
+ batchSize: batch.jobs.length,
136977
+ estimatedTokens: batch.estimatedTokens,
136978
+ elapsedMs,
136979
+ throughputTokensPerMs: batch.estimatedTokens / elapsedMs,
136980
+ desiredConcurrency,
136981
+ activeWorkers,
136982
+ maxConcurrency
136983
+ });
136984
+ maybeGrowConcurrency();
136985
+ } catch (error) {
136986
+ if (input.signal.aborted) throw error;
136987
+ markBatchError(batch, error);
136988
+ }
136989
+ }
136990
+ } finally {
136991
+ translator?.destroy?.();
136992
+ }
136993
+ })().finally(() => {
136994
+ activeWorkers -= 1;
136995
+ workerPromises.delete(workerPromise);
136996
+ startWorkersToDesired();
136997
+ });
136998
+ workerPromises.add(workerPromise);
136999
+ };
137000
+ startWorkersToDesired();
137001
+ while (workerPromises.size > 0) await Promise.race(workerPromises);
137002
+ }
137003
+ function getUnsupportedEngineLanguagePairMessage(input) {
137004
+ if (!isManagedLocalTranslationEngineId(input.engine.cacheIdentity.engineId)) return null;
137005
+ const directionCheck = checkLocalDirectionalModelLanguagePair({
137006
+ model: input.engine.cacheIdentity.model,
137007
+ sourceLanguage: input.sourceLanguage,
137008
+ targetLanguage: input.targetLanguage
137009
+ });
137010
+ if (directionCheck.supported) return null;
137011
+ return directionCheck.message ?? "Selected local model does not support the detected translation direction.";
137012
+ }
137013
+ function summarizeTranslationLogThroughput(logs) {
137014
+ if (logs.length === 0) return 0;
137015
+ const totalTokens = logs.reduce((total, log) => total + log.estimatedTokens, 0);
137016
+ const totalElapsedMs = logs.reduce((total, log) => total + log.elapsedMs, 0);
137017
+ if (totalTokens <= 0 || totalElapsedMs <= 0) return 0;
137018
+ return totalTokens / totalElapsedMs;
137019
+ }
137020
+ function packTranslationJobs(jobs) {
137021
+ if (jobs.length === 0) return [];
137022
+ const averageTokens = jobs.reduce((total, job) => total + job.estimatedTokens, 0) / Math.max(1, jobs.length);
137023
+ const targetTokens = Math.max(1, Math.round(averageTokens * 6));
137024
+ const batches = [];
137025
+ let currentJobs = [];
137026
+ let currentTokens = 0;
137027
+ const flush = () => {
137028
+ if (currentJobs.length === 0) return;
137029
+ batches.push({
137030
+ jobs: currentJobs,
137031
+ estimatedTokens: currentTokens
137032
+ });
137033
+ currentJobs = [];
137034
+ currentTokens = 0;
137035
+ };
137036
+ for (const job of jobs) {
137037
+ if (currentJobs.length === 0) {
137038
+ currentJobs = [job];
137039
+ currentTokens = job.estimatedTokens;
137040
+ continue;
137041
+ }
137042
+ const nextTokens = currentTokens + job.estimatedTokens;
137043
+ if (nextTokens <= targetTokens) {
137044
+ currentJobs.push(job);
137045
+ currentTokens = nextTokens;
137046
+ continue;
137047
+ }
137048
+ const withoutDelta = Math.abs(currentTokens - targetTokens);
137049
+ if (Math.abs(nextTokens - targetTokens) <= withoutDelta) {
137050
+ currentJobs.push(job);
137051
+ currentTokens = nextTokens;
137052
+ flush();
137053
+ continue;
137054
+ }
137055
+ flush();
137056
+ currentJobs = [job];
137057
+ currentTokens = job.estimatedTokens;
137058
+ }
137059
+ flush();
137060
+ return batches;
137061
+ }
137062
+ async function collectBatchTranslationOutputs(stream, expectedCount) {
137063
+ const outputs = /* @__PURE__ */ new Map();
137064
+ for await (const item of stream) {
137065
+ if (item.index < 0 || item.index >= expectedCount) throw new Error(`Translator yielded output for unexpected index ${item.index}.`);
137066
+ if (!outputs.has(item.index)) outputs.set(item.index, item.output);
137067
+ }
137068
+ if (outputs.size !== expectedCount) throw new Error(`Translator returned ${outputs.size} outputs for ${expectedCount} inputs.`);
137069
+ return Array.from({ length: expectedCount }, (_, index) => outputs.get(index) ?? "");
137070
+ }
137071
+ function estimateTranslationTokens(input) {
137072
+ const trimmed = input.trim();
137073
+ if (!trimmed) return 1;
137074
+ const segmenter = getTokenSegmenter();
137075
+ if (!segmenter) return Math.max(1, trimmed.split(/\s+/).filter(Boolean).length);
137076
+ let count = 0;
137077
+ for (const segment of segmenter.segment(trimmed)) if (segment.isWordLike ?? segment.segment.trim().length > 0) count += 1;
137078
+ return Math.max(1, count);
137079
+ }
137080
+ function getTokenSegmenter() {
137081
+ if (typeof Intl === "undefined" || typeof Intl.Segmenter !== "function") return null;
137082
+ try {
137083
+ return new Intl.Segmenter(void 0, { granularity: "word" });
137084
+ } catch {
137085
+ return null;
137086
+ }
136487
137087
  }
136488
- async function readSingleBatchOutput(stream) {
136489
- for await (const item of stream) return item.output;
136490
- throw new Error("Translator returned no batch output.");
137088
+ function getCurrentTimeMs() {
137089
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
136491
137090
  }
136492
137091
  async function createSourceLanguageDetectionSession(markdown, signal) {
136493
137092
  const detectorFactory = window.LanguageDetector;
@@ -136963,87 +137562,10 @@ function getErrorMessage(error) {
136963
137562
  return error instanceof Error ? error.message : "Unknown translation error.";
136964
137563
  }
136965
137564
  //#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
137565
  //#region ../core/src/local-download-profiles.ts
137044
137566
  function selectLocalDownloadGroup(plan, selectedGroupId) {
137045
137567
  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);
137568
+ 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
137569
  }
137048
137570
  function selectSmallestSelectableGroup(groups) {
137049
137571
  return groups.filter((group) => group.selectable && group.estimatedTotalBytes !== void 0).sort((left, right) => (left.estimatedTotalBytes ?? 0) - (right.estimatedTotalBytes ?? 0))[0] ?? null;
@@ -137102,25 +137624,37 @@ function projectTranslateServiceStatus(input) {
137102
137624
  };
137103
137625
  }
137104
137626
  }
137105
- if (input.engineId === "local") {
137627
+ if (input.engineLifecycleLoading || input.engineLifecycle !== void 0) {
137628
+ if (input.engineLifecycleLoading || !input.engineLifecycle) return {
137629
+ state: "checking",
137630
+ engineId: input.engineId,
137631
+ message: "Checking translation engine runtime."
137632
+ };
137633
+ if (shouldShowTranslationEngineInstallGate(input.engineLifecycle)) return {
137634
+ state: "unavailable",
137635
+ engineId: input.engineId,
137636
+ message: getTranslationEngineLifecycleMessage(input.engineLifecycle) ?? "Translation engine runtime is not ready."
137637
+ };
137638
+ }
137639
+ if (isManagedLocalTranslationEngineId(input.engineId)) {
137106
137640
  if (!input.localModel?.trim()) return {
137107
137641
  state: "unavailable",
137108
- engineId: "local",
137109
- message: "Select a local model before translating."
137642
+ engineId: input.engineId,
137643
+ message: "Select a model before translating."
137110
137644
  };
137111
137645
  if (input.localAssetLoading || !input.localAsset) return {
137112
137646
  state: "checking",
137113
- engineId: "local",
137647
+ engineId: input.engineId,
137114
137648
  message: "Checking local model files."
137115
137649
  };
137116
137650
  if (isLocalAssetReady(input.localAsset, input.localSelectedGroupId)) return {
137117
137651
  state: "ready",
137118
- engineId: "local",
137652
+ engineId: input.engineId,
137119
137653
  message: "Selected local model files are ready."
137120
137654
  };
137121
137655
  return {
137122
137656
  state: "unavailable",
137123
- engineId: "local",
137657
+ engineId: input.engineId,
137124
137658
  message: "Selected local model files are not installed locally."
137125
137659
  };
137126
137660
  }
@@ -137142,6 +137676,294 @@ function isLocalAssetReady(asset, selectedGroupId) {
137142
137676
  return allRequiredFilesReady;
137143
137677
  }
137144
137678
  //#endregion
137679
+ //#region src/lib/translate-service.ts
137680
+ async function resolveTranslateServiceState(input) {
137681
+ const config = input.config;
137682
+ if (!config?.enabled || !input.hasSource) return emitTranslateServiceState(input.onUpdate, { status: projectTranslateServiceStatus({
137683
+ enabled: config?.enabled ?? false,
137684
+ hasSource: input.hasSource,
137685
+ engineId: config?.engineId ?? "browser"
137686
+ }) });
137687
+ let engineLifecycle = null;
137688
+ if (config.engineId !== "browser") {
137689
+ input.onUpdate?.(createTranslateServiceState({ status: projectTranslateServiceStatus({
137690
+ enabled: config.enabled,
137691
+ hasSource: input.hasSource,
137692
+ engineId: config.engineId,
137693
+ engineLifecycleLoading: true
137694
+ }) }));
137695
+ try {
137696
+ engineLifecycle = await trpcClient.translationEngines.getLifecycle.query({ engineId: config.engineId });
137697
+ } catch (lifecycleError) {
137698
+ return createTranslateServiceState({ status: {
137699
+ state: "unavailable",
137700
+ engineId: config.engineId,
137701
+ message: lifecycleError instanceof Error ? lifecycleError.message : "Unable to check translation engine runtime."
137702
+ } });
137703
+ }
137704
+ if (shouldShowTranslationEngineInstallGate(engineLifecycle)) return createTranslateServiceState({ status: projectTranslateServiceStatus({
137705
+ enabled: config.enabled,
137706
+ hasSource: input.hasSource,
137707
+ engineId: config.engineId,
137708
+ engineLifecycle
137709
+ }) });
137710
+ }
137711
+ if (isManagedLocalTranslationEngineId(config.engineId)) {
137712
+ const localEngineConfig = getManagedLocalEngineConfig(config);
137713
+ const model = localEngineConfig.model?.trim();
137714
+ if (!model) return emitTranslateServiceState(input.onUpdate, { status: projectTranslateServiceStatus({
137715
+ enabled: config.enabled,
137716
+ hasSource: input.hasSource,
137717
+ engineId: config.engineId,
137718
+ engineLifecycle,
137719
+ localModel: model,
137720
+ localSelectedGroupId: localEngineConfig.selectedGroupId
137721
+ }) });
137722
+ const directionCheck = checkLocalDirectionalModelLanguagePair({
137723
+ model,
137724
+ targetLanguage: config.targetLanguage
137725
+ });
137726
+ if (!directionCheck.supported) return emitTranslateServiceState(input.onUpdate, { status: {
137727
+ state: "unavailable",
137728
+ engineId: config.engineId,
137729
+ message: directionCheck.message ?? "Selected local model does not support the configured target language."
137730
+ } });
137731
+ input.onUpdate?.(createTranslateServiceState({ status: projectTranslateServiceStatus({
137732
+ enabled: config.enabled,
137733
+ hasSource: input.hasSource,
137734
+ engineId: config.engineId,
137735
+ engineLifecycle,
137736
+ localModel: model,
137737
+ localSelectedGroupId: localEngineConfig.selectedGroupId,
137738
+ localAssetLoading: true
137739
+ }) }));
137740
+ try {
137741
+ const panelState = await queryManagedLocalPanelState(config.engineId, {
137742
+ modelId: model,
137743
+ selectedGroupId: localEngineConfig.selectedGroupId
137744
+ });
137745
+ const selectedGroupId = panelState.selectedGroupId ?? localEngineConfig.selectedGroupId;
137746
+ return createTranslateServiceState({ status: projectTranslateServiceStatus({
137747
+ enabled: config.enabled,
137748
+ hasSource: input.hasSource,
137749
+ engineId: config.engineId,
137750
+ engineLifecycle,
137751
+ localModel: model,
137752
+ localSelectedGroupId: selectedGroupId,
137753
+ localAsset: panelState.asset
137754
+ }) });
137755
+ } catch (assetError) {
137756
+ return createTranslateServiceState({ status: {
137757
+ state: "unavailable",
137758
+ engineId: config.engineId,
137759
+ message: assetError instanceof Error ? assetError.message : "Unable to check local model files."
137760
+ } });
137761
+ }
137762
+ }
137763
+ if (config.engineId === "openai") return emitTranslateServiceState(input.onUpdate, { status: projectTranslateServiceStatus({
137764
+ enabled: config.enabled,
137765
+ hasSource: input.hasSource,
137766
+ engineId: "openai",
137767
+ engineLifecycle
137768
+ }) });
137769
+ const cachedTable = getBrowserSupportTableState(config.targetLanguage);
137770
+ if (cachedTable) return emitTranslateServiceState(input.onUpdate, {
137771
+ browserSupportTable: cachedTable,
137772
+ status: projectTranslateServiceStatus({
137773
+ enabled: config.enabled,
137774
+ hasSource: input.hasSource,
137775
+ engineId: "browser",
137776
+ browserSupportTable: cachedTable
137777
+ })
137778
+ });
137779
+ const checkingTable = {
137780
+ state: "checking",
137781
+ table: null,
137782
+ message: "Checking browser translation pairs…"
137783
+ };
137784
+ input.onUpdate?.(createTranslateServiceState({
137785
+ browserSupportTable: checkingTable,
137786
+ status: projectTranslateServiceStatus({
137787
+ enabled: config.enabled,
137788
+ hasSource: input.hasSource,
137789
+ engineId: "browser",
137790
+ browserSupportTable: checkingTable
137791
+ })
137792
+ }));
137793
+ try {
137794
+ const nextTable = await scanBrowserTranslationPairs(config.targetLanguage, {
137795
+ signal: input.signal ?? new AbortController().signal,
137796
+ onProgress: (progressState) => {
137797
+ input.onUpdate?.(createTranslateServiceState({
137798
+ browserSupportTable: progressState,
137799
+ status: projectTranslateServiceStatus({
137800
+ enabled: config.enabled,
137801
+ hasSource: input.hasSource,
137802
+ engineId: "browser",
137803
+ browserSupportTable: progressState
137804
+ })
137805
+ }));
137806
+ }
137807
+ });
137808
+ return createTranslateServiceState({
137809
+ browserSupportTable: nextTable,
137810
+ status: projectTranslateServiceStatus({
137811
+ enabled: config.enabled,
137812
+ hasSource: input.hasSource,
137813
+ engineId: "browser",
137814
+ browserSupportTable: nextTable
137815
+ })
137816
+ });
137817
+ } catch (probeError) {
137818
+ const nextCapability = {
137819
+ availability: "error",
137820
+ message: probeError instanceof Error ? probeError.message : "Unable to check translation support."
137821
+ };
137822
+ return createTranslateServiceState({
137823
+ capability: nextCapability,
137824
+ status: projectTranslateServiceStatus({
137825
+ enabled: config.enabled,
137826
+ hasSource: input.hasSource,
137827
+ engineId: "browser",
137828
+ browserCapability: nextCapability
137829
+ })
137830
+ });
137831
+ }
137832
+ }
137833
+ function prepareTranslateServiceRun(input) {
137834
+ if (input.config.engineId !== "browser") return createTranslateServiceState({ status: projectTranslateServiceStatus({
137835
+ enabled: input.config.enabled,
137836
+ hasSource: input.hasSource,
137837
+ engineId: input.config.engineId
137838
+ }) });
137839
+ 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;
137840
+ if (!preferredRow) return createTranslateServiceState({
137841
+ browserSupportTable: input.browserSupportTable,
137842
+ status: projectTranslateServiceStatus({
137843
+ enabled: input.config.enabled,
137844
+ hasSource: input.hasSource,
137845
+ engineId: "browser",
137846
+ browserSupportTable: input.browserSupportTable
137847
+ })
137848
+ });
137849
+ const nextCapability = {
137850
+ availability: preferredRow.availability,
137851
+ progress: preferredRow.progress,
137852
+ message: preferredRow.message
137853
+ };
137854
+ const nextTable = patchBrowserSupportTableRow(input.config.targetLanguage, preferredRow, { message: void 0 });
137855
+ return createTranslateServiceState({
137856
+ capability: nextCapability,
137857
+ browserSupportTable: nextTable,
137858
+ status: projectTranslateServiceStatus({
137859
+ enabled: input.config.enabled,
137860
+ hasSource: input.hasSource,
137861
+ engineId: "browser",
137862
+ browserSupportTable: nextTable,
137863
+ browserCapability: nextCapability
137864
+ })
137865
+ });
137866
+ }
137867
+ function createTranslationEngineExecution(config) {
137868
+ if (config.engineId === "browser" || isStaticMode()) return createBrowserTranslationExecution();
137869
+ const model = config.engineId === "openai" ? config.engines.openai.model : getManagedLocalEngineConfig(config).model;
137870
+ return {
137871
+ factory: new TrpcTranslatorFactory(config.engineId, model, isManagedLocalTranslationEngineId(config.engineId) ? getManagedLocalEngineConfig(config).selectedGroupId : void 0),
137872
+ cacheIdentity: {
137873
+ engineId: config.engineId,
137874
+ model,
137875
+ selectedGroupId: isManagedLocalTranslationEngineId(config.engineId) ? getManagedLocalEngineConfig(config).selectedGroupId : void 0,
137876
+ translatorContractVersion: 3
137877
+ }
137878
+ };
137879
+ }
137880
+ var TrpcTranslatorFactory = class {
137881
+ constructor(engineId, model, selectedGroupId) {
137882
+ this.engineId = engineId;
137883
+ this.model = model;
137884
+ this.selectedGroupId = selectedGroupId;
137885
+ }
137886
+ async create(options) {
137887
+ return new TrpcTranslator({
137888
+ engineId: this.engineId,
137889
+ sourceLanguage: options.sourceLanguage,
137890
+ targetLanguage: options.targetLanguage,
137891
+ model: options.model ?? this.model,
137892
+ selectedGroupId: isManagedLocalTranslationEngineId(this.engineId) ? this.selectedGroupId : void 0
137893
+ });
137894
+ }
137895
+ };
137896
+ function getManagedLocalEngineConfig(config) {
137897
+ return config.engineId === "local-ct2" ? {
137898
+ model: config.engines.localCt2.model,
137899
+ selectedGroupId: config.engines.localCt2.selectedGroupId
137900
+ } : {
137901
+ model: config.engines.local.model,
137902
+ selectedGroupId: config.engines.local.selectedGroupId
137903
+ };
137904
+ }
137905
+ async function queryManagedLocalPanelState(engineId, input) {
137906
+ return engineId === "local" ? trpcClient.localModels.panelState.query(input) : trpcClient.localCt2Models.panelState.query(input);
137907
+ }
137908
+ var TrpcTranslator = class {
137909
+ constructor(options) {
137910
+ this.options = options;
137911
+ }
137912
+ async *batchTranslate(inputs, options) {
137913
+ if (options?.signal?.aborted) throw new DOMException("Translation cancelled.", "AbortError");
137914
+ const queue = [];
137915
+ let completed = false;
137916
+ let thrown = null;
137917
+ const subscription = trpcClient.translationEngines.batchTranslate.subscribe({
137918
+ engineId: this.options.engineId,
137919
+ sourceLanguage: this.options.sourceLanguage,
137920
+ targetLanguage: this.options.targetLanguage,
137921
+ model: this.options.model,
137922
+ selectedGroupId: this.options.selectedGroupId,
137923
+ inputs,
137924
+ instructions: options?.instructions,
137925
+ context: options?.context
137926
+ }, {
137927
+ onData(event) {
137928
+ queue.push(event);
137929
+ },
137930
+ onError(error) {
137931
+ thrown = error instanceof Error ? error : new Error(String(error));
137932
+ completed = true;
137933
+ },
137934
+ onComplete() {
137935
+ completed = true;
137936
+ }
137937
+ });
137938
+ try {
137939
+ while (!completed || queue.length > 0) {
137940
+ if (options?.signal?.aborted) throw new DOMException("Translation cancelled.", "AbortError");
137941
+ const item = queue.shift();
137942
+ if (item) {
137943
+ yield item;
137944
+ continue;
137945
+ }
137946
+ await new Promise((resolve) => setTimeout(resolve, 0));
137947
+ }
137948
+ if (thrown) throw thrown;
137949
+ } finally {
137950
+ subscription.unsubscribe();
137951
+ }
137952
+ }
137953
+ };
137954
+ function createTranslateServiceState(input) {
137955
+ return {
137956
+ capability: input.capability ?? null,
137957
+ browserSupportTable: input.browserSupportTable ?? null,
137958
+ status: input.status
137959
+ };
137960
+ }
137961
+ function emitTranslateServiceState(onUpdate, input) {
137962
+ const state = createTranslateServiceState(input);
137963
+ onUpdate?.(state);
137964
+ return state;
137965
+ }
137966
+ //#endregion
137145
137967
  //#region src/lib/use-document-translation.ts
137146
137968
  function useDocumentTranslation(markdown, config) {
137147
137969
  const [status, setStatus] = (0, import_react.useState)("source");
@@ -137154,9 +137976,11 @@ function useDocumentTranslation(markdown, config) {
137154
137976
  const [error, setError] = (0, import_react.useState)(null);
137155
137977
  const [result, setResult] = (0, import_react.useState)(null);
137156
137978
  const abortRef = (0, import_react.useRef)(null);
137979
+ const generationRef = (0, import_react.useRef)(0);
137157
137980
  const latestStartRef = (0, import_react.useRef)(null);
137158
137981
  const { activation } = useDocumentTranslationActivation();
137159
137982
  const cancel = (0, import_react.useCallback)(() => {
137983
+ generationRef.current += 1;
137160
137984
  abortRef.current?.abort();
137161
137985
  abortRef.current = null;
137162
137986
  setStatus("source");
@@ -137164,6 +137988,7 @@ function useDocumentTranslation(markdown, config) {
137164
137988
  setError(null);
137165
137989
  }, []);
137166
137990
  const reset = (0, import_react.useCallback)(() => {
137991
+ generationRef.current += 1;
137167
137992
  abortRef.current?.abort();
137168
137993
  abortRef.current = null;
137169
137994
  setStatus("source");
@@ -137172,6 +137997,7 @@ function useDocumentTranslation(markdown, config) {
137172
137997
  }, []);
137173
137998
  (0, import_react.useEffect)(() => reset, [reset]);
137174
137999
  (0, import_react.useEffect)(() => {
138000
+ generationRef.current += 1;
137175
138001
  setCapability(null);
137176
138002
  setBrowserSupportTable(null);
137177
138003
  setResult(null);
@@ -137181,141 +138007,41 @@ function useDocumentTranslation(markdown, config) {
137181
138007
  markdown,
137182
138008
  config?.displayMode,
137183
138009
  config?.enabled,
138010
+ config?.engineId,
138011
+ config?.engines.local.model,
138012
+ config?.engines.local.selectedGroupId,
138013
+ config?.engines.localCt2.model,
138014
+ config?.engines.localCt2.selectedGroupId,
138015
+ config?.engines.openai.model,
137184
138016
  config?.targetLanguage
137185
138017
  ]);
137186
138018
  (0, import_react.useEffect)(() => {
137187
138019
  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
138020
  const controller = new AbortController();
137285
- scanBrowserTranslationPairs(config.targetLanguage, {
138021
+ resolveTranslateServiceState({
138022
+ config,
138023
+ hasSource: markdown.length > 0,
137286
138024
  signal: controller.signal,
137287
- onProgress: (nextState) => {
138025
+ onUpdate: (nextState) => {
137288
138026
  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
- }));
138027
+ setCapability(nextState.capability);
138028
+ setBrowserSupportTable(nextState.browserSupportTable);
138029
+ setServiceStatus(nextState.status);
137296
138030
  }
137297
138031
  }).then((nextState) => {
137298
138032
  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) => {
138033
+ setCapability(nextState.capability);
138034
+ setBrowserSupportTable(nextState.browserSupportTable);
138035
+ setServiceStatus(nextState.status);
138036
+ }).catch((stateError) => {
137307
138037
  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
- }));
138038
+ setCapability(null);
138039
+ setBrowserSupportTable(null);
138040
+ setServiceStatus({
138041
+ state: "unavailable",
138042
+ engineId: config?.engineId ?? "browser",
138043
+ message: stateError instanceof Error ? stateError.message : "Unable to check translation service."
138044
+ });
137319
138045
  });
137320
138046
  return () => {
137321
138047
  disposed = true;
@@ -137326,6 +138052,8 @@ function useDocumentTranslation(markdown, config) {
137326
138052
  config?.engineId,
137327
138053
  config?.engines.local.model,
137328
138054
  config?.engines.local.selectedGroupId,
138055
+ config?.engines.localCt2.model,
138056
+ config?.engines.localCt2.selectedGroupId,
137329
138057
  config?.targetLanguage,
137330
138058
  markdown.length
137331
138059
  ]);
@@ -137333,6 +138061,8 @@ function useDocumentTranslation(markdown, config) {
137333
138061
  if (!config?.enabled) return;
137334
138062
  abortRef.current?.abort();
137335
138063
  const controller = new AbortController();
138064
+ const generationId = generationRef.current + 1;
138065
+ generationRef.current = generationId;
137336
138066
  abortRef.current = controller;
137337
138067
  setError(null);
137338
138068
  setStatus("initializing");
@@ -137343,27 +138073,19 @@ function useDocumentTranslation(markdown, config) {
137343
138073
  return;
137344
138074
  }
137345
138075
  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);
138076
+ const nextState = prepareTranslateServiceRun({
138077
+ config,
138078
+ hasSource: markdown.length > 0,
138079
+ browserSupportTable
138080
+ });
138081
+ setCapability(nextState.capability);
138082
+ setBrowserSupportTable(nextState.browserSupportTable);
138083
+ setServiceStatus(nextState.status);
138084
+ if (nextState.status.state !== "ready") {
138085
+ setError(nextState.status.message);
137349
138086
  setStatus("unavailable");
137350
138087
  return;
137351
138088
  }
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
138089
  }
137368
138090
  setStatus("translating");
137369
138091
  setResult({
@@ -137382,21 +138104,27 @@ function useDocumentTranslation(markdown, config) {
137382
138104
  write: (input) => trpcClient.translationCache.write.mutate(input)
137383
138105
  } : void 0
137384
138106
  }, (patch) => {
137385
- if (controller.signal.aborted || abortRef.current !== controller) return;
138107
+ if (controller.signal.aborted || abortRef.current !== controller || generationRef.current !== generationId) return;
137386
138108
  setResult((current) => applyDocumentTranslationPatch(current, patch, {
137387
138109
  displayMode: config.displayMode,
137388
138110
  targetLanguage: config.targetLanguage
137389
138111
  }));
137390
138112
  });
137391
- if (controller.signal.aborted) return;
138113
+ if (controller.signal.aborted || abortRef.current !== controller || generationRef.current !== generationId) return;
138114
+ const documentFailure = getDocumentTranslationFailureMessage(nextResult);
137392
138115
  setResult(nextResult);
138116
+ if (documentFailure) {
138117
+ setError(documentFailure);
138118
+ setStatus("error");
138119
+ return;
138120
+ }
137393
138121
  setStatus("translated");
137394
138122
  } catch (translationError) {
137395
- if (controller.signal.aborted) return;
138123
+ if (controller.signal.aborted || abortRef.current !== controller || generationRef.current !== generationId) return;
137396
138124
  setError(translationError instanceof Error ? translationError.message : "Translation failed.");
137397
138125
  setStatus("error");
137398
138126
  } finally {
137399
- if (abortRef.current === controller) abortRef.current = null;
138127
+ if (abortRef.current === controller && generationRef.current === generationId) abortRef.current = null;
137400
138128
  }
137401
138129
  }, [
137402
138130
  browserSupportTable,
@@ -137408,6 +138136,8 @@ function useDocumentTranslation(markdown, config) {
137408
138136
  config?.engines.openai.model,
137409
138137
  config?.engines.local.model,
137410
138138
  config?.engines.local.selectedGroupId,
138139
+ config?.engines.localCt2.model,
138140
+ config?.engines.localCt2.selectedGroupId,
137411
138141
  markdown,
137412
138142
  serviceStatus
137413
138143
  ]);
@@ -137437,8 +138167,16 @@ function useDocumentTranslation(markdown, config) {
137437
138167
  reset
137438
138168
  };
137439
138169
  }
138170
+ function getDocumentTranslationFailureMessage(result) {
138171
+ const segments = (Array.isArray(result.segments) ? result.segments : []).filter((segment) => segment !== void 0);
138172
+ if (segments.length === 0) return null;
138173
+ if (segments.some((segment) => segment.status !== "error" && typeof segment.target === "string")) return null;
138174
+ const errors = segments.map((segment) => segment.status === "error" ? segment.error : void 0).filter((message) => typeof message === "string" && message.length > 0);
138175
+ if (errors.length === 0) return null;
138176
+ return errors[0] ?? "Translation failed.";
138177
+ }
137440
138178
  function applyDocumentTranslationPatch(current, patch, fallback) {
137441
- const segments = [...current?.segments ?? []];
138179
+ const segments = current?.segments.slice() ?? [];
137442
138180
  segments[patch.segmentIndex] = patch.segment;
137443
138181
  return {
137444
138182
  displayMode: current?.displayMode ?? fallback.displayMode,
@@ -148221,7 +148959,8 @@ function stripOpenSpecHeadingKindFromTextNode(node, kind, state) {
148221
148959
  //#endregion
148222
148960
  //#region src/components/document-translation-action.tsx
148223
148961
  function useDocumentTranslationRenderPlugin({ markdown, translationConfig }) {
148224
- const resolvedTranslationConfig = (0, import_react.useMemo)(() => translationConfig === void 0 ? void 0 : DocumentTranslationConfigSchema.parse(translationConfig), [translationConfig]);
148962
+ const { data: globalSettings } = useGlobalSettingsSubscription();
148963
+ const resolvedTranslationConfig = (0, import_react.useMemo)(() => translationConfig === void 0 ? void 0 : DocumentTranslationConfigSchema.parse(resolveDocumentTranslationConfig(translationConfig, globalSettings)), [globalSettings, translationConfig]);
148225
148964
  const session = useDocumentTranslation(markdown ?? "", resolvedTranslationConfig);
148226
148965
  const canTranslate = resolvedTranslationConfig !== void 0 && typeof markdown === "string" && markdown.length > 0;
148227
148966
  const translationProjection = (0, import_react.useMemo)(() => createTranslationProjection(session.result), [session.result]);
@@ -148245,6 +148984,8 @@ function useDocumentTranslationRenderPlugin({ markdown, translationConfig }) {
148245
148984
  resolvedTranslationConfig?.engineId,
148246
148985
  resolvedTranslationConfig?.engines.local.model ?? "no-local-model",
148247
148986
  resolvedTranslationConfig?.engines.local.selectedGroupId ?? "no-local-group",
148987
+ resolvedTranslationConfig?.engines.localCt2.model ?? "no-local-ct2-model",
148988
+ resolvedTranslationConfig?.engines.localCt2.selectedGroupId ?? "no-local-ct2-group",
148248
148989
  session.capability?.availability ?? "unknown",
148249
148990
  session.capability?.message ?? "no-message",
148250
148991
  session.serviceStatus.state,
@@ -148261,6 +149002,7 @@ function DocumentTranslationAction({ enabled, session }) {
148261
149002
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DocumentTranslationButton, {
148262
149003
  capability: session.capability,
148263
149004
  enabled,
149005
+ error: session.error,
148264
149006
  serviceStatus: session.serviceStatus,
148265
149007
  status: session.status,
148266
149008
  onActivate: () => {
@@ -148294,7 +149036,8 @@ function hashString(value) {
148294
149036
  }
148295
149037
  function createTranslationProjection(result) {
148296
149038
  if (!result) return { blockAnnotations: [] };
148297
- const segmentByOffset = new Map(result.segments.filter((segment) => segment.target).map((segment) => [segment.sourceStartOffset, segment]));
149039
+ const segments = getRenderableTranslationSegments(result);
149040
+ const segmentByOffset = new Map(segments.filter((segment) => segment.target).map((segment) => [segment.sourceStartOffset, segment]));
148298
149041
  return {
148299
149042
  headingProcessor: {
148300
149043
  name: "document-translation",
@@ -148316,7 +149059,7 @@ function createTranslationProjection(result) {
148316
149059
  return createTranslatedHeadingTransform(input, segment, result.displayMode);
148317
149060
  }
148318
149061
  },
148319
- blockAnnotations: result.segments.filter((segment) => segment.target && segment.kind !== "heading").map((segment) => ({
149062
+ blockAnnotations: segments.filter((segment) => segment.target && segment.kind !== "heading").map((segment) => ({
148320
149063
  sourceStartOffset: segment.sourceStartOffset,
148321
149064
  sourceKind: segment.sourceKind,
148322
149065
  className: result.displayMode === "direct" ? "document-translation-direct" : "document-translation-bilingual",
@@ -148337,6 +149080,9 @@ function createTranslationProjection(result) {
148337
149080
  }))
148338
149081
  };
148339
149082
  }
149083
+ function getRenderableTranslationSegments(result) {
149084
+ return (Array.isArray(result.segments) ? result.segments : []).filter((segment) => segment !== void 0);
149085
+ }
148340
149086
  function createTranslatedHeadingTransform(input, segment, displayMode) {
148341
149087
  const projectedTarget = segment.target ?? "";
148342
149088
  const openSpecHeading = createTranslatedOpenSpecHeadingProjection(input, segment, displayMode);
@@ -148415,28 +149161,31 @@ function sanitizeTranslatedProperties(properties) {
148415
149161
  }
148416
149162
  return nextProperties;
148417
149163
  }
148418
- function DocumentTranslationButton({ capability, enabled, serviceStatus, status, onActivate }) {
149164
+ function DocumentTranslationButton({ capability, enabled, error, serviceStatus, status, onActivate }) {
148419
149165
  const isServiceChecking = serviceStatus.state === "checking";
148420
149166
  const isServiceUnavailable = serviceStatus.state === "unavailable" || status === "unavailable";
148421
149167
  const isSettingsDisabled = !enabled;
148422
149168
  const isTranslated = status === "translated";
148423
149169
  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" })
149170
+ const isError = status === "error";
149171
+ const ariaLabel = isServiceUnavailable ? "Translation unavailable" : isSettingsDisabled ? "Configure translation" : isServiceChecking ? "Checking translation" : isError ? "Retry translation" : isBusy ? "Cancel translation" : isTranslated ? "Show source" : "Translate";
149172
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
149173
+ content: isServiceUnavailable ? serviceStatus.message ?? capability?.message ?? "Translation is unavailable." : isSettingsDisabled ? "Translation is disabled in settings." : isError ? `${error ?? "Translation failed."} Click to retry.` : ariaLabel,
149174
+ delay: 0,
149175
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
149176
+ size: "icon-sm",
149177
+ variant: "secondary",
149178
+ disabled: isServiceUnavailable || isServiceChecking,
149179
+ "aria-disabled": isSettingsDisabled ? true : void 0,
149180
+ onClick: (event) => {
149181
+ event.stopPropagation();
149182
+ onActivate();
149183
+ },
149184
+ "aria-label": ariaLabel,
149185
+ "data-translation-action-state": isServiceUnavailable ? "unavailable" : isServiceChecking ? "checking" : isSettingsDisabled ? "settings-disabled" : isError ? "error" : isBusy ? "busy" : isTranslated ? "translated" : "ready",
149186
+ 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",
149187
+ 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" })
149188
+ })
148440
149189
  });
148441
149190
  }
148442
149191
  //#endregion
@@ -150398,89 +151147,6 @@ var viewerStyles = String.raw`
150398
151147
  /* MarkdownViewer keeps layout hooks local; shared ToC geometry lives in index.css. */
150399
151148
  `;
150400
151149
  //#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
151150
  //#region src/lib/file-preview.ts
150485
151151
  async function prepareEntityFilePreview(input) {
150486
151152
  if (isStaticMode()) return null;
@@ -150555,13 +151221,10 @@ function isPreviewOnlyFile(file) {
150555
151221
  }
150556
151222
  function resolveDefaultMode(file, inStaticMode) {
150557
151223
  if (!file) return "read";
151224
+ if (!inStaticMode && isFileEntry(file) && file.previewKind === "html") return "preview";
150558
151225
  if (!inStaticMode && isPreviewOnlyFile(file)) return "preview";
150559
151226
  return "read";
150560
151227
  }
150561
- function clampPreviewHeight(viewportHeight) {
150562
- if (viewportHeight == null) return 480;
150563
- return Math.max(320, Math.min(viewportHeight - 112, 920));
150564
- }
150565
151228
  function resolveRemotePreviewFrameStyle(frameHeight) {
150566
151229
  if (frameHeight == null) return void 0;
150567
151230
  return {
@@ -150601,7 +151264,7 @@ async function sharePreview(input) {
150601
151264
  }
150602
151265
  function PreviewPane({ file, preview, loading, error, className = "", frameHeight, isDarkMode }) {
150603
151266
  if (file.previewKind === "markdown") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150604
- className: `min-h-0 h-full flex-1 overflow-hidden ${className}`,
151267
+ className: `h-full min-h-0 flex-1 overflow-hidden ${className}`,
150605
151268
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownViewer, {
150606
151269
  markdown: file.content ?? "",
150607
151270
  path: file.path,
@@ -150621,7 +151284,7 @@ function PreviewPane({ file, preview, loading, error, className = "", frameHeigh
150621
151284
  children: "Preview unavailable."
150622
151285
  });
150623
151286
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150624
- className: `bg-background min-h-0 h-full overflow-hidden ${className}`,
151287
+ className: `bg-background h-full min-h-0 overflow-hidden ${className}`,
150625
151288
  style: resolveRemotePreviewFrameStyle(frameHeight),
150626
151289
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("iframe", {
150627
151290
  src: resolvePreviewFrameUrl(preview, isDarkMode),
@@ -150635,7 +151298,6 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150635
151298
  const isDarkMode = useDarkMode();
150636
151299
  const { data: files, isLoading, error } = archived ? useArchiveFilesSubscription(changeId) : useChangeFilesSubscription(changeId);
150637
151300
  const [selectedPath, setSelectedPath] = (0, import_react.useState)(null);
150638
- const [viewportNode, setViewportNode] = (0, import_react.useState)(null);
150639
151301
  const [mode, setMode] = (0, import_react.useState)("read");
150640
151302
  const [draftContent, setDraftContent] = (0, import_react.useState)({});
150641
151303
  const [savingPath, setSavingPath] = (0, import_react.useState)(null);
@@ -150644,10 +151306,6 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150644
151306
  const [previewErrorByPath, setPreviewErrorByPath] = (0, import_react.useState)({});
150645
151307
  const [previewMaximized, setPreviewMaximized] = (0, import_react.useState)(false);
150646
151308
  const [shareFeedback, setShareFeedback] = (0, import_react.useState)(null);
150647
- const viewportHeight = useViewportConstrainedHeight({
150648
- target: viewportNode,
150649
- enabled: viewportNode !== null
150650
- });
150651
151309
  const sortedEntries = (0, import_react.useMemo)(() => {
150652
151310
  if (providedFiles) return [...providedFiles];
150653
151311
  if (!files) return [];
@@ -150663,7 +151321,6 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150663
151321
  const readEnabled = !isPreviewOnlyFile(activeFile);
150664
151322
  const previewEnabled = !inStaticMode && canPreviewFile(activeFile);
150665
151323
  const hasDirtyDraft = !!activeFile && isTextLikeFile(activeFile) && activeDraft !== (activeFile.content ?? "");
150666
- const remotePreviewHeight = clampPreviewHeight(viewportHeight);
150667
151324
  (0, import_react.useEffect)(() => {
150668
151325
  if (!sortedEntries.length) {
150669
151326
  setSelectedPath(null);
@@ -150694,6 +151351,7 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150694
151351
  const nextDefaultMode = resolveDefaultMode(activeFile, inStaticMode);
150695
151352
  setMode((currentMode) => {
150696
151353
  if (currentMode === nextDefaultMode) return currentMode;
151354
+ if (currentMode === "read" && nextDefaultMode === "preview") return nextDefaultMode;
150697
151355
  if (currentMode === "edit" && editEnabled) return currentMode;
150698
151356
  if (currentMode === "preview" && previewEnabled) return currentMode;
150699
151357
  if (currentMode === "read" && readEnabled) return currentMode;
@@ -150788,13 +151446,12 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150788
151446
  };
150789
151447
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
150790
151448
  "data-tab-scroll-root": "true",
150791
- className: "scrollbar-thin scrollbar-track-transparent min-h-0 flex-1 overflow-auto",
151449
+ className: "min-h-0 flex-1 overflow-hidden",
150792
151450
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
150793
- className: "pr-1",
151451
+ className: "h-full min-h-0 pr-1",
150794
151452
  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,
151453
+ "data-folder-viewport": "",
151454
+ className: "flex h-full min-h-0 flex-col overflow-hidden",
150798
151455
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileExplorer, {
150799
151456
  entries: sortedEntries,
150800
151457
  selectedPath,
@@ -150808,92 +151465,141 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150808
151465
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
150809
151466
  className: "flex min-h-0 flex-1 flex-col",
150810
151467
  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",
151468
+ "data-folder-toolbar": "",
151469
+ className: "border-border/60 bg-muted/20 flex flex-wrap items-center gap-3 border-b px-3 py-2",
150812
151470
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonGroup, {
150813
151471
  value: mode,
150814
151472
  onChange: setMode,
151473
+ presentation: "icon-only",
151474
+ className: "min-w-0",
150815
151475
  options: [
150816
151476
  {
150817
151477
  value: "read",
150818
151478
  label: "Read",
151479
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollText, { className: "h-3.5 w-3.5" }),
151480
+ ariaLabel: "Read",
151481
+ tooltip: "Read",
150819
151482
  disabled: !readEnabled
150820
151483
  },
150821
151484
  {
150822
151485
  value: "edit",
150823
151486
  label: "Edit",
151487
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilePenLine, { className: "h-3.5 w-3.5" }),
151488
+ ariaLabel: "Edit",
151489
+ tooltip: "Edit",
150824
151490
  disabled: !editEnabled
150825
151491
  },
150826
151492
  {
150827
151493
  value: "preview",
150828
151494
  label: "Preview",
151495
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Eye, { className: "h-3.5 w-3.5" }),
151496
+ ariaLabel: "Preview",
151497
+ tooltip: "Preview",
150829
151498
  disabled: !previewEnabled
150830
151499
  }
150831
151500
  ]
150832
151501
  }), /* @__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, {
151502
+ "data-folder-toolbar-actions": "",
151503
+ className: "ml-auto flex min-w-0 max-w-full flex-wrap items-center justify-end gap-2",
151504
+ children: mode === "edit" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151505
+ content: "Revert",
151506
+ delay: 0,
151507
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
150854
151508
  variant: "secondary",
150855
- size: "sm",
151509
+ size: "icon-sm",
151510
+ "aria-label": "Revert",
151511
+ title: "Revert",
151512
+ disabled: !hasDirtyDraft,
150856
151513
  onClick: () => {
150857
- setPreviewByPath((current) => {
150858
- const next = { ...current };
150859
- delete next[currentFile.path];
150860
- return next;
150861
- });
151514
+ if (!isTextLikeFile(activeFile)) return;
151515
+ setDraftContent((current) => ({
151516
+ ...current,
151517
+ [activeFile.path]: activeFile.content ?? ""
151518
+ }));
150862
151519
  },
150863
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCw, { className: "h-3.5 w-3.5" }), "Refresh"]
151520
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Undo2, { className: "h-3.5 w-3.5" })
151521
+ })
151522
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151523
+ content: "Save",
151524
+ delay: 0,
151525
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151526
+ variant: "primary",
151527
+ size: "icon-sm",
151528
+ "aria-label": "Save",
151529
+ title: "Save",
151530
+ disabled: !hasDirtyDraft || savingPath === currentFile.path,
151531
+ onClick: saveActiveDraft,
151532
+ 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" })
151533
+ })
151534
+ })] }) : mode === "preview" && canPreviewRemote(activeFile) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
151535
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151536
+ content: "Refresh",
151537
+ delay: 0,
151538
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151539
+ variant: "secondary",
151540
+ size: "icon-sm",
151541
+ "aria-label": "Refresh",
151542
+ title: "Refresh",
151543
+ onClick: () => {
151544
+ setPreviewByPath((current) => {
151545
+ const next = { ...current };
151546
+ delete next[currentFile.path];
151547
+ return next;
151548
+ });
151549
+ },
151550
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCw, { className: "h-3.5 w-3.5" })
151551
+ })
150864
151552
  }),
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"]
151553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151554
+ content: previewMaximized ? "Exit maximize" : "Maximize",
151555
+ delay: 0,
151556
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151557
+ variant: "secondary",
151558
+ size: "icon-sm",
151559
+ "aria-label": previewMaximized ? "Exit maximize" : "Maximize",
151560
+ title: previewMaximized ? "Exit maximize" : "Maximize",
151561
+ onClick: () => {
151562
+ setPreviewMaximized((current) => !current);
151563
+ },
151564
+ 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" })
151565
+ })
150872
151566
  }),
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"]
151567
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151568
+ content: "Download",
151569
+ delay: 0,
151570
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151571
+ variant: "secondary",
151572
+ size: "icon-sm",
151573
+ "aria-label": "Download",
151574
+ title: "Download",
151575
+ disabled: !previewDownloadUrl,
151576
+ onClick: () => {
151577
+ if (!previewDownloadUrl) return;
151578
+ triggerDownload(previewDownloadUrl, currentFile.path.split("/").pop() ?? "preview");
151579
+ },
151580
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Download, { className: "h-3.5 w-3.5" })
151581
+ })
150882
151582
  }),
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"]
151583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
151584
+ content: shareFeedback === "shared" ? "Shared" : shareFeedback === "copied" ? "Copied" : "Share",
151585
+ delay: 0,
151586
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
151587
+ variant: "secondary",
151588
+ size: "icon-sm",
151589
+ "aria-label": shareFeedback === "shared" ? "Shared" : shareFeedback === "copied" ? "Copied" : "Share",
151590
+ title: shareFeedback === "shared" ? "Shared" : shareFeedback === "copied" ? "Copied" : "Share",
151591
+ disabled: !previewShareUrl,
151592
+ onClick: () => {
151593
+ if (!previewShareUrl) return;
151594
+ sharePreview({
151595
+ url: previewShareUrl,
151596
+ title: currentFile.path
151597
+ }).then((shared) => {
151598
+ setShareFeedback(shared ? "shared" : "copied");
151599
+ });
151600
+ },
151601
+ 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" })
151602
+ })
150897
151603
  })
150898
151604
  ] }) : null
150899
151605
  })]
@@ -150907,7 +151613,6 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150907
151613
  preview,
150908
151614
  loading: previewLoadingPath === currentFile.path,
150909
151615
  error: previewError,
150910
- frameHeight: canPreviewRemote(currentFile) ? remotePreviewHeight : void 0,
150911
151616
  isDarkMode
150912
151617
  })
150913
151618
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileExplorerCodeEditor, {
@@ -150939,7 +151644,7 @@ function FolderEditorViewer({ changeId, archived = false, files: providedFiles }
150939
151644
  contentClassName: "px-3 py-3",
150940
151645
  maxHeight: "96vh",
150941
151646
  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",
151647
+ className: "flex h-[80vh] max-h-[88vh] min-h-[420px] min-w-0 flex-col overflow-hidden",
150943
151648
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PreviewPane, {
150944
151649
  file: activeFile,
150945
151650
  preview,
@@ -151164,6 +151869,8 @@ function GlobContent({ changeId, artifact, translationConfig }) {
151164
151869
  }
151165
151870
  function ArtifactOutputViewer({ changeId, artifact }) {
151166
151871
  const { data: config } = useConfigSubscription();
151872
+ const { data: globalSettings } = useGlobalSettingsSubscription();
151873
+ const translationConfig = resolveDocumentTranslationConfig(config?.translation, globalSettings);
151167
151874
  if (artifact.files) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
151168
151875
  className: "flex min-h-0 flex-1 flex-col",
151169
151876
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -151171,25 +151878,26 @@ function ArtifactOutputViewer({ changeId, artifact }) {
151171
151878
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactFilesDocumentShell, {
151172
151879
  artifact,
151173
151880
  files: artifact.files,
151174
- translationConfig: config?.translation
151881
+ translationConfig
151175
151882
  })
151176
151883
  })
151177
151884
  });
151178
151885
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LiveArtifactOutputViewer, {
151179
151886
  changeId,
151180
151887
  artifact,
151181
- translationConfig: config?.translation
151888
+ translationConfig
151182
151889
  });
151183
151890
  }
151184
151891
  function ContentFallbackViewer({ fallback }) {
151185
151892
  const { data: config } = useConfigSubscription();
151893
+ const { data: globalSettings } = useGlobalSettingsSubscription();
151186
151894
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
151187
151895
  className: "flex min-h-0 flex-1 flex-col",
151188
151896
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
151189
151897
  className: "min-h-0 flex-1",
151190
151898
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FallbackDocumentShell, {
151191
151899
  fallback,
151192
- translationConfig: config?.translation
151900
+ translationConfig: resolveDocumentTranslationConfig(config?.translation, globalSettings)
151193
151901
  })
151194
151902
  })
151195
151903
  });
@@ -151786,6 +152494,89 @@ function renderAnsiLine(line) {
151786
152494
  return parts.length > 0 ? parts : line;
151787
152495
  }
151788
152496
  //#endregion
152497
+ //#region src/components/scroll-spy.ts
152498
+ function hasVerticalScrollBehavior(overflowY) {
152499
+ return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
152500
+ }
152501
+ function findVerticalScrollContainer(node, options = {}) {
152502
+ const { allowNonScrollable = false } = options;
152503
+ let current = node?.parentElement ?? null;
152504
+ while (current) {
152505
+ if (hasVerticalScrollBehavior(window.getComputedStyle(current).overflowY)) {
152506
+ if (allowNonScrollable || current.scrollHeight > current.clientHeight) return current;
152507
+ }
152508
+ current = current.parentElement;
152509
+ }
152510
+ return null;
152511
+ }
152512
+ function scrollViewportBounds(root) {
152513
+ if (root) {
152514
+ const rect = root.getBoundingClientRect();
152515
+ return {
152516
+ top: rect.top,
152517
+ bottom: rect.bottom
152518
+ };
152519
+ }
152520
+ return {
152521
+ top: 0,
152522
+ bottom: window.innerHeight
152523
+ };
152524
+ }
152525
+ function measureAvailableViewportHeight(node, root = findVerticalScrollContainer(node, { allowNonScrollable: true })) {
152526
+ if (typeof window === "undefined" || !node) return null;
152527
+ const nodeRect = node.getBoundingClientRect();
152528
+ const viewport = scrollViewportBounds(root);
152529
+ return Math.max(Math.floor(viewport.bottom - Math.max(nodeRect.top, viewport.top)), 0);
152530
+ }
152531
+ function useViewportConstrainedHeight({ target, enabled = true }) {
152532
+ const [height, setHeight] = (0, import_react.useState)(null);
152533
+ (0, import_react.useLayoutEffect)(() => {
152534
+ if (!enabled || typeof window === "undefined") {
152535
+ setHeight(null);
152536
+ return;
152537
+ }
152538
+ if (!target) {
152539
+ setHeight(null);
152540
+ return;
152541
+ }
152542
+ let resizeObserver = null;
152543
+ let scrollRoot = null;
152544
+ let scrollTarget = window;
152545
+ const setConstrainedHeight = (nextHeight) => {
152546
+ setHeight((currentHeight) => currentHeight === nextHeight ? currentHeight : nextHeight);
152547
+ };
152548
+ const bindScrollRoot = (nextRoot) => {
152549
+ if (scrollRoot === nextRoot) return;
152550
+ scrollTarget.removeEventListener("scroll", handleUpdate);
152551
+ if (resizeObserver && scrollRoot) resizeObserver.unobserve(scrollRoot);
152552
+ scrollRoot = nextRoot;
152553
+ scrollTarget = nextRoot ?? window;
152554
+ scrollTarget.addEventListener("scroll", handleUpdate, { passive: true });
152555
+ if (resizeObserver && scrollRoot) resizeObserver.observe(scrollRoot);
152556
+ };
152557
+ const handleUpdate = () => {
152558
+ const nextRoot = findVerticalScrollContainer(target, { allowNonScrollable: true });
152559
+ bindScrollRoot(nextRoot);
152560
+ setConstrainedHeight(measureAvailableViewportHeight(target, nextRoot));
152561
+ };
152562
+ if (typeof ResizeObserver !== "undefined") {
152563
+ resizeObserver = new ResizeObserver(() => {
152564
+ handleUpdate();
152565
+ });
152566
+ resizeObserver.observe(target);
152567
+ if (target.parentElement) resizeObserver.observe(target.parentElement);
152568
+ }
152569
+ handleUpdate();
152570
+ window.addEventListener("resize", handleUpdate);
152571
+ return () => {
152572
+ window.removeEventListener("resize", handleUpdate);
152573
+ scrollTarget.removeEventListener("scroll", handleUpdate);
152574
+ resizeObserver?.disconnect();
152575
+ };
152576
+ }, [enabled, target]);
152577
+ return height;
152578
+ }
152579
+ //#endregion
151789
152580
  //#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
152581
  function useControlled({ controlled, default: defaultProp, name, state = "value" }) {
151791
152582
  const { current: isControlled } = import_react.useRef(controlled !== void 0);
@@ -158633,6 +159424,8 @@ function SpecView() {
158633
159424
  const { data: spec, isLoading } = useSpecSubscription(specId);
158634
159425
  const { data: rawMarkdown, isLoading: isRawLoading } = useSpecRawSubscription(specId);
158635
159426
  const { data: config } = useConfigSubscription();
159427
+ const { data: globalSettings } = useGlobalSettingsSubscription();
159428
+ const translationConfig = (0, import_react.useMemo)(() => resolveDocumentTranslationConfig(config?.translation, globalSettings), [config?.translation, globalSettings]);
158636
159429
  const validation = null;
158637
159430
  if (isLoading && !spec || isRawLoading && !rawMarkdown) {
158638
159431
  if (handoff) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -158678,7 +159471,7 @@ function SpecView() {
158678
159471
  spec,
158679
159472
  rawMarkdown: rawMarkdown ?? "",
158680
159473
  validation,
158681
- translationConfig: config?.translation
159474
+ translationConfig
158682
159475
  });
158683
159476
  }
158684
159477
  function SpecContent({ spec, rawMarkdown, validation, translationConfig }) {