@agent-native/toolkit 0.14.3 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/agent-native.eject.json +19 -1
  2. package/dist/chat-history.css +3 -1
  3. package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
  4. package/dist/composer/ComposerPlusMenu.js +131 -35
  5. package/dist/composer/ComposerPlusMenu.js.map +1 -1
  6. package/dist/composer/MentionPopover.d.ts.map +1 -1
  7. package/dist/composer/MentionPopover.js +40 -5
  8. package/dist/composer/MentionPopover.js.map +1 -1
  9. package/dist/composer/PastedTextChip.d.ts.map +1 -1
  10. package/dist/composer/PastedTextChip.js +26 -2
  11. package/dist/composer/PastedTextChip.js.map +1 -1
  12. package/dist/composer/PastedTextChip.spec.d.ts +2 -0
  13. package/dist/composer/PastedTextChip.spec.d.ts.map +1 -0
  14. package/dist/composer/PastedTextChip.spec.js +42 -0
  15. package/dist/composer/PastedTextChip.spec.js.map +1 -0
  16. package/dist/composer/PromptComposer.d.ts.map +1 -1
  17. package/dist/composer/PromptComposer.js +26 -5
  18. package/dist/composer/PromptComposer.js.map +1 -1
  19. package/dist/composer/TiptapComposer.d.ts +5 -3
  20. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  21. package/dist/composer/TiptapComposer.js +289 -79
  22. package/dist/composer/TiptapComposer.js.map +1 -1
  23. package/dist/composer/TiptapComposer.spec.js +57 -0
  24. package/dist/composer/TiptapComposer.spec.js.map +1 -1
  25. package/dist/composer/VoiceButton.d.ts.map +1 -1
  26. package/dist/composer/VoiceButton.js +24 -5
  27. package/dist/composer/VoiceButton.js.map +1 -1
  28. package/dist/composer/runtime-adapters.d.ts +1 -0
  29. package/dist/composer/runtime-adapters.d.ts.map +1 -1
  30. package/dist/composer/runtime-adapters.js +8 -1
  31. package/dist/composer/runtime-adapters.js.map +1 -1
  32. package/dist/composer/runtime-adapters.spec.js +15 -0
  33. package/dist/composer/runtime-adapters.spec.js.map +1 -1
  34. package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  35. package/dist/composer/useRealtimeVoiceMode.js +204 -68
  36. package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
  37. package/dist/composer/useRealtimeVoiceMode.spec.js +27 -1
  38. package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -1
  39. package/dist/context-ui/ContextMeter.d.ts +3 -2
  40. package/dist/context-ui/ContextMeter.d.ts.map +1 -1
  41. package/dist/context-ui/ContextMeter.js +23 -3
  42. package/dist/context-ui/ContextMeter.js.map +1 -1
  43. package/dist/context-ui/ContextMeter.spec.d.ts +2 -0
  44. package/dist/context-ui/ContextMeter.spec.d.ts.map +1 -0
  45. package/dist/context-ui/ContextMeter.spec.js +52 -0
  46. package/dist/context-ui/ContextMeter.spec.js.map +1 -0
  47. package/dist/context-ui/ContextSegmentRow.d.ts +5 -3
  48. package/dist/context-ui/ContextSegmentRow.d.ts.map +1 -1
  49. package/dist/context-ui/ContextSegmentRow.js +74 -7
  50. package/dist/context-ui/ContextSegmentRow.js.map +1 -1
  51. package/dist/context-ui/ContextTreemap.d.ts +3 -2
  52. package/dist/context-ui/ContextTreemap.d.ts.map +1 -1
  53. package/dist/context-ui/ContextTreemap.js +5 -2
  54. package/dist/context-ui/ContextTreemap.js.map +1 -1
  55. package/dist/context-ui/ContextXRayPanel.d.ts +3 -2
  56. package/dist/context-ui/ContextXRayPanel.d.ts.map +1 -1
  57. package/dist/context-ui/ContextXRayPanel.js +112 -23
  58. package/dist/context-ui/ContextXRayPanel.js.map +1 -1
  59. package/dist/context-ui/types.d.ts +2 -0
  60. package/dist/context-ui/types.d.ts.map +1 -1
  61. package/dist/context-ui/types.js +7 -1
  62. package/dist/context-ui/types.js.map +1 -1
  63. package/dist/design-system/default-adapter.d.ts.map +1 -1
  64. package/dist/design-system/default-adapter.js +4 -1
  65. package/dist/design-system/default-adapter.js.map +1 -1
  66. package/dist/design-system/default-adapter.spec.js +12 -0
  67. package/dist/design-system/default-adapter.spec.js.map +1 -1
  68. package/dist/design-system/types.d.ts +2 -0
  69. package/dist/design-system/types.d.ts.map +1 -1
  70. package/dist/provider.d.ts.map +1 -1
  71. package/dist/provider.js +5 -2
  72. package/dist/provider.js.map +1 -1
  73. package/dist/sharing/SharePrimitives.d.ts +31 -0
  74. package/dist/sharing/SharePrimitives.d.ts.map +1 -0
  75. package/dist/sharing/SharePrimitives.js +45 -0
  76. package/dist/sharing/SharePrimitives.js.map +1 -0
  77. package/dist/sharing/index.d.ts +1 -0
  78. package/dist/sharing/index.d.ts.map +1 -1
  79. package/dist/sharing/index.js +1 -0
  80. package/dist/sharing/index.js.map +1 -1
  81. package/package.json +2 -2
  82. package/src/chat-history.css +3 -1
  83. package/src/composer/ComposerPlusMenu.tsx +170 -52
  84. package/src/composer/MentionPopover.tsx +43 -7
  85. package/src/composer/PastedTextChip.spec.tsx +58 -0
  86. package/src/composer/PastedTextChip.tsx +35 -7
  87. package/src/composer/PromptComposer.tsx +28 -7
  88. package/src/composer/TiptapComposer.spec.ts +85 -0
  89. package/src/composer/TiptapComposer.tsx +338 -81
  90. package/src/composer/VoiceButton.tsx +36 -11
  91. package/src/composer/runtime-adapters.spec.tsx +19 -0
  92. package/src/composer/runtime-adapters.tsx +12 -2
  93. package/src/composer/useRealtimeVoiceMode.spec.ts +34 -0
  94. package/src/composer/useRealtimeVoiceMode.tsx +219 -69
  95. package/src/context-ui/ContextMeter.spec.tsx +73 -0
  96. package/src/context-ui/ContextMeter.tsx +34 -4
  97. package/src/context-ui/ContextSegmentRow.tsx +111 -22
  98. package/src/context-ui/ContextTreemap.tsx +7 -1
  99. package/src/context-ui/ContextXRayPanel.tsx +138 -24
  100. package/src/context-ui/types.ts +14 -0
  101. package/src/design-system/default-adapter.spec.tsx +15 -0
  102. package/src/design-system/default-adapter.tsx +5 -1
  103. package/src/design-system/types.ts +2 -0
  104. package/src/provider.tsx +9 -1
  105. package/src/sharing/SharePrimitives.tsx +185 -0
  106. package/src/sharing/index.ts +8 -0
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useState } from "react";
5
5
 
