@agent-native/toolkit 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
  2. package/dist/composer/ComposerPlusMenu.js +131 -35
  3. package/dist/composer/ComposerPlusMenu.js.map +1 -1
  4. package/dist/composer/MentionPopover.d.ts.map +1 -1
  5. package/dist/composer/MentionPopover.js +40 -5
  6. package/dist/composer/MentionPopover.js.map +1 -1
  7. package/dist/composer/PastedTextChip.d.ts.map +1 -1
  8. package/dist/composer/PastedTextChip.js +26 -2
  9. package/dist/composer/PastedTextChip.js.map +1 -1
  10. package/dist/composer/PastedTextChip.spec.d.ts +2 -0
  11. package/dist/composer/PastedTextChip.spec.d.ts.map +1 -0
  12. package/dist/composer/PastedTextChip.spec.js +42 -0
  13. package/dist/composer/PastedTextChip.spec.js.map +1 -0
  14. package/dist/composer/PromptComposer.d.ts.map +1 -1
  15. package/dist/composer/PromptComposer.js +26 -5
  16. package/dist/composer/PromptComposer.js.map +1 -1
  17. package/dist/composer/TiptapComposer.d.ts +5 -3
  18. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  19. package/dist/composer/TiptapComposer.js +291 -80
  20. package/dist/composer/TiptapComposer.js.map +1 -1
  21. package/dist/composer/TiptapComposer.spec.js +60 -3
  22. package/dist/composer/TiptapComposer.spec.js.map +1 -1
  23. package/dist/composer/VoiceButton.d.ts.map +1 -1
  24. package/dist/composer/VoiceButton.js +24 -5
  25. package/dist/composer/VoiceButton.js.map +1 -1
  26. package/dist/composer/runtime-adapters.d.ts +1 -0
  27. package/dist/composer/runtime-adapters.d.ts.map +1 -1
  28. package/dist/composer/runtime-adapters.js +8 -1
  29. package/dist/composer/runtime-adapters.js.map +1 -1
  30. package/dist/composer/runtime-adapters.spec.js +15 -0
  31. package/dist/composer/runtime-adapters.spec.js.map +1 -1
  32. package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  33. package/dist/composer/useRealtimeVoiceMode.js +204 -68
  34. package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
  35. package/dist/composer/useRealtimeVoiceMode.spec.js +27 -1
  36. package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -1
  37. package/dist/context-ui/ContextMeter.d.ts +3 -2
  38. package/dist/context-ui/ContextMeter.d.ts.map +1 -1
  39. package/dist/context-ui/ContextMeter.js +23 -3
  40. package/dist/context-ui/ContextMeter.js.map +1 -1
  41. package/dist/context-ui/ContextMeter.spec.d.ts +2 -0
  42. package/dist/context-ui/ContextMeter.spec.d.ts.map +1 -0
  43. package/dist/context-ui/ContextMeter.spec.js +52 -0
  44. package/dist/context-ui/ContextMeter.spec.js.map +1 -0
  45. package/dist/context-ui/ContextSegmentRow.d.ts +5 -3
  46. package/dist/context-ui/ContextSegmentRow.d.ts.map +1 -1
  47. package/dist/context-ui/ContextSegmentRow.js +74 -7
  48. package/dist/context-ui/ContextSegmentRow.js.map +1 -1
  49. package/dist/context-ui/ContextTreemap.d.ts +3 -2
  50. package/dist/context-ui/ContextTreemap.d.ts.map +1 -1
  51. package/dist/context-ui/ContextTreemap.js +5 -2
  52. package/dist/context-ui/ContextTreemap.js.map +1 -1
  53. package/dist/context-ui/ContextXRayPanel.d.ts +3 -2
  54. package/dist/context-ui/ContextXRayPanel.d.ts.map +1 -1
  55. package/dist/context-ui/ContextXRayPanel.js +112 -23
  56. package/dist/context-ui/ContextXRayPanel.js.map +1 -1
  57. package/dist/context-ui/types.d.ts +2 -0
  58. package/dist/context-ui/types.d.ts.map +1 -1
  59. package/dist/context-ui/types.js +7 -1
  60. package/dist/context-ui/types.js.map +1 -1
  61. package/package.json +2 -2
  62. package/src/composer/ComposerPlusMenu.tsx +170 -52
  63. package/src/composer/MentionPopover.tsx +43 -7
  64. package/src/composer/PastedTextChip.spec.tsx +58 -0
  65. package/src/composer/PastedTextChip.tsx +35 -7
  66. package/src/composer/PromptComposer.tsx +28 -7
  67. package/src/composer/TiptapComposer.spec.ts +90 -3
  68. package/src/composer/TiptapComposer.tsx +341 -82
  69. package/src/composer/VoiceButton.tsx +36 -11
  70. package/src/composer/runtime-adapters.spec.tsx +19 -0
  71. package/src/composer/runtime-adapters.tsx +12 -2
  72. package/src/composer/useRealtimeVoiceMode.spec.ts +34 -0
  73. package/src/composer/useRealtimeVoiceMode.tsx +219 -69
  74. package/src/context-ui/ContextMeter.spec.tsx +73 -0
  75. package/src/context-ui/ContextMeter.tsx +34 -4
  76. package/src/context-ui/ContextSegmentRow.tsx +111 -22
  77. package/src/context-ui/ContextTreemap.tsx +7 -1
  78. package/src/context-ui/ContextXRayPanel.tsx +138 -24
  79. package/src/context-ui/types.ts +14 -0
