@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
@@ -0,0 +1,73 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import React, { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+
7
+ import { ContextMeterView } from "./ContextMeter.js";
8
+ import { fallbackContextTranslate } from "./types.js";
9
+
10
+ let container: HTMLDivElement;
11
+ let root: Root;
12
+
13
+ beforeEach(() => {
14
+ container = document.createElement("div");
15
+ document.body.appendChild(container);
16
+ root = createRoot(container);
17
+ });
18
+
19
+ afterEach(() => {
20
+ act(() => root.unmount());
21
+ container.remove();
22
+ });
23
+
24
+ const manifest = {
25
+ totalTokens: 300,
26
+ rawTokens: 300,
27
+ reclaimedTokens: 0,
28
+ tokenCountMethod: "exact" as const,
29
+ enforceable: true,
30
+ segments: [],
31
+ systemTokens: 100,
32
+ conversationTokens: 200,
33
+ };
34
+
35
+ describe("ContextMeterView localization", () => {
36
+ it("interpolates the providerless English fallback", () => {
37
+ expect(
38
+ fallbackContextTranslate("agentChat.contextMeter.summary", {
39
+ defaultValue: "Context {{percent}}% · {{totalTokens}}",
40
+ percent: 25,
41
+ totalTokens: "2.0k",
42
+ }),
43
+ ).toBe("Context 25% · 2.0k");
44
+ });
45
+
46
+ it("uses the supplied translator for its accessible label", () => {
47
+ const translate = (key: string, options: Record<string, unknown> = {}) => {
48
+ if (key === "agentChat.contextMeter.breakdown") return " AUFTEILUNG";
49
+ if (key === "agentChat.contextMeter.ariaLabel") {
50
+ return `Kontext ${options.percent}% ${options.totalTokens}${options.breakdown}`;
51
+ }
52
+ return String(options.defaultValue ?? key);
53
+ };
54
+
55
+ act(() => {
56
+ root.render(
57
+ <ContextMeterView
58
+ manifest={manifest}
59
+ contextWindow={1_200}
60
+ open={false}
61
+ onOpenChange={() => undefined}
62
+ translate={translate}
63
+ >
64
+ <div>Panel</div>
65
+ </ContextMeterView>,
66
+ );
67
+ });
68
+
69
+ expect(container.querySelector("button")?.getAttribute("aria-label")).toBe(
70
+ "Kontext 25% 300 AUFTEILUNG",
71
+ );
72
+ });
73
+ });
@@ -9,7 +9,11 @@ import {
9
9
  } from "../ui/tooltip.js";
10
10
  import { cn } from "../utils.js";
11
11
  import { formatContextTokens } from "./format.js";
12
- import type { ContextManifestViewData } from "./types.js";
12
+ import {
13
+ fallbackContextTranslate,
14
+ type ContextManifestViewData,
15
+ type ContextTranslate,
16
+ } from "./types.js";
13
17
 
14
18
  function ContextDonut({ pct, advisory }: { pct: number; advisory: boolean }) {
15
19
  const radius = 7.5;
@@ -50,12 +54,14 @@ export function ContextMeterView({
50
54
  open,
51
55
  onOpenChange,
52
56
  children,
57
+ translate = fallbackContextTranslate,
53
58
  }: {
54
59
  manifest: ContextManifestViewData;
55
60
  contextWindow: number;
56
61
  open: boolean;
57
62
  onOpenChange: (open: boolean) => void;
58
63
  children: ReactNode;
64
+ translate?: ContextTranslate;
59
65
  }) {
60
66
  const pct = Math.min(
61
67
  100,
@@ -70,7 +76,22 @@ export function ContextMeterView({
70
76
  <PopoverTrigger asChild>
71
77
  <button
72
78
  type="button"
73
- aria-label={`Context ${pct}%, ${formatContextTokens(manifest.totalTokens)}${systemTokens > 0 ? ` total: ${formatContextTokens(systemTokens)} system + ${formatContextTokens(conversationTokens)} conversation` : ""}. Open Context X-Ray.`}
79
+ aria-label={translate("agentChat.contextMeter.ariaLabel", {
80
+ defaultValue:
81
+ "Context {{percent}}%, {{totalTokens}}{{breakdown}}. Open Context X-Ray.",
82
+ percent: pct,
83
+ totalTokens: formatContextTokens(manifest.totalTokens),
84
+ breakdown:
85
+ systemTokens > 0
86
+ ? translate("agentChat.contextMeter.breakdown", {
87
+ defaultValue:
88
+ " total: {{systemTokens}} system + {{conversationTokens}} conversation",
89
+ systemTokens: formatContextTokens(systemTokens),
90
+ conversationTokens:
91
+ formatContextTokens(conversationTokens),
92
+ })
93
+ : "",
94
+ })}
74
95
  className={cn(
75
96
  "flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
76
97
  open && "bg-accent/60 text-foreground",
@@ -81,9 +102,18 @@ export function ContextMeterView({
81
102
  </PopoverTrigger>
82
103
  </TooltipTrigger>
83
104
  <TooltipContent>
84
- Context {pct}% · {formatContextTokens(manifest.totalTokens)}
105
+ {translate("agentChat.contextMeter.summary", {
106
+ defaultValue: "Context {{percent}}% · {{totalTokens}}",
107
+ percent: pct,
108
+ totalTokens: formatContextTokens(manifest.totalTokens),
109
+ })}
85
110
  {systemTokens > 0
86
- ? ` (${formatContextTokens(systemTokens)} system + ${formatContextTokens(conversationTokens)} conversation)`
111
+ ? translate("agentChat.contextMeter.summaryBreakdown", {
112
+ defaultValue:
113
+ " ({{systemTokens}} system + {{conversationTokens}} conversation)",
114
+ systemTokens: formatContextTokens(systemTokens),
115
+ conversationTokens: formatContextTokens(conversationTokens),
116
+ })
87
117
  : ""}
88
118
  </TooltipContent>
89
119
  </Tooltip>
@@ -4,15 +4,46 @@ import type { ReactNode } from "react";
4
4
  import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
5
5
  import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
6
6
  import { cn } from "../utils.js";
7
- import { contextStatusLabel, formatContextTokens } from "./format.js";
8
- import type { ContextSegmentViewData } from "./types.js";
7
+ import { formatContextTokens } from "./format.js";
8
+ import {
9
+ fallbackContextTranslate,
10
+ type ContextSegmentViewData,
11
+ type ContextTranslate,
12
+ } from "./types.js";
13
+
14
+ function statusLabel(
15
+ segment: ContextSegmentViewData,
16
+ translate: ContextTranslate,
17
+ ): string {
18
+ if (segment.protected)
19
+ return translate("agentChat.contextXray.status.protected", {
20
+ defaultValue: "Protected",
21
+ });
22
+ if (segment.status === "pinned")
23
+ return translate("agentChat.contextXray.status.pinned", {
24
+ defaultValue: "Pinned",
25
+ });
26
+ if (segment.status === "evicted")
27
+ return translate("agentChat.contextXray.status.evicted", {
28
+ defaultValue: "Evicted",
29
+ });
30
+ if (segment.status === "summarized")
31
+ return translate("agentChat.contextXray.status.summarized", {
32
+ defaultValue: "Summarized",
33
+ });
34
+ return translate("agentChat.contextXray.status.active", {
35
+ defaultValue: "Active",
36
+ });
37
+ }
9
38
 
10
39
  export function SegmentProvenancePopoverView({
11
40
  segment,
12
41
  children,
42
+ translate = fallbackContextTranslate,
13
43
  }: {
14
44
  segment: ContextSegmentViewData;
15
45
  children: ReactNode;
46
+ translate?: ContextTranslate;
16
47
  }) {
17
48
  return (
18
49
  <Popover>
@@ -21,7 +52,9 @@ export function SegmentProvenancePopoverView({
21
52
  <div className="space-y-2">
22
53
  <div>
23
54
  <div className="text-[11px] font-medium uppercase text-muted-foreground">
24
- Segment
55
+ {translate("agentChat.contextXray.segment", {
56
+ defaultValue: "Segment",
57
+ })}
25
58
  </div>
26
59
  <div className="mt-0.5 break-words text-xs text-foreground">
27
60
  {segment.label}
@@ -32,30 +65,46 @@ export function SegmentProvenancePopoverView({
32
65
  <span className="block font-medium text-foreground">
33
66
  {formatContextTokens(segment.tokenCount)}
34
67
  </span>
35
- tokens{segment.tokenMethod === "estimate" ? " estimated" : ""}
68
+ {translate("agentChat.contextXray.tokens", {
69
+ defaultValue: "tokens",
70
+ })}
71
+ {segment.tokenMethod === "estimate"
72
+ ? translate("agentChat.contextXray.estimatedPrefix", {
73
+ defaultValue: " estimated",
74
+ })
75
+ : ""}
36
76
  </div>
37
77
  <div>
38
78
  <span className="block font-medium text-foreground">
39
- {contextStatusLabel(segment)}
79
+ {statusLabel(segment, translate)}
40
80
  </span>
41
- current status
81
+ {translate("agentChat.contextXray.currentStatus", {
82
+ defaultValue: "current status",
83
+ })}
42
84
  </div>
43
85
  <div>
44
86
  <span className="block font-medium text-foreground">
45
87
  {segment.msgIndex ?? "-"}
46
88
  </span>
47
- message index
89
+ {translate("agentChat.contextXray.messageIndex", {
90
+ defaultValue: "message index",
91
+ })}
48
92
  </div>
49
93
  <div>
50
94
  <span className="block font-medium text-foreground">
51
95
  {segment.partIndex ?? "-"}
52
96
  </span>
53
- part index
97
+ {translate("agentChat.contextXray.partIndex", {
98
+ defaultValue: "part index",
99
+ })}
54
100
  </div>
55
101
  </div>
56
102
  {segment.protected ? (
57
103
  <div className="rounded-md border border-border bg-muted/40 px-2 py-1.5 text-[11px] text-muted-foreground">
58
- This segment is part of the active turn and cannot be evicted yet.
104
+ {translate("agentChat.contextXray.protectedDescription", {
105
+ defaultValue:
106
+ "This segment is part of the active turn and cannot be evicted yet.",
107
+ })}
59
108
  </div>
60
109
  ) : null}
61
110
  </div>
@@ -70,12 +119,14 @@ export function ContextSegmentRowView({
70
119
  onPin,
71
120
  onEvict,
72
121
  onRestore,
122
+ translate = fallbackContextTranslate,
73
123
  }: {
74
124
  segment: ContextSegmentViewData;
75
125
  advisory: boolean;
76
126
  onPin: () => void;
77
127
  onEvict: () => void;
78
128
  onRestore: () => void;
129
+ translate?: ContextTranslate;
79
130
  }) {
80
131
  const disabled = segment.protected || segment.status === "evicted";
81
132
  return (
@@ -91,11 +142,14 @@ export function ContextSegmentRowView({
91
142
  segment.status === "evicted" && "opacity-60",
92
143
  )}
93
144
  >
94
- <SegmentProvenancePopoverView segment={segment}>
145
+ <SegmentProvenancePopoverView segment={segment} translate={translate}>
95
146
  <button
96
147
  type="button"
97
- className="min-w-0 flex-1 text-left"
98
- aria-label={`Inspect ${segment.label}`}
148
+ className="min-w-0 flex-1 text-start"
149
+ aria-label={translate("agentChat.contextXray.inspect", {
150
+ defaultValue: "Inspect {{name}}",
151
+ name: segment.label,
152
+ })}
99
153
  >
100
154
  <div
101
155
  className={cn(
@@ -109,8 +163,15 @@ export function ContextSegmentRowView({
109
163
  <span>{formatContextTokens(segment.tokenCount)}</span>
110
164
  {segment.tokenMethod === "estimate" ? <span>~</span> : null}
111
165
  <span>·</span>
112
- <span>{contextStatusLabel(segment)}</span>
113
- {advisory ? <span>· advisory</span> : null}
166
+ <span>{statusLabel(segment, translate)}</span>
167
+ {advisory ? (
168
+ <span>
169
+ ·{" "}
170
+ {translate("agentChat.contextXray.advisory", {
171
+ defaultValue: "advisory",
172
+ })}
173
+ </span>
174
+ ) : null}
114
175
  </div>
115
176
  </button>
116
177
  </SegmentProvenancePopoverView>
@@ -122,7 +183,11 @@ export function ContextSegmentRowView({
122
183
  <IconLock className="h-3.5 w-3.5" />
123
184
  </span>
124
185
  </TooltipTrigger>
125
- <TooltipContent>Protected during active turn</TooltipContent>
186
+ <TooltipContent>
187
+ {translate("agentChat.contextXray.protectedDuringTurn", {
188
+ defaultValue: "Protected during active turn",
189
+ })}
190
+ </TooltipContent>
126
191
  </Tooltip>
127
192
  ) : segment.status === "evicted" ||
128
193
  segment.status === "summarized" ||
@@ -135,15 +200,25 @@ export function ContextSegmentRowView({
135
200
  className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground"
136
201
  aria-label={
137
202
  segment.status === "pinned"
138
- ? "Unpin segment"
139
- : "Restore segment"
203
+ ? translate("agentChat.contextXray.unpinSegment", {
204
+ defaultValue: "Unpin segment",
205
+ })
206
+ : translate("agentChat.contextXray.restoreSegment", {
207
+ defaultValue: "Restore segment",
208
+ })
140
209
  }
141
210
  >
142
211
  <IconRotate2 className="h-3.5 w-3.5" />
143
212
  </button>
144
213
  </TooltipTrigger>
145
214
  <TooltipContent>
146
- {segment.status === "pinned" ? "Unpin" : "Restore"}
215
+ {segment.status === "pinned"
216
+ ? translate("agentChat.contextXray.unpin", {
217
+ defaultValue: "Unpin",
218
+ })
219
+ : translate("agentChat.contextXray.restore", {
220
+ defaultValue: "Restore",
221
+ })}
147
222
  </TooltipContent>
148
223
  </Tooltip>
149
224
  ) : (
@@ -154,12 +229,18 @@ export function ContextSegmentRowView({
154
229
  type="button"
155
230
  onClick={onPin}
156
231
  className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground"
157
- aria-label="Pin segment"
232
+ aria-label={translate("agentChat.contextXray.pinSegment", {
233
+ defaultValue: "Pin segment",
234
+ })}
158
235
  >
159
236
  <IconPin className="h-3.5 w-3.5" />
160
237
  </button>
161
238
  </TooltipTrigger>
162
- <TooltipContent>Pin</TooltipContent>
239
+ <TooltipContent>
240
+ {translate("agentChat.contextXray.pin", {
241
+ defaultValue: "Pin",
242
+ })}
243
+ </TooltipContent>
163
244
  </Tooltip>
164
245
  <Tooltip>
165
246
  <TooltipTrigger asChild>
@@ -167,13 +248,21 @@ export function ContextSegmentRowView({
167
248
  type="button"
168
249
  onClick={onEvict}
169
250
  className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-destructive"
170
- aria-label="Evict segment"
251
+ aria-label={translate("agentChat.contextXray.evictSegment", {
252
+ defaultValue: "Evict segment",
253
+ })}
171
254
  >
172
255
  <IconX className="h-3.5 w-3.5" />
173
256
  </button>
174
257
  </TooltipTrigger>
175
258
  <TooltipContent>
176
- {advisory ? "Record eviction intent" : "Evict"}
259
+ {advisory
260
+ ? translate("agentChat.contextXray.recordEvictionIntent", {
261
+ defaultValue: "Record eviction intent",
262
+ })
263
+ : translate("agentChat.contextXray.evict", {
264
+ defaultValue: "Evict",
265
+ })}
177
266
  </TooltipContent>
178
267
  </Tooltip>
179
268
  </>
@@ -5,7 +5,9 @@ import { contextGroupFill, formatContextTokens } from "./format.js";
5
5
  import type {
6
6
  ContextSegmentViewData,
7
7
  ContextSystemSectionViewData,
8
+ ContextTranslate,
8
9
  } from "./types.js";
10
+ import { fallbackContextTranslate } from "./types.js";
9
11
 
10
12
  interface TreemapDatum {
11
13
  name: string;
@@ -52,10 +54,12 @@ export function ContextTreemapView({
52
54
  segments,
53
55
  systemSections = [],
54
56
  onSelect,
57
+ translate = fallbackContextTranslate,
55
58
  }: {
56
59
  segments: ContextSegmentViewData[];
57
60
  systemSections?: ContextSystemSectionViewData[];
58
61
  onSelect?: (segmentId: string) => void;
62
+ translate?: ContextTranslate;
59
63
  }) {
60
64
  const containerRef = useRef<HTMLDivElement>(null);
61
65
  const [chartWidth, setChartWidth] = useState(0);
@@ -99,7 +103,9 @@ export function ContextTreemapView({
99
103
  if (data.length === 0) {
100
104
  return (
101
105
  <div className="flex h-52 items-center justify-center rounded-md bg-muted/30 text-xs text-muted-foreground">
102
- No active segments
106
+ {translate("agentChat.contextXray.noActiveSegments", {
107
+ defaultValue: "No active segments",
108
+ })}
103
109
  </div>
104
110
  );
105
111
  }