6
6
  import { cn } from "../utils.js";
7
7
  import { countLines, unwrapAttachmentEnvelope } from "./pasted-text.js";
8
+ import { useComposerRuntimeAdapters } from "./runtime-adapters.js";
8
9
 
9
10
  function readAttachmentText(attachment: Attachment): Promise<string> | string {
10
11
  if ("file" in attachment && attachment.file instanceof File) {
@@ -58,6 +59,9 @@ export function PastedTextChip({
58
59
  }: PastedTextChipProps) {
59
60
  const [open, setOpen] = useState(false);
60
61
  const { text, lines, chars } = usePastedAttachmentText(attachment);
62
+ const adapters = useComposerRuntimeAdapters();
63
+ const t = adapters.translate!;
64
+ const formatNumber = adapters.formatNumber!;
61
65
 
62
66
  const handleRemove = useCallback(
63
67
  (event: React.MouseEvent) => {
@@ -67,7 +71,17 @@ export function PastedTextChip({
67
71
  [attachment.id, onRemove],
68
72
  );
69
73
 
70
- const summary = lines > 0 ? `${lines} lines` : `${chars} chars`;
74
+ const lineSummary = t("agentChat.pastedText.lines", {
75
+ count: lines,
76
+ formattedCount: formatNumber(lines),
77
+ defaultValue: `${lines} lines`,
78
+ });
79
+ const charSummary = t("agentChat.pastedText.characters", {
80
+ count: chars,
81
+ formattedCount: formatNumber(chars),
82
+ defaultValue: `${chars} chars`,
83
+ });
84
+ const summary = lines > 0 ? lineSummary : charSummary;
71
85
 
72
86
  return (
73
87
  <PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
@@ -81,7 +95,9 @@ export function PastedTextChip({
81
95
  : "max-w-[260px] px-2.5 py-2 text-xs",
82
96
  onRemove && !compact && "pe-7",
83
97
  )}
84
- aria-label="Preview pasted text"
98
+ aria-label={t("agentChat.pastedText.preview", {
99
+ defaultValue: "Preview pasted text",
100
+ })}
85
101
  >
86
102
  <span
87
103
  className={cn(
@@ -94,7 +110,11 @@ export function PastedTextChip({
94
110
  />
95
111
  </span>
96
112
  <span className="min-w-0 flex-1">
97
- <span className="block truncate font-medium">Pasted text</span>
113
+ <span className="block truncate font-medium">
114
+ {t("agentChat.pastedText.title", {
115
+ defaultValue: "Pasted text",
116
+ })}
117
+ </span>
98
118
  <span className="block text-[11px] text-muted-foreground">
99
119
  {summary}
100
120
  </span>
@@ -104,7 +124,9 @@ export function PastedTextChip({
104
124
  role="button"
105
125
  tabIndex={-1}
106
126
  onClick={handleRemove}
107
- aria-label="Remove pasted text"
127
+ aria-label={t("agentChat.pastedText.remove", {
128
+ defaultValue: "Remove pasted text",
129
+ })}
108
130
  className={cn(
109
131
  "flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground",
110
132
  compact ? "" : "absolute end-1.5 top-1.5",
@@ -126,15 +148,21 @@ export function PastedTextChip({
126
148
  <div className="flex items-center justify-between gap-2 border-b border-border px-3 py-2">
127
149
  <div className="flex items-center gap-2 min-w-0">
128
150
  <IconClipboardText className="h-4 w-4 shrink-0 text-muted-foreground" />
129
- <span className="text-sm font-medium truncate">Pasted text</span>
151
+ <span className="text-sm font-medium truncate">
152
+ {t("agentChat.pastedText.title", {
153
+ defaultValue: "Pasted text",
154
+ })}
155
+ </span>
130
156
  <span className="text-[11px] text-muted-foreground shrink-0">
131
- {lines} lines · {chars} chars
157
+ {lineSummary} · {charSummary}
132
158
  </span>
133
159
  </div>
134
160
  <button
135
161
  type="button"
136
162
  onClick={() => setOpen(false)}
137
- aria-label="Close preview"
163
+ aria-label={t("agentChat.composer.closePreview", {
164
+ defaultValue: "Close preview",
165
+ })}
138
166
  className="flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground"
139
167
  >
140
168
  <IconX className="h-3.5 w-3.5" />
@@ -315,6 +315,7 @@ function ImagePreviewLightbox({
315
315
  alt: string;
316
316
  onClose: () => void;
317
317
  }) {
318
+ const t = useComposerRuntimeAdapters().translate!;
318
319
  useEffect(() => {
319
320
  const handler = (e: KeyboardEvent) => {
320
321
  if (e.key === "Escape") onClose();
@@ -330,7 +331,9 @@ function ImagePreviewLightbox({
330
331
  return (
331
332
  <div
332
333
  role="dialog"
333
- aria-label="Image preview"
334
+ aria-label={t("agentChat.composer.imagePreview", {
335
+ defaultValue: "Image preview",
336
+ })}
334
337
  onClick={onClose}
335
338
  className="fixed inset-0 z-[300] flex items-center justify-center bg-black/80 p-6 cursor-zoom-out"
336
339
  >
@@ -343,7 +346,9 @@ function ImagePreviewLightbox({
343
346
  <button
344
347
  type="button"
345
348
  onClick={onClose}
346
- aria-label="Close preview"
349
+ aria-label={t("agentChat.composer.closePreview", {
350
+ defaultValue: "Close preview",
351
+ })}
347
352
  className="absolute end-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border border-white/30 bg-black/40 text-white hover:bg-black/60"
348
353
  >
349
354
  <IconX className="h-4 w-4" />
@@ -361,6 +366,7 @@ function AttachmentChip({
361
366
  }) {
362
367
  const src = useMemo(() => getImageSrc(attachment), [attachment]);
363
368
  const [previewOpen, setPreviewOpen] = useState(false);
369
+ const t = useComposerRuntimeAdapters().translate!;
364
370
  useEffect(
365
371
  () => () => {
366
372
  if (src?.startsWith("blob:")) URL.revokeObjectURL(src);
@@ -378,7 +384,10 @@ function AttachmentChip({
378
384
  <button
379
385
  type="button"
380
386
  onClick={() => setPreviewOpen(true)}
381
- aria-label={`Preview ${attachment.name}`}
387
+ aria-label={t("agentChat.composer.previewAttachment", {
388
+ name: attachment.name,
389
+ defaultValue: `Preview ${attachment.name}`,
390
+ })}
382
391
  className="agent-composer-attachment-image group relative flex h-16 min-w-16 max-w-28 cursor-zoom-in items-center justify-center overflow-hidden rounded-lg border border-border/70 bg-muted/50"
383
392
  >
384
393
  <img
@@ -400,7 +409,10 @@ function AttachmentChip({
400
409
  onRemove(attachment.id);
401
410
  }
402
411
  }}
403
- aria-label={`Remove ${attachment.name}`}
412
+ aria-label={t("agentChat.composer.removeAttachment", {
413
+ name: attachment.name,
414
+ defaultValue: `Remove ${attachment.name}`,
415
+ })}
404
416
  className="absolute end-1 top-1 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full border border-border/60 bg-background/90 text-muted-foreground hover:text-foreground"
405
417
  >
406
418
  <IconX className="h-3 w-3" />
@@ -420,13 +432,17 @@ function AttachmentChip({
420
432
  return (
421
433
  <div className="agent-composer-attachment-chip group relative inline-flex max-w-[200px] items-center gap-2 rounded-md border border-border/70 bg-muted/50 px-2 py-1.5 text-xs">
422
434
  <div className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-background text-[9px] font-semibold uppercase text-muted-foreground">
423
- {attachment.name.split(".").pop() || "file"}
435
+ {attachment.name.split(".").pop() ||
436
+ t("agentChat.composer.file", { defaultValue: "file" })}
424
437
  </div>
425
438
  <span className="min-w-0 truncate font-medium">{attachment.name}</span>
426
439
  <button
427
440
  type="button"
428
441
  onClick={() => onRemove(attachment.id)}
429
- aria-label={`Remove ${attachment.name}`}
442
+ aria-label={t("agentChat.composer.removeAttachment", {
443
+ name: attachment.name,
444
+ defaultValue: `Remove ${attachment.name}`,
445
+ })}
430
446
  className="flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground"
431
447
  >
432
448
  <IconX className="h-3 w-3" />
@@ -506,6 +522,7 @@ function PromptComposerInner({
506
522
  composerRef,
507
523
  }: PromptComposerProps) {
508
524
  const adapters = useComposerRuntimeAdapters();
525
+ const t = adapters.translate!;
509
526
  const modelsAdapter = adapters.models!;
510
527
  const BuilderSetupCard = modelsAdapter.BuilderSetupCard;
511
528
  const BuilderSetupContent = modelsAdapter.BuilderSetupContent;
@@ -641,7 +658,11 @@ function PromptComposerInner({
641
658
  maxDocumentAttachmentBytes={maxDocumentAttachmentBytes}
642
659
  documentAttachmentLimitLabel={documentAttachmentLimitLabel}
643
660
  placeholder={
644
- gateComposer ? "Connect AI above to continue..." : placeholder
661
+ gateComposer
662
+ ? t("agentChat.composer.connectAbove", {
663
+ defaultValue: "Connect AI above to continue...",
664
+ })
665
+ : placeholder
645
666
  }
646
667
  initialText={initialText}
647
668
  initialTextKey={initialTextKey}
@@ -28,6 +28,31 @@ import {
28
28
  } from "./TiptapComposer.js";
29
29
 
30
30
  describe("createTiptapComposerExtensions", () => {
31
+ it("refreshes the rendered placeholder after a locale change", () => {
32
+ let placeholder = "Ask the agent...";
33
+ const element = document.createElement("div");
34
+ const editor = new Editor({
35
+ element,
36
+ extensions: createTiptapComposerExtensions(() => placeholder),
37
+ });
38
+
39
+ expect(
40
+ element
41
+ .querySelector(".is-editor-empty")
42
+ ?.getAttribute("data-placeholder"),
43
+ ).toBe("Ask the agent...");
44
+
45
+ placeholder = "Frag den Agenten...";
46
+ editor.view.dispatch(editor.state.tr.setSelection(editor.state.selection));
47
+
48
+ expect(
49
+ element
50
+ .querySelector(".is-editor-empty")
51
+ ?.getAttribute("data-placeholder"),
52
+ ).toBe("Frag den Agenten...");
53
+ editor.destroy();
54
+ });
55
+
31
56
  it("rejects a truthy editor after BFCache/remount destruction", () => {
32
57
  const editor = new Editor({
33
58
  element: document.createElement("div"),
@@ -68,6 +93,17 @@ describe("createTiptapComposerExtensions", () => {
68
93
  expect(compactComposerReasoningEffortLabel("medium")).toBe("Med");
69
94
  expect(compactComposerReasoningEffortLabel("minimal")).toBe("Min");
70
95
  expect(compactComposerReasoningEffortLabel("xhigh")).toBe("XHigh");
96
+
97
+ const translate = (key: string, options?: Record<string, unknown>) =>
98
+ key === "agentChat.composer.defaultModel"
99
+ ? "Standardmodell"
100
+ : key === "agentChat.composer.reasoningMediumShort"
101
+ ? "Mittel"
102
+ : String(options?.defaultValue ?? key);
103
+ expect(compactComposerModelName("auto", translate)).toBe("Standardmodell");
104
+ expect(compactComposerReasoningEffortLabel("medium", translate)).toBe(
105
+ "Mittel",
106
+ );
71
107
  });
72
108
 
73
109
  it("keeps the prompt composer schema minimal and restores legacy draft HTML", () => {
@@ -202,6 +238,14 @@ describe("createTiptapComposerExtensions", () => {
202
238
  attachmentCount: 1,
203
239
  }),
204
240
  ).toBe("Create an extension: Use the attached context.");
241
+ expect(
242
+ displayableComposerModeMessage({
243
+ messagePrefix: "Erstelle eine Erweiterung: ",
244
+ trimmedText: "",
245
+ attachmentCount: 1,
246
+ attachedContextFallback: "Verwende den angehängten Kontext.",
247
+ }),
248
+ ).toBe("Erstelle eine Erweiterung: Verwende den angehängten Kontext.");
205
249
  });
206
250
 
207
251
  it("detects oversized PDF attachments before submit", () => {
@@ -256,6 +300,47 @@ describe("createTiptapComposerExtensions", () => {
256
300
  ).toBeNull();
257
301
  });
258
302
 
303
+ it("localizes a custom multipart document cap", () => {
304
+ const file = new File(
305
+ [new Uint8Array(4 * 1024 * 1024 + 1)],
306
+ "reference.pdf",
307
+ { type: "application/pdf" },
308
+ );
309
+ let translatedOptions: Record<string, unknown> | undefined;
310
+
311
+ const error = getOversizedDocumentAttachmentError(
312
+ [
313
+ {
314
+ type: "document",
315
+ name: "reference.pdf",
316
+ contentType: "application/pdf",
317
+ file,
318
+ },
319
+ ],
320
+ {
321
+ maxBytes: 4 * 1024 * 1024,
322
+ label: "Präsentationsdateien",
323
+ translate: (key, options) => {
324
+ expect(key).toBe("agentChat.composer.documentTooLarge");
325
+ translatedOptions = options;
326
+ return `„${String(options?.name)}“ ist ${String(options?.size)} MB groß. ${String(options?.label)} sind auf ${String(options?.maxSize)} MB begrenzt.`;
327
+ },
328
+ },
329
+ );
330
+
331
+ expect(error).toBe(
332
+ "„reference.pdf“ ist 4.0 MB groß. Präsentationsdateien sind auf 4 MB begrenzt.",
333
+ );
334
+ expect(translatedOptions).toEqual(
335
+ expect.objectContaining({
336
+ name: "reference.pdf",
337
+ size: "4.0",
338
+ label: "Präsentationsdateien",
339
+ maxSize: "4",
340
+ }),
341
+ );
342
+ });
343
+
259
344
  it("maps Enter keybindings to immediate and queued submit intents", () => {
260
345
  const enter = {
261
346
  key: "Enter",