@@ -148,7 +148,11 @@ function assetImageSource(payload: unknown): string | null {
148
148
  );
149
149
  }
150
150
 
151
- function assetTitle(payload: unknown, url: string): string {
151
+ function assetTitle(
152
+ payload: unknown,
153
+ url: string,
154
+ generatedImageLabel: string,
155
+ ): string {
152
156
  if (payload && typeof payload === "object") {
153
157
  const title = assetString((payload as AssetPickerPayload).title);
154
158
  if (title) return title;
@@ -157,9 +161,9 @@ function assetTitle(payload: unknown, url: string): string {
157
161
  }
158
162
  try {
159
163
  const name = new URL(url).pathname.split("/").filter(Boolean).pop();
160
- return name ? decodeURIComponent(name) : "Generated image";
164
+ return name ? decodeURIComponent(name) : generatedImageLabel;
161
165
  } catch {
162
- return "Generated image";
166
+ return generatedImageLabel;
163
167
  }
164
168
  }
165
169
 
@@ -196,6 +200,7 @@ function UploadOnlyAttachButton({
196
200
  onAttachmentError,
197
201
  }: Pick<ComposerPlusMenuProps, "onAttachmentError">) {
198
202
  const composerRuntime = useComposerRuntime();
203
+ const t = useComposerRuntimeAdapters().translate!;
199
204
  const inputRef = useRef<HTMLInputElement>(null);
200
205
  const handleFilesSelected = async (files: FileList | null) => {
201
206
  if (!files || files.length === 0) return;
@@ -205,7 +210,12 @@ function UploadOnlyAttachButton({
205
210
  );
206
211
  } catch (error) {
207
212
  onAttachmentError?.(
208
- formatAttachmentError(error, "Could not upload the selected file."),
213
+ formatAttachmentError(
214
+ error,
215
+ t("agentChat.composer.uploadFailed", {
216
+ defaultValue: "Could not upload the selected file.",
217
+ }),
218
+ ),
209
219
  );
210
220
  }
211
221
  };
@@ -228,14 +238,18 @@ function UploadOnlyAttachButton({
228
238
  <button
229
239
  type="button"
230
240
  className="shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/50"
231
- aria-label="Upload"
241
+ aria-label={t("agentChat.composer.upload", {
242
+ defaultValue: "Upload",
243
+ })}
232
244
  onClick={() => inputRef.current?.click()}
233
245
  >
234
246
  <IconPlus className="h-4 w-4" />
235
247
  </button>
236
248
  </span>
237
249
  </TooltipTrigger>
238
- <TooltipContent>Upload</TooltipContent>
250
+ <TooltipContent>
251
+ {t("agentChat.composer.upload", { defaultValue: "Upload" })}
252
+ </TooltipContent>
239
253
  </Tooltip>
240
254
  </>
241
255
  );
@@ -363,7 +377,12 @@ function ComposerPlusMenuFull({
363
377
  );
364
378
  } catch (error) {
365
379
  onAttachmentError?.(
366
- formatAttachmentError(error, "Could not upload the selected file."),
380
+ formatAttachmentError(
381
+ error,
382
+ t("agentChat.composer.uploadFailed", {
383
+ defaultValue: "Could not upload the selected file.",
384
+ }),
385
+ ),
367
386
  );
368
387
  }
369
388
  };
@@ -390,17 +409,30 @@ function ComposerPlusMenuFull({
390
409
  );
391
410
  if (!res.ok) {
392
411
  const body = await res.text().catch(() => "");
393
- throw new Error(body || `Upload failed (${res.status})`);
412
+ throw new Error(
413
+ body ||
414
+ t("agentChat.composer.skill.uploadFailedStatus", {
415
+ status: res.status,
416
+ defaultValue: `Upload failed (${res.status})`,
417
+ }),
418
+ );
394
419
  }
395
420
  setSkillUploadStatus({
396
421
  kind: "ok",
397
- message: `Skill "${skillUploadFileName || `${slug}/SKILL.md`}" added`,
422
+ message: t("agentChat.composer.skill.added", {
423
+ name: skillUploadFileName || `${slug}/SKILL.md`,
424
+ defaultValue: `Skill "${skillUploadFileName || `${slug}/SKILL.md`}" added`,
425
+ }),
398
426
  });
399
427
  window.setTimeout(() => setOpen(false), 1200);
400
428
  } catch (err: any) {
401
429
  setSkillUploadStatus({
402
430
  kind: "err",
403
- message: err?.message || "Failed to save skill file",
431
+ message:
432
+ err?.message ||
433
+ t("agentChat.composer.skill.saveFailed", {
434
+ defaultValue: "Failed to save skill file",
435
+ }),
404
436
  });
405
437
  } finally {
406
438
  setSkillUploadBusy(false);
@@ -413,11 +445,16 @@ function ComposerPlusMenuFull({
413
445
  desc: string;
414
446
  action: () => void;
415
447
  hoverAction?: () => void;
448
+ isSkill?: boolean;
416
449
  }[] = [
417
450
  {
418
451
  icon: <IconUpload className="h-3.5 w-3.5" />,
419
- label: "Upload File",
420
- desc: "Images, PDFs, text/code, JSON, CSV",
452
+ label: t("agentChat.composer.menu.uploadFile", {
453
+ defaultValue: "Upload File",
454
+ }),
455
+ desc: t("agentChat.composer.menu.uploadFileDescription", {
456
+ defaultValue: "Images, PDFs, text/code, JSON, CSV",
457
+ }),
421
458
  action: () => {
422
459
  setOpen(false);
423
460
  setTimeout(() => fileUploadRef.current?.click(), 0);
@@ -425,8 +462,12 @@ function ComposerPlusMenuFull({
425
462
  },
426
463
  {
427
464
  icon: <IconPhotoPlus className="h-3.5 w-3.5" />,
428
- label: "Generate Image",
429
- desc: "Open the Assets image picker",
465
+ label: t("agentChat.composer.menu.generateImage", {
466
+ defaultValue: "Generate Image",
467
+ }),
468
+ desc: t("agentChat.composer.menu.generateImageDescription", {
469
+ defaultValue: "Open the Assets image picker",
470
+ }),
430
471
  action: () => {
431
472
  setOpen(false);
432
473
  setAssetsPickerOpen(true);
@@ -434,8 +475,12 @@ function ComposerPlusMenuFull({
434
475
  },
435
476
  {
436
477
  icon: <IconClock className="h-3.5 w-3.5" />,
437
- label: "Schedule Task",
438
- desc: "Run something on a schedule",
478
+ label: t("agentChat.composer.menu.scheduleTask", {
479
+ defaultValue: "Schedule Task",
480
+ }),
481
+ desc: t("agentChat.composer.menu.scheduleTaskDescription", {
482
+ defaultValue: "Run something on a schedule",
483
+ }),
439
484
  action: () => {
440
485
  onSelectMode?.("job");
441
486
  setOpen(false);
@@ -443,8 +488,12 @@ function ComposerPlusMenuFull({
443
488
  },
444
489
  {
445
490
  icon: <IconBolt className="h-3.5 w-3.5" />,
446
- label: "Create Automation",
447
- desc: "Set up a when-X-do-Y rule",
491
+ label: t("agentChat.composer.menu.createAutomation", {
492
+ defaultValue: "Create Automation",
493
+ }),
494
+ desc: t("agentChat.composer.menu.createAutomationDescription", {
495
+ defaultValue: "Set up a when-X-do-Y rule",
496
+ }),
448
497
  action: () => {
449
498
  onSelectMode?.("automation");
450
499
  setOpen(false);
@@ -454,8 +503,12 @@ function ComposerPlusMenuFull({
454
503
  ? [
455
504
  {
456
505
  icon: <IconTool className="h-3.5 w-3.5" />,
457
- label: "Create Extension",
458
- desc: "Build a mini app extension",
506
+ label: t("agentChat.composer.menu.createExtension", {
507
+ defaultValue: "Create Extension",
508
+ }),
509
+ desc: t("agentChat.composer.menu.createExtensionDescription", {
510
+ defaultValue: "Build a mini app extension",
511
+ }),
459
512
  action: () => {
460
513
  onSelectMode?.("extension");
461
514
  setOpen(false);
@@ -467,8 +520,12 @@ function ComposerPlusMenuFull({
467
520
  ? [
468
521
  {
469
522
  icon: <IconPlugConnected className="h-3.5 w-3.5" />,
470
- label: t("mcpIntegrations.menuLabel"),
471
- desc: t("mcpIntegrations.menuDescription"),
523
+ label: t("agentChat.composer.menu.integrations", {
524
+ defaultValue: "Integrations",
525
+ }),
526
+ desc: t("agentChat.composer.menu.integrationsDescription", {
527
+ defaultValue: "Connect tools and services to the agent",
528
+ }),
472
529
  action: () => {
473
530
  setOpen(false);
474
531
  setMcpDialogOpen(true);
@@ -478,10 +535,15 @@ function ComposerPlusMenuFull({
478
535
  : []),
479
536
  {
480
537
  icon: <IconBulb className="h-3.5 w-3.5" />,
481
- label: "Create Skill",
482
- desc: "Teach the agent a new ability",
538
+ label: t("agentChat.composer.menu.createSkill", {
539
+ defaultValue: "Create Skill",
540
+ }),
541
+ desc: t("agentChat.composer.menu.createSkillDescription", {
542
+ defaultValue: "Teach the agent a new ability",
543
+ }),
483
544
  action: openSkillFlyout,
484
545
  hoverAction: openSkillFlyout,
546
+ isSkill: true,
485
547
  },
486
548
  ];
487
549
 
@@ -516,7 +578,9 @@ function ComposerPlusMenuFull({
516
578
  <PopoverTrigger asChild>
517
579
  <button
518
580
  type="button"
519
- aria-label="Add..."
581
+ aria-label={t("agentChat.composer.add", {
582
+ defaultValue: "Add...",
583
+ })}
520
584
  className="shrink-0 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/50 disabled:opacity-30 disabled:cursor-not-allowed"
521
585
  >
522
586
  <IconPlus className="h-4 w-4" />
@@ -524,7 +588,9 @@ function ComposerPlusMenuFull({
524
588
  </PopoverTrigger>
525
589
  </span>
526
590
  </TooltipTrigger>
527
- <TooltipContent>Add...</TooltipContent>
591
+ <TooltipContent>
592
+ {t("agentChat.composer.add", { defaultValue: "Add..." })}
593
+ </TooltipContent>
528
594
  </Tooltip>
529
595
  <PopoverContent
530
596
  side="top"
@@ -542,7 +608,7 @@ function ComposerPlusMenuFull({
542
608
  {view === "menu" && (
543
609
  <div className="py-1">
544
610
  {menuItems.map((item) => {
545
- const isSkill = item.label === "Create Skill";
611
+ const isSkill = item.isSkill === true;
546
612
  return (
547
613
  <div
548
614
  key={item.label}
@@ -619,10 +685,15 @@ function ComposerPlusMenuFull({
619
685
  </span>
620
686
  <div className="min-w-0">
621
687
  <div className="text-[12px] font-medium text-foreground">
622
- Create new skill
688
+ {t("agentChat.composer.skill.createNew", {
689
+ defaultValue: "Create new skill",
690
+ })}
623
691
  </div>
624
692
  <div className="mt-0.5 text-[10px] text-muted-foreground/60">
625
- Describe a skill and let the agent draft it
693
+ {t("agentChat.composer.skill.createDescription", {
694
+ defaultValue:
695
+ "Describe a skill and let the agent draft it",
696
+ })}
626
697
  </div>
627
698
  </div>
628
699
  </button>
@@ -639,10 +710,15 @@ function ComposerPlusMenuFull({
639
710
  </span>
640
711
  <div className="min-w-0">
641
712
  <div className="text-[12px] font-medium text-foreground">
642
- Upload skill file
713
+ {t("agentChat.composer.skill.uploadFile", {
714
+ defaultValue: "Upload skill file",
715
+ })}
643
716
  </div>
644
717
  <div className="mt-0.5 text-[10px] text-muted-foreground/60">
645
- Import an existing SKILL.md file
718
+ {t("agentChat.composer.skill.uploadDescription", {
719
+ defaultValue:
720
+ "Import an existing SKILL.md file",
721
+ })}
646
722
  </div>
647
723
  </div>
648
724
  </button>
@@ -662,20 +738,29 @@ function ComposerPlusMenuFull({
662
738
  className="mb-1.5 flex items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground"
663
739
  >
664
740
  <IconArrowLeft className="h-3 w-3 rtl:-scale-x-100" />
665
- Back
741
+ {t("agentChat.composer.skill.back", {
742
+ defaultValue: "Back",
743
+ })}
666
744
  </button>
667
745
  <label className="mb-1 block text-[11px] font-semibold text-foreground">
668
- Upload skill file
746
+ {t("agentChat.composer.skill.uploadFile", {
747
+ defaultValue: "Upload skill file",
748
+ })}
669
749
  </label>
670
750
  <p className="mb-2 text-[10px] leading-relaxed text-muted-foreground/60">
671
- Review the content from{" "}
672
- <span className="font-mono">
673
- {skillUploadFileName || "the selected file"}
674
- </span>{" "}
675
- before saving.
751
+ {t("agentChat.composer.skill.review", {
752
+ name:
753
+ skillUploadFileName ||
754
+ t("agentChat.composer.skill.selectedFile", {
755
+ defaultValue: "the selected file",
756
+ }),
757
+ defaultValue: `Review the content from ${skillUploadFileName || "the selected file"} before saving.`,
758
+ })}
676
759
  </p>
677
760
  <label className="mb-1 block text-[10px] font-medium text-muted-foreground">
678
- Skill name
761
+ {t("agentChat.composer.skill.name", {
762
+ defaultValue: "Skill name",
763
+ })}
679
764
  </label>
680
765
  <input
681
766
  value={skillUploadSlug}
@@ -684,14 +769,18 @@ function ComposerPlusMenuFull({
684
769
  placeholder="my-skill"
685
770
  />
686
771
  <p className="mb-2 text-[10px] text-muted-foreground/60">
687
- Saved at{" "}
772
+ {t("agentChat.composer.skill.savedAt", {
773
+ defaultValue: "Saved at",
774
+ })}{" "}
688
775
  <span className="font-mono">
689
776
  skills/{slugifyName(skillUploadSlug || "uploaded-skill")}
690
777
  /SKILL.md
691
778
  </span>
692
779
  </p>
693
780
  <label className="mb-1 block text-[10px] font-medium text-muted-foreground">
694
- Content
781
+ {t("agentChat.composer.skill.content", {
782
+ defaultValue: "Content",
783
+ })}
695
784
  </label>
696
785
  <textarea
697
786
  value={skillUploadContent}
@@ -717,7 +806,9 @@ function ComposerPlusMenuFull({
717
806
  onClick={() => setView("menu")}
718
807
  className="rounded-md px-3 py-1.5 text-[12px] font-medium text-muted-foreground hover:bg-accent/40"
719
808
  >
720
- Cancel
809
+ {t("agentChat.common.cancel", {
810
+ defaultValue: "Cancel",
811
+ })}
721
812
  </button>
722
813
  <button
723
814
  type="button"
@@ -732,7 +823,7 @@ function ComposerPlusMenuFull({
732
823
  {skillUploadBusy ? (
733
824
  <IconLoader2 className="h-3 w-3 animate-spin" />
734
825
  ) : (
735
- "Save"
826
+ t("agentChat.common.save", { defaultValue: "Save" })
736
827
  )}
737
828
  </button>
738
829
  </div>
@@ -766,6 +857,7 @@ function AssetsPickerModal({
766
857
  onOpenChange: (open: boolean) => void;
767
858
  }) {
768
859
  const adapters = useComposerRuntimeAdapters();
860
+ const t = adapters.translate!;
769
861
  const iframeRef = useRef<HTMLIFrameElement | null>(null);
770
862
  const standaloneWindowRef = useRef<Window | null>(null);
771
863
  const [pickerReady, setPickerReady] = useState(false);
@@ -862,14 +954,23 @@ function AssetsPickerModal({
862
954
 
863
955
  const url = assetImageSource(event.data.payload);
864
956
  if (!url) return;
865
- const title = assetTitle(event.data.payload, url);
957
+ const title = assetTitle(
958
+ event.data.payload,
959
+ url,
960
+ t("agentChat.composer.assets.generatedImage", {
961
+ defaultValue: "Generated image",
962
+ }),
963
+ );
866
964
  const assetId =
867
965
  event.data.payload && typeof event.data.payload === "object"
868
966
  ? assetString((event.data.payload as AssetPickerPayload).assetId)
869
967
  : null;
870
968
  adapters.agentChat!.setContextItem!({
871
969
  key: `asset-image:${assetId ?? url}`,
872
- title: `Image: ${title}`,
970
+ title: t("agentChat.composer.assets.contextTitle", {
971
+ title,
972
+ defaultValue: `Image: ${title}`,
973
+ }),
873
974
  context: assetContext(event.data.payload, url),
874
975
  });
875
976
  standaloneWindowRef.current = null;
@@ -894,6 +995,7 @@ function AssetsPickerModal({
894
995
  onOpenChange,
895
996
  open,
896
997
  standaloneHandoffId,
998
+ t,
897
999
  targetOrigin,
898
1000
  ]);
899
1001
 
@@ -927,13 +1029,17 @@ function AssetsPickerModal({
927
1029
  id="composer-assets-picker-title"
928
1030
  className="text-sm font-medium text-foreground"
929
1031
  >
930
- Generate image
1032
+ {t("agentChat.composer.assets.generateImage", {
1033
+ defaultValue: "Generate image",
1034
+ })}
931
1035
  </div>
932
1036
  <button
933
1037
  type="button"
934
1038
  onClick={() => onOpenChange(false)}
935
1039
  className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
936
- aria-label="Close image picker"
1040
+ aria-label={t("agentChat.composer.assets.closePicker", {
1041
+ defaultValue: "Close image picker",
1042
+ })}
937
1043
  >
938
1044
  <IconX className="h-4 w-4" />
939
1045
  </button>
@@ -941,7 +1047,10 @@ function AssetsPickerModal({
941
1047
  {externalPicker ? (
942
1048
  <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-8 text-center">
943
1049
  <div className="max-w-md text-sm text-muted-foreground">
944
- Open Assets in a new tab to sign in and choose an image securely.
1050
+ {t("agentChat.composer.assets.openSecurely", {
1051
+ defaultValue:
1052
+ "Open Assets in a new tab to sign in and choose an image securely.",
1053
+ })}
945
1054
  </div>
946
1055
  <a
947
1056
  href={standaloneUrl}
@@ -949,7 +1058,9 @@ function AssetsPickerModal({
949
1058
  onClick={openStandalonePicker}
950
1059
  className="inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:bg-primary/90"
951
1060
  >
952
- Open Assets image picker
1061
+ {t("agentChat.composer.assets.openPicker", {
1062
+ defaultValue: "Open Assets image picker",
1063
+ })}
953
1064
  </a>
954
1065
  </div>
955
1066
  ) : targetOrigin ? (
@@ -958,7 +1069,9 @@ function AssetsPickerModal({
958
1069
  <iframe
959
1070
  ref={iframeRef}
960
1071
  src={iframeUrl}
961
- title="Assets image picker"
1072
+ title={t("agentChat.composer.assets.pickerTitle", {
1073
+ defaultValue: "Assets image picker",
1074
+ })}
962
1075
  className={cn(
963
1076
  "absolute inset-0 h-full w-full border-0 bg-background transition-opacity duration-150",
964
1077
  pickerReady ? "opacity-100" : "pointer-events-none opacity-0",
@@ -974,7 +1087,9 @@ function AssetsPickerModal({
974
1087
  </div>
975
1088
  ) : (
976
1089
  <div className="flex min-h-0 flex-1 items-center justify-center p-8 text-center text-sm text-muted-foreground">
977
- The configured image picker URL is not valid.
1090
+ {t("agentChat.composer.assets.invalidUrl", {
1091
+ defaultValue: "The configured image picker URL is not valid.",
1092
+ })}
978
1093
  </div>
979
1094
  )}
980
1095
  </div>
@@ -984,11 +1099,14 @@ function AssetsPickerModal({
984
1099
  }
985
1100
 
986
1101
  function AssetsPickerLoadingSkeleton() {
1102
+ const t = useComposerRuntimeAdapters().translate!;
987
1103
  return (
988
1104
  <div
989
1105
  className="absolute inset-0 flex flex-col gap-5 p-5"
990
1106
  role="status"
991
- aria-label="Loading Assets picker"
1107
+ aria-label={t("agentChat.composer.assets.loadingPicker", {
1108
+ defaultValue: "Loading Assets picker",
1109
+ })}
992
1110
  >
993
1111
  <div className="flex items-center gap-3">
994
1112
  <div className="h-9 flex-1 animate-pulse rounded-md bg-muted" />
@@ -31,6 +31,7 @@ import {
31
31
  TooltipProvider,
32
32
  TooltipTrigger,
33
33
  } from "../ui/tooltip.js";
34
+ import { useComposerRuntimeAdapters } from "./runtime-adapters.js";
34
35
  import type { MentionItem, SkillResult, SlashCommand } from "./types.js";
35
36
 
36
37
  export interface MentionPopoverRef {
@@ -101,6 +102,7 @@ function CommandIcon({ icon }: { icon?: string }) {
101
102
  }
102
103
 
103
104
  function HintWithLink({ hint }: { hint: string }) {
105
+ const t = useComposerRuntimeAdapters().translate!;
104
106
  // If hint contains a URL, split it and render the URL as a link
105
107
  const urlMatch = hint.match(/(https?:\/\/\S+)/);
106
108
  if (!urlMatch) return <>{hint}</>;
@@ -115,7 +117,7 @@ function HintWithLink({ hint }: { hint: string }) {
115
117
  rel="noopener noreferrer"
116
118
  className="underline hover:text-foreground"
117
119
  >
118
- Learn more
120
+ {t("agentChat.mentions.learnMore", { defaultValue: "Learn more" })}
119
121
  </a>
120
122
  </>
121
123
  );
@@ -190,6 +192,30 @@ export const MentionPopover = forwardRef<
190
192
 
191
193
  const [selectedIndex, setSelectedIndex] = useState(0);
192
194
  const listRef = useRef<HTMLDivElement>(null);
195
+ const t = useComposerRuntimeAdapters().translate!;
196
+
197
+ const sectionLabel = (section: string) => {
198
+ switch (section) {
199
+ case "Agents":
200
+ return t("agentChat.mentions.sections.agents", {
201
+ defaultValue: "Agents",
202
+ });
203
+ case "Connected Agents":
204
+ return t("agentChat.mentions.sections.connectedAgents", {
205
+ defaultValue: "Connected Agents",
206
+ });
207
+ case "Files":
208
+ return t("agentChat.mentions.sections.files", {
209
+ defaultValue: "Files",
210
+ });
211
+ case "Other":
212
+ return t("agentChat.mentions.sections.other", {
213
+ defaultValue: "Other",
214
+ });
215
+ default:
216
+ return section;
217
+ }
218
+ };
193
219
 
194
220
  const itemCount =
195
221
  type === "@" ? mentionItems.length : commands.length + skills.length;
@@ -302,14 +328,20 @@ export const MentionPopover = forwardRef<
302
328
  <div className="px-3 py-4 text-center text-xs text-muted-foreground">
303
329
  {type === "@" ? (
304
330
  query ? (
305
- "No results found"
331
+ t("agentChat.mentions.noResults", {
332
+ defaultValue: "No results found",
333
+ })
306
334
  ) : (
307
- "Type to search..."
335
+ t("agentChat.mentions.typeToSearch", {
336
+ defaultValue: "Type to search...",
337
+ })
308
338
  )
309
339
  ) : hint ? (
310
340
  <HintWithLink hint={hint} />
311
341
  ) : (
312
- "No skills available"
342
+ t("agentChat.mentions.noSkills", {
343
+ defaultValue: "No skills available",
344
+ })
313
345
  )}
314
346
  </div>
315
347
  ) : (
@@ -321,7 +353,7 @@ export const MentionPopover = forwardRef<
321
353
  return groupedMentions.map((group) => (
322
354
  <div key={group.section}>
323
355
  <div className="px-2 pt-2 pb-1 text-[10px] font-medium text-muted-foreground/70 uppercase tracking-wider">
324
- {group.section}
356
+ {sectionLabel(group.section)}
325
357
  </div>
326
358
  {group.items.map((item) => {
327
359
  const idx = flatIndex++;
@@ -359,7 +391,9 @@ export const MentionPopover = forwardRef<
359
391
  {commands.length > 0 && (
360
392
  <div>
361
393
  <div className="px-2 pt-2 pb-1 text-[10px] font-medium text-muted-foreground/70 uppercase tracking-wider">
362
- Commands
394
+ {t("agentChat.mentions.commands", {
395
+ defaultValue: "Commands",
396
+ })}
363
397
  </div>
364
398
  {commands.map((cmd) => {
365
399
  const i = idx++;
@@ -395,7 +429,9 @@ export const MentionPopover = forwardRef<
395
429
  <div>
396
430
  {commands.length > 0 && (
397
431
  <div className="px-2 pt-2 pb-1 text-[10px] font-medium text-muted-foreground/70 uppercase tracking-wider">
398
- Skills
432
+ {t("agentChat.mentions.skills", {
433
+ defaultValue: "Skills",
434
+ })}
399
435
  </div>
400
436
  )}
401
437
  <TooltipProvider delayDuration={200}>
@@ -0,0 +1,58 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import type { Attachment } from "@assistant-ui/react";
4
+ import { act } from "react";
5
+ import { createRoot, type Root } from "react-dom/client";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+
8
+ import { PastedTextChip } from "./PastedTextChip.js";
9
+ import { ComposerRuntimeAdaptersProvider } from "./runtime-adapters.js";
10
+
11
+ describe("PastedTextChip", () => {
12
+ let container: HTMLDivElement;
13
+ let root: Root;
14
+
15
+ beforeEach(() => {
16
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
17
+ container = document.createElement("div");
18
+ document.body.appendChild(container);
19
+ root = createRoot(container);
20
+ });
21
+
22
+ afterEach(() => {
23
+ act(() => root.unmount());
24
+ container.remove();
25
+ vi.unstubAllGlobals();
26
+ });
27
+
28
+ it("formats pasted-text counts with the runtime locale formatter", async () => {
29
+ const text = Array.from({ length: 12_345 }, () => "line").join("\n");
30
+ const attachment = {
31
+ id: "pasted-1",
32
+ type: "file",
33
+ name: "pasted-text-1.txt",
34
+ content: [{ type: "text", text }],
35
+ } as Attachment;
36
+
37
+ await act(async () => {
38
+ root.render(
39
+ <ComposerRuntimeAdaptersProvider
40
+ adapters={{
41
+ formatNumber: (value) =>
42
+ new Intl.NumberFormat("de-DE").format(value),
43
+ translate: (key, options) =>
44
+ key === "agentChat.pastedText.lines"
45
+ ? `${String(options?.formattedCount)} lines`
46
+ : String(options?.defaultValue ?? key),
47
+ }}
48
+ >
49
+ <PastedTextChip attachment={attachment} />
50
+ </ComposerRuntimeAdaptersProvider>,
51
+ );
52
+ });
53
+
54
+ await vi.waitFor(() => {
55
+ expect(container.textContent).toContain("12.345 lines");
56
+ });
57
+ });
58
+ });