@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
@@ -66,6 +66,7 @@ export function isRealtimeVoiceSetupRequired(
66
66
 
67
67
  export function VoiceButton({ voice, isMac, disabled }: VoiceButtonProps) {
68
68
  const adapters = useComposerRuntimeAdapters();
69
+ const t = adapters.translate!;
69
70
  const { state, start, stop, supported } = voice;
70
71
  const realtimeVoice = useRealtimeVoiceModeOptional();
71
72
  const realtimeCopy = useRealtimeVoiceModeCopy();
@@ -144,10 +145,17 @@ export function VoiceButton({ voice, isMac, disabled }: VoiceButtonProps) {
144
145
  }
145
146
 
146
147
  const label = recording
147
- ? "Stop recording"
148
+ ? t("agentChat.voice.dictation.stopRecording", {
149
+ defaultValue: "Stop recording",
150
+ })
148
151
  : transcribing
149
- ? "Transcribing…"
150
- : `Dictate (${isMac ? "Cmd Shift M" : "Ctrl Shift M"})`;
152
+ ? t("agentChat.voice.dictation.transcribing", {
153
+ defaultValue: "Transcribing…",
154
+ })
155
+ : t("agentChat.voice.dictation.start", {
156
+ shortcut: isMac ? "⌘⇧M" : "Ctrl+Shift+M",
157
+ defaultValue: `Dictate (${isMac ? "⌘⇧M" : "Ctrl+Shift+M"})`,
158
+ });
151
159
 
152
160
  const onClick = () => {
153
161
  if (recording) stop();
@@ -190,6 +198,7 @@ export interface VoiceRecordingOverlayProps {
190
198
  export function VoiceRecordingOverlay({ voice }: VoiceRecordingOverlayProps) {
191
199
  const { state, amplitude, durationMs, errorMessage, cancel } = voice;
192
200
  const { dismissError, start } = voice;
201
+ const t = useComposerRuntimeAdapters().translate!;
193
202
 
194
203
  if (state === "error" && errorMessage) {
195
204
  return (
@@ -207,12 +216,16 @@ export function VoiceRecordingOverlay({ voice }: VoiceRecordingOverlayProps) {
207
216
  void start();
208
217
  }}
209
218
  className="shrink-0 cursor-pointer rounded px-1.5 py-0.5 text-[11px] font-medium text-red-500 hover:bg-red-500/20"
210
- aria-label="Try again"
219
+ aria-label={t("agentChat.common.retry", {
220
+ defaultValue: "Try again",
221
+ })}
211
222
  >
212
- Try again
223
+ {t("agentChat.common.retry", { defaultValue: "Try again" })}
213
224
  </button>
214
225
  </TooltipTrigger>
215
- <TooltipContent>Try again</TooltipContent>
226
+ <TooltipContent>
227
+ {t("agentChat.common.retry", { defaultValue: "Try again" })}
228
+ </TooltipContent>
216
229
  </Tooltip>
217
230
  <Tooltip>
218
231
  <TooltipTrigger asChild>
@@ -220,12 +233,16 @@ export function VoiceRecordingOverlay({ voice }: VoiceRecordingOverlayProps) {
220
233
  type="button"
221
234
  onClick={dismissError}
222
235
  className="shrink-0 flex h-4 w-4 cursor-pointer items-center justify-center rounded text-red-500 hover:bg-red-500/20"
223
- aria-label="Dismiss"
236
+ aria-label={t("agentChat.common.dismiss", {
237
+ defaultValue: "Dismiss",
238
+ })}
224
239
  >
225
240
  <IconX className="h-3 w-3" />
226
241
  </button>
227
242
  </TooltipTrigger>
228
- <TooltipContent>Dismiss</TooltipContent>
243
+ <TooltipContent>
244
+ {t("agentChat.common.dismiss", { defaultValue: "Dismiss" })}
245
+ </TooltipContent>
229
246
  </Tooltip>
230
247
  </div>
231
248
  );
@@ -245,18 +262,26 @@ export function VoiceRecordingOverlay({ voice }: VoiceRecordingOverlayProps) {
245
262
  type="button"
246
263
  onClick={cancel}
247
264
  className="shrink-0 flex h-5 w-5 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/40"
248
- aria-label="Cancel recording"
265
+ aria-label={t("agentChat.voice.dictation.cancelRecording", {
266
+ defaultValue: "Cancel recording",
267
+ })}
249
268
  >
250
269
  <IconX className="h-3 w-3" />
251
270
  </button>
252
271
  </TooltipTrigger>
253
- <TooltipContent>Cancel (Esc)</TooltipContent>
272
+ <TooltipContent>
273
+ {t("agentChat.voice.dictation.cancel", {
274
+ defaultValue: "Cancel (Esc)",
275
+ })}
276
+ </TooltipContent>
254
277
  </Tooltip>
255
278
 
256
279
  <div className="flex-1 flex items-center gap-[2px] min-w-0 h-4">
257
280
  {state === "transcribing" ? (
258
281
  <span className="text-[11px] text-muted-foreground">
259
- Transcribing…
282
+ {t("agentChat.voice.dictation.transcribing", {
283
+ defaultValue: "Transcribing…",
284
+ })}
260
285
  </span>
261
286
  ) : (
262
287
  <AmplitudeBars amplitude={amplitude} />
@@ -25,6 +25,25 @@ afterEach(() => {
25
25
  });
26
26
 
27
27
  describe("ComposerRuntimeAdaptersProvider", () => {
28
+ it("interpolates fallback translations without a provider", () => {
29
+ let translated = "";
30
+
31
+ function Consumer() {
32
+ const runtime = useComposerRuntimeAdapters();
33
+ translated = runtime.translate!("attachment.tooLarge", {
34
+ defaultValue: '"{{name}}" is {{size}} MB (max {{maxSize}} MB)',
35
+ name: "report.pdf",
36
+ size: 12.5,
37
+ maxSize: 10,
38
+ });
39
+ return null;
40
+ }
41
+
42
+ act(() => root.render(<Consumer />));
43
+
44
+ expect(translated).toBe('"report.pdf" is 12.5 MB (max 10 MB)');
45
+ });
46
+
28
47
  it("keeps the context value stable so consumer effects do not refire", () => {
29
48
  const readAppState = vi.fn(() => undefined);
30
49
  const adapters: ComposerRuntimeAdapters = { voice: { readAppState } };
@@ -110,6 +110,7 @@ export interface VoiceContextPack {
110
110
  export interface ComposerRuntimeAdapters {
111
111
  resolvePath?: (path: string) => string;
112
112
  translate?: ComposerTranslate;
113
+ formatNumber?: (value: number, options?: Intl.NumberFormatOptions) => string;
113
114
  models?: {
114
115
  useChatModels?: (options: { enabled: boolean }) => ComposerModelState;
115
116
  useAgentEngineConfigured?: (enabled: boolean) => {
@@ -178,8 +179,15 @@ export interface ComposerRuntimeAdapters {
178
179
  }
179
180
 
180
181
  const identityPath = (path: string) => path;
181
- const fallbackTranslate: ComposerTranslate = (key, options) =>
182
- typeof options?.defaultValue === "string" ? options.defaultValue : key;
182
+ const fallbackTranslate: ComposerTranslate = (key, options) => {
183
+ const template =
184
+ typeof options?.defaultValue === "string" ? options.defaultValue : key;
185
+
186
+ return template.replace(/{{\s*([\w$.-]+)\s*}}/g, (match, name: string) => {
187
+ const value = options?.[name];
188
+ return value == null ? match : String(value);
189
+ });
190
+ };
183
191
  const fallbackModels = {
184
192
  useChatModels: () => ({
185
193
  selectedModel: "auto",
@@ -210,6 +218,8 @@ const fallbackAdapters: Required<Pick<ComposerRuntimeAdapters, "resolvePath">> &
210
218
  ComposerRuntimeAdapters = {
211
219
  resolvePath: identityPath,
212
220
  translate: fallbackTranslate,
221
+ formatNumber: (value, options) =>
222
+ new Intl.NumberFormat("en-US", options).format(value),
213
223
  models: fallbackModels,
214
224
  agentChat: {
215
225
  sendToAgentChat: () => {},
@@ -1,7 +1,10 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
+ import { act, createElement } from "react";
4
+ import { createRoot } from "react-dom/client";
3
5
  import { afterEach, describe, expect, it, vi } from "vitest";
4
6
 
7
+ import { ComposerRuntimeAdaptersProvider } from "./runtime-adapters.js";
5
8
  import {
6
9
  createRealtimeVoiceGreetingEvent,
7
10
  createRealtimeVoiceGreetingStarter,
@@ -28,6 +31,7 @@ import {
28
31
  realtimeVoiceReasoningEffort,
29
32
  resolveRealtimeVoiceLanguage,
30
33
  shouldRestoreRealtimeVoiceTranscriptThread,
34
+ useRealtimeVoiceModeCopy,
31
35
  } from "./useRealtimeVoiceMode.js";
32
36
  import type { RealtimeVoiceFunctionTool } from "./useRealtimeVoiceMode.js";
33
37
 
@@ -45,6 +49,36 @@ function realtimeTool(name: string): RealtimeVoiceFunctionTool {
45
49
  };
46
50
  }
47
51
 
52
+ describe("Realtime voice localization", () => {
53
+ it("uses app overrides from the modern agentChat namespace", () => {
54
+ const container = document.createElement("div");
55
+ const root = createRoot(container);
56
+ let entryButtonLabel = "";
57
+
58
+ function Consumer() {
59
+ entryButtonLabel = useRealtimeVoiceModeCopy().entryButtonLabel;
60
+ return null;
61
+ }
62
+
63
+ act(() => {
64
+ root.render(
65
+ createElement(ComposerRuntimeAdaptersProvider, {
66
+ adapters: {
67
+ translate: (key, options) =>
68
+ key === "agentChat.voiceMode.entryButtonLabel"
69
+ ? "Modern voice override"
70
+ : String(options?.defaultValue ?? key),
71
+ },
72
+ children: createElement(Consumer),
73
+ }),
74
+ );
75
+ });
76
+
77
+ expect(entryButtonLabel).toBe("Modern voice override");
78
+ act(() => root.unmount());
79
+ });
80
+ });
81
+
48
82
  describe("Realtime voice client transport", () => {
49
83
  it("normalizes persisted preferences and resolves Auto from the browser language", () => {
50
84
  expect(
@@ -22,7 +22,10 @@ import {
22
22
  type RealtimeVoiceModeCopy,
23
23
  type RealtimeVoiceModeState,
24
24
  } from "./RealtimeVoiceMode.js";
25
- import { useComposerRuntimeAdapters } from "./runtime-adapters.js";
25
+ import {
26
+ type ComposerTranslate,
27
+ useComposerRuntimeAdapters,
28
+ } from "./runtime-adapters.js";
26
29
 
27
30
  const REALTIME_VOICE_STATE_KEY = "realtime-voice-session";
28
31
  const REALTIME_VOICE_PREFERENCES_KEY = "realtime-voice-prefs";
@@ -1097,81 +1100,203 @@ export function listenForRealtimeVoicePageHide(
1097
1100
  return () => window.removeEventListener("pagehide", cleanup);
1098
1101
  }
1099
1102
 
1100
- function voiceCopy(t: (key: string) => string): RealtimeVoiceModeCopy {
1103
+ function voiceCopy(t: ComposerTranslate): RealtimeVoiceModeCopy {
1101
1104
  return {
1102
- entryButtonLabel: t("agentPanel.voiceMode.entryButtonLabel"),
1103
- promptTitle: t("agentPanel.voiceMode.promptTitle"),
1104
- promptDescription: t("agentPanel.voiceMode.promptDescription"),
1105
- setupTitle: t("agentPanel.voiceMode.setupTitle"),
1106
- setupDescription: t("agentPanel.voiceMode.setupDescription"),
1107
- connectBuilder: t("agentPanel.voiceMode.connectBuilder"),
1108
- useOpenAiKey: t("agentPanel.voiceMode.useOpenAiKey"),
1109
- startWithOpenAiKey: t("agentPanel.voiceMode.startWithOpenAiKey"),
1110
- startVoiceMode: t("agentPanel.voiceMode.start"),
1111
- keepDictating: t("agentPanel.voiceMode.keepDictating"),
1112
- rememberPreference: t("agentPanel.voiceMode.rememberPreference"),
1113
- showChat: t("agentPanel.voiceMode.showChat"),
1114
- hideChat: t("agentPanel.voiceMode.hideChat"),
1115
- endVoiceMode: t("agentPanel.voiceMode.end"),
1116
- voiceSettings: t("agentPanel.voiceMode.voiceSettings"),
1105
+ entryButtonLabel: t("agentChat.voiceMode.entryButtonLabel", {
1106
+ defaultValue: "Use microphone",
1107
+ }),
1108
+ promptTitle: t("agentChat.voiceMode.promptTitle", {
1109
+ defaultValue: "Use your voice",
1110
+ }),
1111
+ promptDescription: t("agentChat.voiceMode.promptDescription", {
1112
+ defaultValue:
1113
+ "Voice mode keeps listening while the agent navigates and takes actions.",
1114
+ }),
1115
+ setupTitle: t("agentChat.voiceMode.setupTitle", {
1116
+ defaultValue: "Set up voice mode",
1117
+ }),
1118
+ setupDescription: t("agentChat.voiceMode.setupDescription", {
1119
+ defaultValue:
1120
+ "Connect Builder.io to use managed voice with free credits, or add your own keys.",
1121
+ }),
1122
+ connectBuilder: t("agentChat.voiceMode.connectBuilder", {
1123
+ defaultValue: "Connect Builder.io",
1124
+ }),
1125
+ useOpenAiKey: t("agentChat.voiceMode.useOpenAiKey", {
1126
+ defaultValue: "Add your own keys",
1127
+ }),
1128
+ startWithOpenAiKey: t("agentChat.voiceMode.startWithOpenAiKey", {
1129
+ defaultValue: "Start with OpenAI key",
1130
+ }),
1131
+ startVoiceMode: t("agentChat.voiceMode.start", {
1132
+ defaultValue: "Real-time voice",
1133
+ }),
1134
+ keepDictating: t("agentChat.voiceMode.keepDictating", {
1135
+ defaultValue: "Dictate",
1136
+ }),
1137
+ rememberPreference: t("agentChat.voiceMode.rememberPreference", {
1138
+ defaultValue: "Remember my preference",
1139
+ }),
1140
+ showChat: t("agentChat.voiceMode.showChat", {
1141
+ defaultValue: "Show chat",
1142
+ }),
1143
+ hideChat: t("agentChat.voiceMode.hideChat", {
1144
+ defaultValue: "Hide chat",
1145
+ }),
1146
+ endVoiceMode: t("agentChat.voiceMode.end", {
1147
+ defaultValue: "End voice mode",
1148
+ }),
1149
+ voiceSettings: t("agentChat.voiceMode.voiceSettings", {
1150
+ defaultValue: "Voice settings",
1151
+ }),
1117
1152
  settings: {
1118
- language: t("agentPanel.voiceMode.settings.language"),
1119
- autoLanguage: t("agentPanel.voiceMode.settings.autoLanguage"),
1153
+ language: t("agentChat.voiceMode.settings.language", {
1154
+ defaultValue: "Language",
1155
+ }),
1156
+ autoLanguage: t("agentChat.voiceMode.settings.autoLanguage", {
1157
+ defaultValue: "Auto",
1158
+ }),
1120
1159
  languages: {
1121
- en: t("agentPanel.voiceMode.settings.languages.en"),
1122
- es: t("agentPanel.voiceMode.settings.languages.es"),
1123
- fr: t("agentPanel.voiceMode.settings.languages.fr"),
1124
- de: t("agentPanel.voiceMode.settings.languages.de"),
1125
- it: t("agentPanel.voiceMode.settings.languages.it"),
1126
- pt: t("agentPanel.voiceMode.settings.languages.pt"),
1127
- ja: t("agentPanel.voiceMode.settings.languages.ja"),
1128
- ko: t("agentPanel.voiceMode.settings.languages.ko"),
1129
- zh: t("agentPanel.voiceMode.settings.languages.zh"),
1160
+ en: t("agentChat.voiceMode.settings.languages.en", {
1161
+ defaultValue: "English",
1162
+ }),
1163
+ es: t("agentChat.voiceMode.settings.languages.es", {
1164
+ defaultValue: "Spanish",
1165
+ }),
1166
+ fr: t("agentChat.voiceMode.settings.languages.fr", {
1167
+ defaultValue: "French",
1168
+ }),
1169
+ de: t("agentChat.voiceMode.settings.languages.de", {
1170
+ defaultValue: "German",
1171
+ }),
1172
+ it: t("agentChat.voiceMode.settings.languages.it", {
1173
+ defaultValue: "Italian",
1174
+ }),
1175
+ pt: t("agentChat.voiceMode.settings.languages.pt", {
1176
+ defaultValue: "Portuguese",
1177
+ }),
1178
+ ja: t("agentChat.voiceMode.settings.languages.ja", {
1179
+ defaultValue: "Japanese",
1180
+ }),
1181
+ ko: t("agentChat.voiceMode.settings.languages.ko", {
1182
+ defaultValue: "Korean",
1183
+ }),
1184
+ zh: t("agentChat.voiceMode.settings.languages.zh", {
1185
+ defaultValue: "Chinese",
1186
+ }),
1130
1187
  },
1131
- intelligence: t("agentPanel.voiceMode.settings.intelligence"),
1188
+ intelligence: t("agentChat.voiceMode.settings.intelligence", {
1189
+ defaultValue: "Intelligence",
1190
+ }),
1132
1191
  intelligenceLevels: {
1133
- instant: t("agentPanel.voiceMode.settings.intelligenceLevels.instant"),
1192
+ instant: t("agentChat.voiceMode.settings.intelligenceLevels.instant", {
1193
+ defaultValue: "Instant",
1194
+ }),
1134
1195
  balanced: t(
1135
- "agentPanel.voiceMode.settings.intelligenceLevels.balanced",
1196
+ "agentChat.voiceMode.settings.intelligenceLevels.balanced",
1197
+ { defaultValue: "Balanced" },
1136
1198
  ),
1137
- deep: t("agentPanel.voiceMode.settings.intelligenceLevels.deep"),
1199
+ deep: t("agentChat.voiceMode.settings.intelligenceLevels.deep", {
1200
+ defaultValue: "Deep",
1201
+ }),
1138
1202
  },
1139
- voiceStyle: t("agentPanel.voiceMode.settings.voiceStyle"),
1140
- microphone: t("agentPanel.voiceMode.settings.microphone"),
1141
- defaultMicrophone: t("agentPanel.voiceMode.settings.defaultMicrophone"),
1203
+ voiceStyle: t("agentChat.voiceMode.settings.voiceStyle", {
1204
+ defaultValue: "Voice style",
1205
+ }),
1206
+ microphone: t("agentChat.voiceMode.settings.microphone", {
1207
+ defaultValue: "Microphone",
1208
+ }),
1209
+ defaultMicrophone: t("agentChat.voiceMode.settings.defaultMicrophone", {
1210
+ defaultValue: "System default",
1211
+ }),
1142
1212
  microphoneSwitchFailed: t(
1143
- "agentPanel.voiceMode.settings.microphoneSwitchFailed",
1213
+ "agentChat.voiceMode.settings.microphoneSwitchFailed",
1214
+ {
1215
+ defaultValue:
1216
+ "Could not switch microphones. Your current microphone is still active.",
1217
+ },
1144
1218
  ),
1145
- voiceChangePending: t("agentPanel.voiceMode.settings.voiceChangePending"),
1219
+ voiceChangePending: t("agentChat.voiceMode.settings.voiceChangePending", {
1220
+ defaultValue:
1221
+ "Your new voice will apply next time you start voice mode.",
1222
+ }),
1146
1223
  voiceDescriptions: {
1147
- marin: t("agentPanel.voiceMode.settings.voiceDescriptions.marin"),
1148
- cedar: t("agentPanel.voiceMode.settings.voiceDescriptions.cedar"),
1149
- coral: t("agentPanel.voiceMode.settings.voiceDescriptions.coral"),
1150
- sage: t("agentPanel.voiceMode.settings.voiceDescriptions.sage"),
1151
- verse: t("agentPanel.voiceMode.settings.voiceDescriptions.verse"),
1152
- alloy: t("agentPanel.voiceMode.settings.voiceDescriptions.alloy"),
1153
- ash: t("agentPanel.voiceMode.settings.voiceDescriptions.ash"),
1154
- ballad: t("agentPanel.voiceMode.settings.voiceDescriptions.ballad"),
1155
- echo: t("agentPanel.voiceMode.settings.voiceDescriptions.echo"),
1156
- shimmer: t("agentPanel.voiceMode.settings.voiceDescriptions.shimmer"),
1224
+ marin: t("agentChat.voiceMode.settings.voiceDescriptions.marin", {
1225
+ defaultValue: "Warm and natural",
1226
+ }),
1227
+ cedar: t("agentChat.voiceMode.settings.voiceDescriptions.cedar", {
1228
+ defaultValue: "Clear and grounded",
1229
+ }),
1230
+ coral: t("agentChat.voiceMode.settings.voiceDescriptions.coral", {
1231
+ defaultValue: "Friendly and bright",
1232
+ }),
1233
+ sage: t("agentChat.voiceMode.settings.voiceDescriptions.sage", {
1234
+ defaultValue: "Calm and thoughtful",
1235
+ }),
1236
+ verse: t("agentChat.voiceMode.settings.voiceDescriptions.verse", {
1237
+ defaultValue: "Expressive and versatile",
1238
+ }),
1239
+ alloy: t("agentChat.voiceMode.settings.voiceDescriptions.alloy", {
1240
+ defaultValue: "Balanced and neutral",
1241
+ }),
1242
+ ash: t("agentChat.voiceMode.settings.voiceDescriptions.ash", {
1243
+ defaultValue: "Smooth and confident",
1244
+ }),
1245
+ ballad: t("agentChat.voiceMode.settings.voiceDescriptions.ballad", {
1246
+ defaultValue: "Warm and expressive",
1247
+ }),
1248
+ echo: t("agentChat.voiceMode.settings.voiceDescriptions.echo", {
1249
+ defaultValue: "Clear and direct",
1250
+ }),
1251
+ shimmer: t("agentChat.voiceMode.settings.voiceDescriptions.shimmer", {
1252
+ defaultValue: "Light and upbeat",
1253
+ }),
1157
1254
  },
1158
1255
  },
1159
1256
  status: {
1160
- connecting: t("agentPanel.voiceMode.status.connecting"),
1161
- listening: t("agentPanel.voiceMode.status.listening"),
1162
- speaking: t("agentPanel.voiceMode.status.speaking"),
1163
- working: t("agentPanel.voiceMode.status.working"),
1164
- error: t("agentPanel.voiceMode.status.error"),
1165
- ending: t("agentPanel.voiceMode.status.ending"),
1257
+ connecting: t("agentChat.voiceMode.status.connecting", {
1258
+ defaultValue: "Connecting",
1259
+ }),
1260
+ listening: t("agentChat.voiceMode.status.listening", {
1261
+ defaultValue: "Listening",
1262
+ }),
1263
+ speaking: t("agentChat.voiceMode.status.speaking", {
1264
+ defaultValue: "Speaking",
1265
+ }),
1266
+ working: t("agentChat.voiceMode.status.working", {
1267
+ defaultValue: "Working",
1268
+ }),
1269
+ error: t("agentChat.voiceMode.status.error", {
1270
+ defaultValue: "Voice mode needs attention",
1271
+ }),
1272
+ ending: t("agentChat.voiceMode.status.ending", {
1273
+ defaultValue: "Ending voice mode",
1274
+ }),
1166
1275
  },
1167
1276
  errors: {
1168
- unsupported: t("agentPanel.voiceMode.errors.unsupported"),
1169
- responseFailed: t("agentPanel.voiceMode.errors.responseFailed"),
1170
- sessionFailed: t("agentPanel.voiceMode.errors.sessionFailed"),
1171
- channelDisconnected: t("agentPanel.voiceMode.errors.channelDisconnected"),
1172
- connectionTimedOut: t("agentPanel.voiceMode.errors.connectionTimedOut"),
1173
- connectionFailed: t("agentPanel.voiceMode.errors.connectionFailed"),
1174
- offerFailed: t("agentPanel.voiceMode.errors.offerFailed"),
1277
+ unsupported: t("agentChat.voiceMode.errors.unsupported", {
1278
+ defaultValue:
1279
+ "This browser does not support realtime voice conversations.",
1280
+ }),
1281
+ responseFailed: t("agentChat.voiceMode.errors.responseFailed", {
1282
+ defaultValue: "OpenAI could not complete the voice response.",
1283
+ }),
1284
+ sessionFailed: t("agentChat.voiceMode.errors.sessionFailed", {
1285
+ defaultValue: "The realtime voice session encountered an error.",
1286
+ }),
1287
+ channelDisconnected: t("agentChat.voiceMode.errors.channelDisconnected", {
1288
+ defaultValue: "The realtime voice control channel disconnected.",
1289
+ }),
1290
+ connectionTimedOut: t("agentChat.voiceMode.errors.connectionTimedOut", {
1291
+ defaultValue:
1292
+ "The realtime voice connection timed out. Try again after the app finishes loading.",
1293
+ }),
1294
+ connectionFailed: t("agentChat.voiceMode.errors.connectionFailed", {
1295
+ defaultValue: "The realtime voice connection failed.",
1296
+ }),
1297
+ offerFailed: t("agentChat.voiceMode.errors.offerFailed", {
1298
+ defaultValue: "The browser did not create an audio offer.",
1299
+ }),
1175
1300
  },
1176
1301
  };
1177
1302
  }
@@ -1186,6 +1311,7 @@ function useRealtimeVoiceModeController(
1186
1311
  copy?: RealtimeVoiceModeCopy,
1187
1312
  ): RealtimeVoiceModeApi {
1188
1313
  const adapters = useComposerRuntimeAdapters();
1314
+ const t = adapters.translate!;
1189
1315
  const [state, setState] = useState<"idle" | RealtimeVoiceModeState>("idle");
1190
1316
  const [error, setError] = useState<string | null>(null);
1191
1317
  const [chatVisible, setChatVisible] = useState(false);
@@ -1395,7 +1521,12 @@ function useRealtimeVoiceModeController(
1395
1521
  )
1396
1522
  .map((device, index) => ({
1397
1523
  deviceId: device.deviceId,
1398
- label: device.label || `Microphone ${index + 1}`,
1524
+ label:
1525
+ device.label ||
1526
+ t("agentChat.voiceMode.settings.microphoneNumber", {
1527
+ number: index + 1,
1528
+ defaultValue: `Microphone ${index + 1}`,
1529
+ }),
1399
1530
  }));
1400
1531
  setMicrophones(inputs);
1401
1532
  return inputs;
@@ -1403,7 +1534,7 @@ function useRealtimeVoiceModeController(
1403
1534
  // Enumeration is progressive enhancement; system default remains usable.
1404
1535
  return null;
1405
1536
  }
1406
- }, []);
1537
+ }, [t]);
1407
1538
 
1408
1539
  const startMeterLoop = useCallback(() => {
1409
1540
  if (meterFrameRef.current !== null) return;
@@ -1508,7 +1639,10 @@ function useRealtimeVoiceModeController(
1508
1639
  } catch {
1509
1640
  setMicrophoneError(
1510
1641
  copy?.settings.microphoneSwitchFailed ??
1511
- "Could not switch microphones. Your current microphone is still active.",
1642
+ t("agentChat.voiceMode.settings.microphoneSwitchFailed", {
1643
+ defaultValue:
1644
+ "Could not switch microphones. Your current microphone is still active.",
1645
+ }),
1512
1646
  );
1513
1647
  } finally {
1514
1648
  setMicrophoneSwitching(false);
@@ -1520,6 +1654,7 @@ function useRealtimeVoiceModeController(
1520
1654
  microphoneDeviceId,
1521
1655
  microphoneSwitching,
1522
1656
  refreshMicrophones,
1657
+ t,
1523
1658
  ],
1524
1659
  );
1525
1660
  switchMicrophoneRef.current = setMicrophone;
@@ -1740,7 +1875,9 @@ function useRealtimeVoiceModeController(
1740
1875
  fail(
1741
1876
  message ||
1742
1877
  copy?.errors.sessionFailed ||
1743
- "The realtime voice session encountered an error.",
1878
+ t("agentChat.voiceMode.errors.sessionFailed", {
1879
+ defaultValue: "The realtime voice session encountered an error.",
1880
+ }),
1744
1881
  );
1745
1882
  return;
1746
1883
  }
@@ -1757,6 +1894,7 @@ function useRealtimeVoiceModeController(
1757
1894
  greetingStarter,
1758
1895
  handleFunctionCall,
1759
1896
  syncAppState,
1897
+ t,
1760
1898
  transcriptSequencer,
1761
1899
  transition,
1762
1900
  responseCoordinator,
@@ -1773,7 +1911,10 @@ function useRealtimeVoiceModeController(
1773
1911
  ) {
1774
1912
  fail(
1775
1913
  copy?.errors.unsupported ??
1776
- "This browser does not support realtime voice conversations.",
1914
+ t("agentChat.voiceMode.errors.unsupported", {
1915
+ defaultValue:
1916
+ "This browser does not support realtime voice conversations.",
1917
+ }),
1777
1918
  );
1778
1919
  return;
1779
1920
  }
@@ -1804,7 +1945,9 @@ function useRealtimeVoiceModeController(
1804
1945
  if (!isCurrentAttempt()) return;
1805
1946
  fail(
1806
1947
  copy?.errors.connectionTimedOut ??
1807
- "The realtime voice connection timed out.",
1948
+ t("agentChat.voiceMode.errors.connectionTimedOut", {
1949
+ defaultValue: "The realtime voice connection timed out.",
1950
+ }),
1808
1951
  );
1809
1952
  });
1810
1953
  connectionGateRef.current = connectionGate;
@@ -1870,7 +2013,9 @@ function useRealtimeVoiceModeController(
1870
2013
  if (!isCurrentAttempt()) return;
1871
2014
  fail(
1872
2015
  copy?.errors.channelDisconnected ??
1873
- "The realtime voice control channel disconnected.",
2016
+ t("agentChat.voiceMode.errors.channelDisconnected", {
2017
+ defaultValue: "The realtime voice control channel disconnected.",
2018
+ }),
1874
2019
  );
1875
2020
  };
1876
2021
  peer.onconnectionstatechange = () => {
@@ -1881,7 +2026,9 @@ function useRealtimeVoiceModeController(
1881
2026
  if (peer.connectionState === "failed") {
1882
2027
  fail(
1883
2028
  copy?.errors.connectionFailed ??
1884
- "The realtime voice connection failed.",
2029
+ t("agentChat.voiceMode.errors.connectionFailed", {
2030
+ defaultValue: "The realtime voice connection failed.",
2031
+ }),
1885
2032
  );
1886
2033
  }
1887
2034
  };
@@ -1893,7 +2040,9 @@ function useRealtimeVoiceModeController(
1893
2040
  if (!offer.sdp) {
1894
2041
  throw new Error(
1895
2042
  copy?.errors.offerFailed ??
1896
- "The browser did not create an audio offer.",
2043
+ t("agentChat.voiceMode.errors.offerFailed", {
2044
+ defaultValue: "The browser did not create an audio offer.",
2045
+ }),
1897
2046
  );
1898
2047
  }
1899
2048
  const answer = await createRealtimeVoiceSessionWithCapability(offer.sdp, {
@@ -1945,6 +2094,7 @@ function useRealtimeVoiceModeController(
1945
2094
  handleServerEvent,
1946
2095
  hydratePreferences,
1947
2096
  refreshMicrophones,
2097
+ t,
1948
2098
  transcriptSequencer,
1949
2099
  transition,
1950
2100
  ]);