@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
@@ -168,6 +168,7 @@ function isDocumentAttachment(value) {
168
168
  export function getOversizedDocumentAttachmentError(attachments, options = {}) {
169
169
  const maxBytes = options.maxBytes ?? MAX_DOCUMENT_ATTACHMENT_BYTES;
170
170
  const label = options.label ?? "PDFs";
171
+ const t = options.translate;
171
172
  for (const attachment of attachments) {
172
173
  if (!attachment || typeof attachment !== "object")
173
174
  continue;
@@ -184,7 +185,14 @@ export function getOversizedDocumentAttachmentError(attachments, options = {}) {
184
185
  : file.name;
185
186
  const mb = (file.size / 1024 / 1024).toFixed(1);
186
187
  const maxMb = (maxBytes / 1024 / 1024).toFixed(0);
187
- return `"${name}" is ${mb} MB. ${label} are capped at ${maxMb} MB to stay within message limits. Please reduce the file size or split it into smaller parts.`;
188
+ return (t?.("agentChat.composer.documentTooLarge", {
189
+ defaultValue: '"{{name}}" is {{size}} MB. {{label}} are capped at {{maxSize}} MB to stay within message limits. Please reduce the file size or split it into smaller parts.',
190
+ name,
191
+ size: mb,
192
+ label,
193
+ maxSize: maxMb,
194
+ }) ??
195
+ `"${name}" is ${mb} MB. ${label} are capped at ${maxMb} MB to stay within message limits. Please reduce the file size or split it into smaller parts.`);
188
196
  }
189
197
  return null;
190
198
  }
@@ -219,7 +227,9 @@ export function getComposerPopoverPosition(view, pos) {
219
227
  }
220
228
  export function displayableComposerModeMessage(options) {
221
229
  const modePrompt = options.trimmedText ||
222
- (options.attachmentCount > 0 ? "Use the attached context." : "");
230
+ (options.attachmentCount > 0
231
+ ? (options.attachedContextFallback ?? "Use the attached context.")
232
+ : "");
223
233
  return `${options.messagePrefix}${modePrompt}`;
224
234
  }
225
235
  function uniquifyComposerImageFile(file) {
@@ -255,14 +265,52 @@ export function handleComposerFileDrop(options) {
255
265
  });
256
266
  return true;
257
267
  }
258
- const BUILT_IN_COMMANDS = [
259
- { name: "clear", description: "Start a new chat", icon: "clear" },
260
- { name: "new", description: "Start a new chat", icon: "new" },
261
- { name: "history", description: "Browse all chats", icon: "history" },
262
- { name: "plan", description: "Switch to read-only planning", icon: "plan" },
263
- { name: "act", description: "Switch back to acting", icon: "act" },
264
- { name: "help", description: "Show available commands", icon: "help" },
265
- ];
268
+ function builtInCommands(t) {
269
+ return [
270
+ {
271
+ name: "clear",
272
+ description: t("agentChat.commands.clearShort", {
273
+ defaultValue: "Start a new chat",
274
+ }),
275
+ icon: "clear",
276
+ },
277
+ {
278
+ name: "new",
279
+ description: t("agentChat.commands.newShort", {
280
+ defaultValue: "Start a new chat",
281
+ }),
282
+ icon: "new",
283
+ },
284
+ {
285
+ name: "history",
286
+ description: t("agentChat.commands.history", {
287
+ defaultValue: "Browse all chats",
288
+ }),
289
+ icon: "history",
290
+ },
291
+ {
292
+ name: "plan",
293
+ description: t("agentChat.commands.plan", {
294
+ defaultValue: "Switch to read-only planning",
295
+ }),
296
+ icon: "plan",
297
+ },
298
+ {
299
+ name: "act",
300
+ description: t("agentChat.commands.act", {
301
+ defaultValue: "Switch back to acting",
302
+ }),
303
+ icon: "act",
304
+ },
305
+ {
306
+ name: "help",
307
+ description: t("agentChat.commands.help", {
308
+ defaultValue: "Show available commands",
309
+ }),
310
+ icon: "help",
311
+ },
312
+ ];
313
+ }
266
314
  function normalizeSlashCommandName(name) {
267
315
  return name.replace(/^\/+/, "").trim().toLowerCase();
268
316
  }
@@ -292,10 +340,7 @@ function mergeSlashSkills(skills) {
292
340
  }
293
341
  const COMPOSER_MODE_CONFIGS = {
294
342
  skill: {
295
- label: "Create Skill",
296
343
  icon: IconBulb,
297
- placeholder: "Describe the skill you want to create...",
298
- messagePrefix: "Create a skill: ",
299
344
  getContext: (prompt) => `The user wants to create an agent skill. Their description: "${prompt}"
300
345
 
301
346
  Follow the create-skill pattern to build this. Before writing:
@@ -316,10 +361,7 @@ After creating, update the shared AGENTS.md resource to reference the new skill
316
361
  Keep the skill concise (under 500 lines) and actionable.`,
317
362
  },
318
363
  job: {
319
- label: "Schedule Task",
320
364
  icon: IconClock,
321
- placeholder: "Describe what should happen and when...",
322
- messagePrefix: "Create a recurring job: ",
323
365
  getContext: (prompt) => `The user wants to create a recurring job. Their description: "${prompt}"
324
366
 
325
367
  Use the manage-jobs tool with action "create" to create this. You need to:
@@ -331,10 +373,7 @@ Use the manage-jobs tool with action "create" to create this. You need to:
331
373
  The job will run automatically on the schedule. Make the instructions specific — include which actions to call and what to do with results.`,
332
374
  },
333
375
  automation: {
334
- label: "Create Automation",
335
376
  icon: IconBolt,
336
- placeholder: "Describe what you want to automate...",
337
- messagePrefix: "Create an automation: ",
338
377
  beforeSend: () => {
339
378
  window.dispatchEvent(new CustomEvent("agent-panel:set-mode", {
340
379
  detail: { mode: "chat" },
@@ -345,10 +384,7 @@ The job will run automatically on the schedule. Make the instructions specific
345
384
  Use manage-automations with action=define to create it. Ask clarifying questions if needed about what event to trigger on, conditions, and what actions to take.`,
346
385
  },
347
386
  extension: {
348
- label: "Create Extension",
349
387
  icon: IconTool,
350
- placeholder: "Describe the interactive extension you want to build...",
351
- messagePrefix: "Create an extension: ",
352
388
  getContext: (prompt) => `The user wants to create an interactive extension (sandboxed mini-app). Their description: "${prompt}"
353
389
 
354
390
  Use the create-extension action with Alpine.js HTML content. The extension runs as a sandboxed iframe with Tailwind CSS and modest default canvas padding. For edge-to-edge layouts, put data-extension-layout="full-bleed" on the outermost element.
@@ -360,8 +396,59 @@ Make the extension functional and visually polished. Extensions can use extensio
360
396
  Prefer appAction()/appFetch() for app data. Some actions return JSON strings for CLI compatibility, so parse string results before counting rows or reading arrays. Do not guess raw SQL table names or columns for app data; use dbQuery() only when the table is known to exist in the current schema.`,
361
397
  },
362
398
  };
363
- function ComposerModeChip({ mode, onRemove, }) {
399
+ function localizedComposerModeConfig(mode, t) {
364
400
  const config = COMPOSER_MODE_CONFIGS[mode];
401
+ const copies = {
402
+ skill: {
403
+ label: t("agentChat.composer.createSkill", {
404
+ defaultValue: "Create Skill",
405
+ }),
406
+ placeholder: t("agentChat.composer.describeSkill", {
407
+ defaultValue: "Describe the skill you want to create...",
408
+ }),
409
+ messagePrefix: t("agentChat.composer.createSkillPrefix", {
410
+ defaultValue: "Create a skill: ",
411
+ }),
412
+ },
413
+ job: {
414
+ label: t("agentChat.composer.scheduleTask", {
415
+ defaultValue: "Schedule Task",
416
+ }),
417
+ placeholder: t("agentChat.composer.describeSchedule", {
418
+ defaultValue: "Describe what should happen and when...",
419
+ }),
420
+ messagePrefix: t("agentChat.composer.scheduleTaskPrefix", {
421
+ defaultValue: "Create a recurring job: ",
422
+ }),
423
+ },
424
+ automation: {
425
+ label: t("agentChat.composer.createAutomation", {
426
+ defaultValue: "Create Automation",
427
+ }),
428
+ placeholder: t("agentChat.composer.describeAutomation", {
429
+ defaultValue: "Describe what you want to automate...",
430
+ }),
431
+ messagePrefix: t("agentChat.composer.createAutomationPrefix", {
432
+ defaultValue: "Create an automation: ",
433
+ }),
434
+ },
435
+ extension: {
436
+ label: t("agentChat.composer.createExtension", {
437
+ defaultValue: "Create Extension",
438
+ }),
439
+ placeholder: t("agentChat.composer.describeExtension", {
440
+ defaultValue: "Describe the interactive extension you want to build...",
441
+ }),
442
+ messagePrefix: t("agentChat.composer.createExtensionPrefix", {
443
+ defaultValue: "Create an extension: ",
444
+ }),
445
+ },
446
+ };
447
+ return { ...config, ...copies[mode] };
448
+ }
449
+ function ComposerModeChip({ mode, onRemove, }) {
450
+ const t = useComposerRuntimeAdapters().translate;
451
+ const config = localizedComposerModeConfig(mode, t);
365
452
  const Icon = config.icon;
366
453
  return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-md border border-border bg-muted/50 px-1.5 py-0.5 text-xs font-medium text-foreground", children: [_jsx(Icon, { className: "h-3 w-3 text-muted-foreground" }), config.label, _jsx("button", { type: "button", onClick: onRemove, className: "ms-0.5 rounded-sm text-muted-foreground hover:text-foreground cursor-pointer", children: _jsx(IconX, { className: "h-3 w-3" }) })] }));
367
454
  }
@@ -410,21 +497,34 @@ export function createTiptapComposerExtensions(getPlaceholder) {
410
497
  MentionReference,
411
498
  ];
412
499
  }
413
- function ModeSelector({ mode, onChange, planModeDisabled = false, planModeDisabledReason = "Open Agent Native Desktop to use Plan mode.", }) {
500
+ function ModeSelector({ mode, onChange, planModeDisabled = false, planModeDisabledReason, }) {
501
+ const t = useComposerRuntimeAdapters().translate;
414
502
  const [open, setOpen] = useState(false);
415
- return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": mode === "build" ? "Act mode" : "Plan mode", "data-agent-composer-slot": "mode-button", className: "agent-composer-mode-button shrink-0 flex items-center gap-1 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground", children: [mode === "build" ? "Act" : "Plan", _jsx(IconChevronDown, { className: "h-3 w-3 opacity-60" })] }) }), _jsxs(PopoverContent, { side: "top", align: "end", sideOffset: 6, collisionPadding: 8, "data-agent-native-composer-popover": "true", className: "z-[260] w-60 rounded-lg border-border p-0 py-1 shadow-lg", style: { fontSize: 13 }, children: [_jsxs("button", { type: "button", onClick: () => {
503
+ const resolvedPlanModeDisabledReason = planModeDisabledReason ??
504
+ t("agentChat.composer.planDesktopRequired", {
505
+ defaultValue: "Open Agent Native Desktop to use Plan mode.",
506
+ });
507
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": mode === "build"
508
+ ? t("agentChat.composer.actMode", { defaultValue: "Act mode" })
509
+ : t("agentChat.plan.mode", { defaultValue: "Plan mode" }), "data-agent-composer-slot": "mode-button", className: "agent-composer-mode-button shrink-0 flex items-center gap-1 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground", children: [mode === "build"
510
+ ? t("agentChat.plan.act", { defaultValue: "Act" })
511
+ : t("agentChat.composer.plan", { defaultValue: "Plan" }), _jsx(IconChevronDown, { className: "h-3 w-3 opacity-60" })] }) }), _jsxs(PopoverContent, { side: "top", align: "end", sideOffset: 6, collisionPadding: 8, "data-agent-native-composer-popover": "true", className: "z-[260] w-60 rounded-lg border-border p-0 py-1 shadow-lg", style: { fontSize: 13 }, children: [_jsxs("button", { type: "button", onClick: () => {
416
512
  onChange("build");
417
513
  setOpen(false);
418
- }, className: "flex w-full items-center gap-3 px-3 py-2 hover:bg-accent/50 text-start", children: [_jsx(IconPencil, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("span", { className: "font-medium text-foreground text-[13px]", children: "Act" }), _jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5", children: "Use tools and make approved changes" })] }), mode === "build" && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] }), _jsxs("button", { type: "button", disabled: planModeDisabled, title: planModeDisabled ? planModeDisabledReason : undefined, onClick: () => {
514
+ }, className: "flex w-full items-center gap-3 px-3 py-2 hover:bg-accent/50 text-start", children: [_jsx(IconPencil, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("span", { className: "font-medium text-foreground text-[13px]", children: t("agentChat.plan.act", { defaultValue: "Act" }) }), _jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5", children: t("agentChat.composer.actDescription", {
515
+ defaultValue: "Use tools and make approved changes",
516
+ }) })] }), mode === "build" && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] }), _jsxs("button", { type: "button", disabled: planModeDisabled, title: planModeDisabled ? resolvedPlanModeDisabledReason : undefined, onClick: () => {
419
517
  if (planModeDisabled)
420
518
  return;
421
519
  onChange("plan");
422
520
  setOpen(false);
423
521
  }, className: `flex w-full items-center gap-3 px-3 py-2 text-start ${planModeDisabled
424
522
  ? "cursor-not-allowed opacity-60"
425
- : "hover:bg-accent/50"}`, children: [_jsx(IconClipboardList, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("span", { className: "font-medium text-foreground text-[13px]", children: "Plan" }), _jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5", children: planModeDisabled
426
- ? planModeDisabledReason
427
- : "Read-only research and approval first" })] }), mode === "plan" && !planModeDisabled && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] })] })] }));
523
+ : "hover:bg-accent/50"}`, children: [_jsx(IconClipboardList, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("span", { className: "font-medium text-foreground text-[13px]", children: t("agentChat.composer.plan", { defaultValue: "Plan" }) }), _jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5", children: planModeDisabled
524
+ ? resolvedPlanModeDisabledReason
525
+ : t("agentChat.composer.planDescription", {
526
+ defaultValue: "Read-only research and approval first",
527
+ }) })] }), mode === "plan" && !planModeDisabled && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] })] })] }));
428
528
  }
429
529
  const FRIENDLY_MODEL_NAMES = {
430
530
  auto: "Default model",
@@ -449,7 +549,12 @@ export function shouldShowModelSelectorSkeleton(isLoading, engineCount) {
449
549
  export function shouldShowOnlyConnectPath(showBuilderCta, groups) {
450
550
  return showBuilderCta && groups.every((group) => !group.configured);
451
551
  }
452
- function friendlyModelName(model) {
552
+ function friendlyModelName(model, t) {
553
+ if (model === "auto") {
554
+ return (t?.("agentChat.composer.defaultModel", {
555
+ defaultValue: "Default model",
556
+ }) ?? "Default model");
557
+ }
453
558
  if (FRIENDLY_MODEL_NAMES[model])
454
559
  return FRIENDLY_MODEL_NAMES[model];
455
560
  // Claude: claude-{tier}-{major}[-minor][-dateYYYYMMDD] → Tier Major[.Minor]
@@ -496,22 +601,53 @@ function friendlyModelName(model) {
496
601
  }
497
602
  return model;
498
603
  }
499
- export function compactComposerModelName(model) {
604
+ export function compactComposerModelName(model, t) {
500
605
  const gpt56Variant = model.match(/^(?:openai\/)?gpt-5[.-]6[.-](sol|terra|luna)$/i)?.[1];
501
606
  if (gpt56Variant) {
502
607
  const variant = gpt56Variant[0].toUpperCase() + gpt56Variant.slice(1).toLowerCase();
503
608
  return `GPT-5.6 ${variant}`;
504
609
  }
505
- return friendlyModelName(model);
610
+ return friendlyModelName(model, t);
506
611
  }
507
- export function compactComposerReasoningEffortLabel(effort) {
508
- if (effort === "medium" || effort === "auto")
509
- return "Med";
510
- if (effort === "minimal")
511
- return "Min";
512
- if (effort === "xhigh")
513
- return "XHigh";
514
- return reasoningEffortLabel(effort);
612
+ export function compactComposerReasoningEffortLabel(effort, t) {
613
+ if (effort === "medium" || effort === "auto") {
614
+ return (t?.("agentChat.composer.reasoningMediumShort", {
615
+ defaultValue: "Med",
616
+ }) ?? "Med");
617
+ }
618
+ if (effort === "minimal") {
619
+ return (t?.("agentChat.composer.reasoningMinimalShort", {
620
+ defaultValue: "Min",
621
+ }) ?? "Min");
622
+ }
623
+ if (effort === "xhigh") {
624
+ return (t?.("agentChat.composer.reasoningExtraHighShort", {
625
+ defaultValue: "XHigh",
626
+ }) ?? "XHigh");
627
+ }
628
+ return t
629
+ ? localizedReasoningEffortLabel(t, effort, reasoningEffortLabel(effort))
630
+ : reasoningEffortLabel(effort);
631
+ }
632
+ function localizedReasoningEffortLabel(t, effort, defaultValue) {
633
+ switch (effort) {
634
+ case "auto":
635
+ return t("agentChat.composer.reasoningEffort.auto", { defaultValue });
636
+ case "none":
637
+ return t("agentChat.composer.reasoningEffort.none", { defaultValue });
638
+ case "minimal":
639
+ return t("agentChat.composer.reasoningEffort.minimal", { defaultValue });
640
+ case "low":
641
+ return t("agentChat.composer.reasoningEffort.low", { defaultValue });
642
+ case "medium":
643
+ return t("agentChat.composer.reasoningEffort.medium", { defaultValue });
644
+ case "high":
645
+ return t("agentChat.composer.reasoningEffort.high", { defaultValue });
646
+ case "xhigh":
647
+ return t("agentChat.composer.reasoningEffort.xhigh", { defaultValue });
648
+ case "max":
649
+ return t("agentChat.composer.reasoningEffort.max", { defaultValue });
650
+ }
515
651
  }
516
652
  /**
517
653
  * Deduplicate models to only the latest version per family.
@@ -562,16 +698,21 @@ const MODEL_COST_TIERS = [
562
698
  ["flash", 1],
563
699
  ["pro", 3],
564
700
  ];
565
- const COST_TIER_LABELS = { 1: "Lower", 2: "Medium", 3: "Higher" };
566
701
  export function composerModelCostTier(model) {
567
702
  const normalized = model.toLowerCase();
568
703
  return MODEL_COST_TIERS.find(([token]) => normalized.includes(token))?.[1];
569
704
  }
570
705
  function ModelCostTier({ model }) {
706
+ const t = useComposerRuntimeAdapters().translate;
571
707
  const tier = composerModelCostTier(model);
572
708
  if (!tier)
573
709
  return null;
574
- return (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-[11px] tabular-nums text-muted-foreground/60", children: "$".repeat(tier) }), _jsxs("span", { className: "sr-only", children: [COST_TIER_LABELS[tier], " cost"] })] }));
710
+ const costLabel = tier === 1
711
+ ? t("agentChat.composer.costLower", { defaultValue: "Lower cost" })
712
+ : tier === 2
713
+ ? t("agentChat.composer.costMedium", { defaultValue: "Medium cost" })
714
+ : t("agentChat.composer.costHigher", { defaultValue: "Higher cost" });
715
+ return (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-[11px] tabular-nums text-muted-foreground/60", children: "$".repeat(tier) }), _jsx("span", { className: "sr-only", children: costLabel })] }));
575
716
  }
576
717
  export function getComposerReasoningEffortOptions(model) {
577
718
  return model === "auto"
@@ -597,7 +738,7 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
597
738
  getComposerReasoningEffortOptions(model);
598
739
  const selectedEffort = reasoning?.resolve?.(model, effort) ??
599
740
  resolveReasoningEffortSelection(model, effort ?? defaultEffort);
600
- const effortLabel = reasoning?.label ?? reasoningEffortLabel;
741
+ const effortLabel = useCallback((value) => localizedReasoningEffortLabel(t, value, (reasoning?.label ?? reasoningEffortLabel)(value)), [reasoning?.label, t]);
601
742
  // Collapse non-selected families by default. The family containing the
602
743
  // currently-selected model stays expanded so the user sees their pick at
603
744
  // a glance; clicking another family's header expands it inline.
@@ -669,9 +810,13 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
669
810
  onConnectLocalRuntime?.(engine);
670
811
  setOpen(false);
671
812
  }, [onConnectLocalRuntime]);
672
- return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", "data-agent-composer-slot": "model-button", "aria-label": `Model: ${friendlyModelName(model)}${effortOptions.length > 0
673
- ? `. Effort: ${effortLabel(selectedEffort)}`
674
- : ""}`, className: "agent-composer-model-button flex min-w-0 max-w-[10.5rem] shrink items-center gap-1 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground", children: [_jsx("span", { className: "min-w-0 truncate", children: compactComposerModelName(model) }), effortOptions.length > 0 && (_jsxs("span", { className: "agent-composer-model-effort min-w-0 shrink truncate text-muted-foreground/70", children: ["\u00B7 ", compactComposerReasoningEffortLabel(selectedEffort)] })), _jsx(IconChevronDown, { className: "h-3 w-3 shrink-0 opacity-60" })] }) }), _jsxs(PopoverContent, { side: "top", align: "end", sideOffset: 6, collisionPadding: 8, "data-agent-native-composer-popover": "true", className: "z-[260] box-border w-72 overflow-y-auto rounded-lg border-border p-0 py-1 shadow-lg", style: MODEL_SELECTOR_POPOVER_STYLE, children: [showBuilderCta && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => {
813
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", "data-agent-composer-slot": "model-button", "aria-label": `${t("agentChat.composer.model", {
814
+ defaultValue: "Model",
815
+ })}: ${friendlyModelName(model, t)}${effortOptions.length > 0
816
+ ? `. ${t("agentChat.composer.reasoning", {
817
+ defaultValue: "Reasoning",
818
+ })}: ${effortLabel(selectedEffort)}`
819
+ : ""}`, className: "agent-composer-model-button flex min-w-0 max-w-[10.5rem] shrink items-center gap-1 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground", children: [_jsx("span", { className: "min-w-0 truncate", children: compactComposerModelName(model, t) }), effortOptions.length > 0 && (_jsxs("span", { className: "agent-composer-model-effort min-w-0 shrink truncate text-muted-foreground/70", children: ["\u00B7 ", compactComposerReasoningEffortLabel(selectedEffort, t)] })), _jsx(IconChevronDown, { className: "h-3 w-3 shrink-0 opacity-60" })] }) }), _jsxs(PopoverContent, { side: "top", align: "end", sideOffset: 6, collisionPadding: 8, "data-agent-native-composer-popover": "true", className: "z-[260] box-border w-72 overflow-y-auto rounded-lg border-border p-0 py-1 shadow-lg", style: MODEL_SELECTOR_POPOVER_STYLE, children: [showBuilderCta && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => {
675
820
  if (onConnectProvider) {
676
821
  onConnectProvider();
677
822
  }
@@ -690,14 +835,17 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
690
835
  defaultValue: "Add your own keys",
691
836
  }) }), _jsx("span", { className: "block text-[11px] text-muted-foreground", children: t("agentPanel.configureProviderKeys", {
692
837
  defaultValue: "Choose a cloud, gateway, or local provider",
693
- }) })] })] }), !onlyConnectPathAvailable && (_jsx("div", { className: "my-1 border-t border-border" }))] })), imageModel && imageModel.options.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex items-center hover:bg-accent/30", children: _jsxs("button", { type: "button", "aria-expanded": imageExpanded, onClick: () => setImageExpanded((prev) => !prev), className: "flex flex-1 min-w-0 items-center gap-1.5 px-2 py-1.5 cursor-pointer text-start", children: [imageExpanded ? (_jsx(IconChevronDown, { className: "h-3 w-3 shrink-0 text-muted-foreground" })) : (_jsx(IconChevronRight, { className: "h-3 w-3 shrink-0 text-muted-foreground rtl:-scale-x-100" })), _jsx("span", { className: "text-[11px] font-medium text-muted-foreground uppercase tracking-wide shrink-0", children: imageModel.label ?? "Image model" }), !imageExpanded && imageModelLabel && (_jsx("span", { className: "text-[11px] text-muted-foreground/80 truncate", children: imageModelLabel }))] }) }), imageExpanded &&
838
+ }) })] })] }), !onlyConnectPathAvailable && (_jsx("div", { className: "my-1 border-t border-border" }))] })), imageModel && imageModel.options.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex items-center hover:bg-accent/30", children: _jsxs("button", { type: "button", "aria-expanded": imageExpanded, onClick: () => setImageExpanded((prev) => !prev), className: "flex flex-1 min-w-0 items-center gap-1.5 px-2 py-1.5 cursor-pointer text-start", children: [imageExpanded ? (_jsx(IconChevronDown, { className: "h-3 w-3 shrink-0 text-muted-foreground" })) : (_jsx(IconChevronRight, { className: "h-3 w-3 shrink-0 text-muted-foreground rtl:-scale-x-100" })), _jsx("span", { className: "text-[11px] font-medium text-muted-foreground uppercase tracking-wide shrink-0", children: imageModel.label ??
839
+ t("agentChat.composer.imageModel", {
840
+ defaultValue: "Image model",
841
+ }) }), !imageExpanded && imageModelLabel && (_jsx("span", { className: "text-[11px] text-muted-foreground/80 truncate", children: imageModelLabel }))] }) }), imageExpanded &&
694
842
  imageModel.options.map((option) => (_jsxs("button", { type: "button", onClick: () => {
695
843
  imageModel.onChange(option.value);
696
844
  setImageExpanded(false);
697
845
  }, className: "flex w-full items-center gap-3 ps-7 pe-3 py-1.5 text-start hover:bg-accent/50", children: [_jsx("span", { className: "flex-1 min-w-0 text-[13px] text-foreground truncate", children: option.label }), option.value === imageModel.value && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] }, option.value))), _jsx("div", { className: "my-1 border-t border-border" })] })), showModelListSkeleton && _jsx(ModelSelectorSkeleton, {}), autoModelGroup && !onlyConnectPathAvailable && (_jsxs("button", { type: "button", onClick: () => {
698
846
  onChange("auto", autoModelGroup.engine);
699
847
  setOpen(false);
700
- }, className: "flex w-full items-center gap-3 px-3 py-1.5 text-start hover:bg-accent/50", children: [_jsx("span", { className: "flex-1 min-w-0 text-[13px] text-foreground truncate", children: "Auto" }), model === "auto" && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] })), autoModelGroup &&
848
+ }, className: "flex w-full items-center gap-3 px-3 py-1.5 text-start hover:bg-accent/50", children: [_jsx("span", { className: "flex-1 min-w-0 text-[13px] text-foreground truncate", children: t("agentChat.composer.auto", { defaultValue: "Auto" }) }), model === "auto" && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] })), autoModelGroup &&
701
849
  providerGroups.length > 0 &&
702
850
  !onlyConnectPathAvailable && (_jsx("div", { className: "my-1 border-t border-border" })), !onlyConnectPathAvailable &&
703
851
  providerGroups.map((group) => {
@@ -710,10 +858,12 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
710
858
  const statusLabel = group.statusLabel ??
711
859
  (!group.configured
712
860
  ? canConnectLocalRuntime
713
- ? "sign in"
714
- : "needs API key"
861
+ ? t("agentChat.auth.logIn", { defaultValue: "Sign in" })
862
+ : t("agentChat.composer.needsApiKey", {
863
+ defaultValue: "needs API key",
864
+ })
715
865
  : undefined);
716
- return (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center hover:bg-accent/30", children: [_jsxs("button", { type: "button", "aria-expanded": isExpanded, onClick: () => toggleGroup(groupKey), className: "flex flex-1 min-w-0 items-center gap-1.5 px-2 py-1.5 cursor-pointer text-start", children: [_jsx(ChevronIcon, { className: "h-3 w-3 shrink-0 text-muted-foreground rtl:-scale-x-100" }), _jsx("span", { className: "text-[11px] font-medium text-muted-foreground uppercase tracking-wide shrink-0", children: group.label }), !isExpanded && groupKey === selectedGroupKey && (_jsx("span", { className: "text-[11px] text-muted-foreground/80 truncate", children: friendlyModelName(model) }))] }), !group.configured && statusLabel && (_jsx("button", { type: "button", className: "ms-auto max-w-[9rem] shrink-0 text-end text-[10px] text-muted-foreground/60 hover:text-foreground cursor-pointer pe-3 py-1.5", onClick: () => canConnectLocalRuntime
866
+ return (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center hover:bg-accent/30", children: [_jsxs("button", { type: "button", "aria-expanded": isExpanded, onClick: () => toggleGroup(groupKey), className: "flex flex-1 min-w-0 items-center gap-1.5 px-2 py-1.5 cursor-pointer text-start", children: [_jsx(ChevronIcon, { className: "h-3 w-3 shrink-0 text-muted-foreground rtl:-scale-x-100" }), _jsx("span", { className: "text-[11px] font-medium text-muted-foreground uppercase tracking-wide shrink-0", children: group.label }), !isExpanded && groupKey === selectedGroupKey && (_jsx("span", { className: "text-[11px] text-muted-foreground/80 truncate", children: friendlyModelName(model, t) }))] }), !group.configured && statusLabel && (_jsx("button", { type: "button", className: "ms-auto max-w-[9rem] shrink-0 text-end text-[10px] text-muted-foreground/60 hover:text-foreground cursor-pointer pe-3 py-1.5", onClick: () => canConnectLocalRuntime
717
867
  ? connectLocalRuntime(group.engine)
718
868
  : openLlmSettings(), children: statusLabel })), group.configured && statusLabel && (_jsx("span", { className: "ms-auto max-w-[9rem] shrink-0 pe-3 text-end text-[10px] text-muted-foreground/70", children: statusLabel }))] }), isExpanded &&
719
869
  models.map((m) => (_jsxs("button", { type: "button", onClick: () => {
@@ -735,16 +885,23 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
735
885
  setOpen(false);
736
886
  }, className: `flex w-full items-center gap-3 ps-7 pe-3 py-1.5 text-start ${group.configured
737
887
  ? "hover:bg-accent/50"
738
- : "opacity-40 cursor-default"}`, children: [_jsx("span", { className: "flex-1 min-w-0 text-[13px] text-foreground truncate", children: friendlyModelName(m) }), _jsx(ModelCostTier, { model: m }), m === model && group.configured && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] }, m)))] }, groupKey));
888
+ : "opacity-40 cursor-default"}`, children: [_jsx("span", { className: "flex-1 min-w-0 text-[13px] text-foreground truncate", children: friendlyModelName(m, t) }), _jsx(ModelCostTier, { model: m }), m === model && group.configured && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] }, m)))] }, groupKey));
739
889
  }), !showModelListSkeleton &&
740
890
  !onlyConnectPathAvailable &&
741
- effortOptions.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "my-1 border-t border-border" }), _jsx("div", { className: "flex items-center hover:bg-accent/30", children: _jsxs("button", { type: "button", "aria-expanded": effortExpanded, onClick: () => setEffortExpanded((prev) => !prev), className: "flex flex-1 min-w-0 items-center gap-1.5 px-2 py-1.5 cursor-pointer text-start", children: [effortExpanded ? (_jsx(IconChevronDown, { className: "h-3 w-3 shrink-0 text-muted-foreground" })) : (_jsx(IconChevronRight, { className: "h-3 w-3 shrink-0 text-muted-foreground rtl:-scale-x-100" })), _jsx("span", { className: "text-[11px] font-medium text-muted-foreground uppercase tracking-wide shrink-0", children: "Effort" }), !effortExpanded && (_jsx("span", { className: "text-[11px] text-muted-foreground/80 truncate", children: effortLabel(selectedEffort) }))] }) }), effortExpanded &&
891
+ effortOptions.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "my-1 border-t border-border" }), _jsx("div", { className: "flex items-center hover:bg-accent/30", children: _jsxs("button", { type: "button", "aria-expanded": effortExpanded, onClick: () => setEffortExpanded((prev) => !prev), className: "flex flex-1 min-w-0 items-center gap-1.5 px-2 py-1.5 cursor-pointer text-start", children: [effortExpanded ? (_jsx(IconChevronDown, { className: "h-3 w-3 shrink-0 text-muted-foreground" })) : (_jsx(IconChevronRight, { className: "h-3 w-3 shrink-0 text-muted-foreground rtl:-scale-x-100" })), _jsx("span", { className: "text-[11px] font-medium text-muted-foreground uppercase tracking-wide shrink-0", children: t("agentChat.composer.reasoning", {
892
+ defaultValue: "Reasoning",
893
+ }) }), !effortExpanded && (_jsx("span", { className: "text-[11px] text-muted-foreground/80 truncate", children: effortLabel(selectedEffort) }))] }) }), effortExpanded &&
742
894
  effortOptions.map((option) => (_jsxs("button", { type: "button", onClick: () => onEffortChange?.(option), className: "flex w-full items-center gap-3 ps-7 pe-3 py-1.5 text-start hover:bg-accent/50", children: [_jsx("span", { className: "flex-1 min-w-0 text-[13px] text-foreground truncate", children: effortLabel(option) }), option === selectedEffort && (_jsx(IconCheck, { className: "h-3.5 w-3.5 shrink-0 text-blue-500" }))] }, option)))] }))] })] }));
743
895
  }
744
896
  function ModelSelectorSkeleton() {
745
- return (_jsxs("div", { className: "space-y-1 px-2 py-2", role: "status", "aria-label": "Loading models", children: [_jsx("span", { className: "sr-only", children: "Loading models\u2026" }), ["w-24", "w-32", "w-20", "w-28"].map((width, index) => (_jsxs("div", { className: "flex items-center gap-1.5 px-1 py-1.5", children: [_jsx(Skeleton, { className: "size-3 rounded-sm" }), _jsx(Skeleton, { className: `h-3 ${width}` })] }, index)))] }));
897
+ const t = useComposerRuntimeAdapters().translate;
898
+ return (_jsxs("div", { className: "space-y-1 px-2 py-2", role: "status", "aria-label": t("agentChat.composer.loadingModels", {
899
+ defaultValue: "Loading models",
900
+ }), children: [_jsx("span", { className: "sr-only", children: t("agentChat.composer.loadingModelsProgress", {
901
+ defaultValue: "Loading models…",
902
+ }) }), ["w-24", "w-32", "w-20", "w-28"].map((width, index) => (_jsxs("div", { className: "flex items-center gap-1.5 px-1 py-1.5", children: [_jsx(Skeleton, { className: "size-3 rounded-sm" }), _jsx(Skeleton, { className: `h-3 ${width}` })] }, index)))] }));
746
903
  }
747
- export function TiptapComposer({ placeholder = "Message agent...", disabled = false, maxDocumentAttachmentBytes = MAX_DOCUMENT_ATTACHMENT_BYTES, documentAttachmentLimitLabel = "PDFs", focusRef, initialText, initialTextKey, onSubmit, onBeforeSubmit, clearOnSubmit = true, onTextChange, actionButton, willQueue = false, extraActionButton, stopButton, attachButton, modeControl, toolbarSlot, layoutVariant = "default", slashCommands = [], slashSkills = [], includeDefaultSlashCommands = true, includeDefaultSlashSkills = true, onSlashCommand, execMode, onExecModeChange, planModeDisabled = false, planModeDisabledReason, voiceEnabled = DEFAULT_VOICE_DICTATION_ENABLED, selectedModel, selectedEffort, showAutoModelOption = true, availableModels, modelListLoading, onModelChange, onEffortChange, providerConnectStatusEnabled, onConnectProvider, onConnectLocalRuntime, imageModelMenu, draftScope, contextItems = [], onRemoveContextItem, plusMenuMode = "full", extensionTools = false, interceptBuildRequestsForBuilder = false, onAttachmentError, }) {
904
+ export function TiptapComposer({ placeholder, disabled = false, maxDocumentAttachmentBytes = MAX_DOCUMENT_ATTACHMENT_BYTES, documentAttachmentLimitLabel = "PDFs", focusRef, initialText, initialTextKey, onSubmit, onBeforeSubmit, clearOnSubmit = true, onTextChange, actionButton, willQueue = false, extraActionButton, stopButton, attachButton, modeControl, toolbarSlot, layoutVariant = "default", slashCommands = [], slashSkills = [], includeDefaultSlashCommands = true, includeDefaultSlashSkills = true, onSlashCommand, execMode, onExecModeChange, planModeDisabled = false, planModeDisabledReason, voiceEnabled = DEFAULT_VOICE_DICTATION_ENABLED, selectedModel, selectedEffort, showAutoModelOption = true, availableModels, modelListLoading, onModelChange, onEffortChange, providerConnectStatusEnabled, onConnectProvider, onConnectLocalRuntime, imageModelMenu, draftScope, contextItems = [], onRemoveContextItem, plusMenuMode = "full", extensionTools = false, interceptBuildRequestsForBuilder = false, onAttachmentError, }) {
748
905
  const adapters = useComposerRuntimeAdapters();
749
906
  const t = adapters.translate;
750
907
  const sendButtonTooltip = t(getComposerSendTooltipKey(willQueue), {
@@ -788,9 +945,9 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
788
945
  const filteredMentionItems = useMemo(() => filterMentionItemsForSlots(mentionItems, slotReferences), [mentionItems, slotReferences]);
789
946
  const { skills, hint, isLoading: skillsLoading, } = useSkills(includeDefaultSlashSkills && popover?.type === "/");
790
947
  const allSlashCommands = useMemo(() => mergeSlashCommands([
791
- ...(includeDefaultSlashCommands ? BUILT_IN_COMMANDS : []),
948
+ ...(includeDefaultSlashCommands ? builtInCommands(t) : []),
792
949
  ...slashCommands,
793
- ]), [includeDefaultSlashCommands, slashCommands]);
950
+ ]), [includeDefaultSlashCommands, slashCommands, t]);
794
951
  const allSlashSkills = useMemo(() => mergeSlashSkills([
795
952
  ...(includeDefaultSlashSkills ? skills : []),
796
953
  ...slashSkills,
@@ -856,12 +1013,14 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
856
1013
  }, [composerRuntime]);
857
1014
  // Tiptap reads extension config once at init; ref keeps runtime prop
858
1015
  // changes visible to Placeholder's function form.
859
- const placeholderRef = useRef(placeholder);
860
- useEffect(() => {
861
- placeholderRef.current = composerMode
862
- ? COMPOSER_MODE_CONFIGS[composerMode].placeholder
863
- : placeholder;
864
- }, [placeholder, composerMode]);
1016
+ const resolvedPlaceholder = composerMode
1017
+ ? localizedComposerModeConfig(composerMode, t).placeholder
1018
+ : (placeholder ??
1019
+ t("agentChat.composer.messageAgent", {
1020
+ defaultValue: "Message agent...",
1021
+ }));
1022
+ const placeholderRef = useRef(resolvedPlaceholder);
1023
+ placeholderRef.current = resolvedPlaceholder;
865
1024
  const editor = useEditor({
866
1025
  extensions: createTiptapComposerExtensions(() => placeholderRef.current),
867
1026
  editable: !disabled,
@@ -944,7 +1103,9 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
944
1103
  void Promise.all(attachments.map((file) => composerRuntime.addAttachment(file))).catch((error) => {
945
1104
  const msg = error instanceof Error
946
1105
  ? error.message
947
- : "Could not attach the pasted image. Try a different format.";
1106
+ : t("agentChat.composer.pastedImageError", {
1107
+ defaultValue: "Could not attach the pasted image. Try a different format.",
1108
+ });
948
1109
  onAttachmentErrorRef.current?.(msg);
949
1110
  });
950
1111
  return true;
@@ -963,7 +1124,9 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
963
1124
  .catch((error) => {
964
1125
  const msg = error instanceof Error
965
1126
  ? error.message
966
- : "Could not attach the pasted text.";
1127
+ : t("agentChat.composer.pastedTextError", {
1128
+ defaultValue: "Could not attach the pasted text.",
1129
+ });
967
1130
  onAttachmentErrorRef.current?.(msg);
968
1131
  });
969
1132
  return true;
@@ -980,7 +1143,9 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
980
1143
  onError: (error) => {
981
1144
  const msg = error instanceof Error
982
1145
  ? error.message
983
- : "Could not attach the dropped file. Try a different format.";
1146
+ : t("agentChat.composer.droppedFileError", {
1147
+ defaultValue: "Could not attach the dropped file. Try a different format.",
1148
+ });
984
1149
  onAttachmentErrorRef.current?.(msg);
985
1150
  },
986
1151
  });
@@ -1145,6 +1310,13 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1145
1310
  },
1146
1311
  },
1147
1312
  });
1313
+ // Placeholder decorations are computed by ProseMirror. Dispatching an empty
1314
+ // transaction makes a locale or composer-mode change visible immediately.
1315
+ useEffect(() => {
1316
+ if (!isComposerEditorUsable(editor))
1317
+ return;
1318
+ editor.view.dispatch(editor.state.tr.setSelection(editor.state.selection));
1319
+ }, [editor, resolvedPlaceholder]);
1148
1320
  // A tab can stay mounted while becoming the active composer later. Publish
1149
1321
  // its existing draft when the host starts observing it so contextual UI is
1150
1322
  // correct immediately after a tab switch, not only after the next keystroke.
@@ -1340,29 +1512,52 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1340
1512
  const activeContext = trimVoiceContextValue(adapters.agentChat.formatContextItems(contextItems) ||
1341
1513
  formatPromptContextItems(contextItems), 3200);
1342
1514
  if (activeContext) {
1343
- snippets.push({ label: "Active app context", value: activeContext });
1515
+ snippets.push({
1516
+ label: t("agentChat.composer.activeAppContext", {
1517
+ defaultValue: "Active app context",
1518
+ }),
1519
+ value: activeContext,
1520
+ });
1344
1521
  }
1345
1522
  const selectedReferences = trimVoiceContextValue(slotReferences.map((ref) => slotReferenceTitle(ref)).join(", "), 1200);
1346
1523
  if (selectedReferences) {
1347
1524
  snippets.push({
1348
- label: "Selected references",
1525
+ label: t("agentChat.composer.selectedReferences", {
1526
+ defaultValue: "Selected references",
1527
+ }),
1349
1528
  value: selectedReferences,
1350
1529
  });
1351
1530
  }
1352
1531
  const draft = isComposerEditorUsable(editor)
1353
1532
  ? trimVoiceContextValue(editor.state.doc.textContent, 1200)
1354
1533
  : null;
1355
- if (draft)
1356
- snippets.push({ label: "Current draft", value: draft });
1534
+ if (draft) {
1535
+ snippets.push({
1536
+ label: t("agentChat.composer.currentDraft", {
1537
+ defaultValue: "Current draft",
1538
+ }),
1539
+ value: draft,
1540
+ });
1541
+ }
1357
1542
  if (typeof document !== "undefined") {
1358
1543
  const title = trimVoiceContextValue(document.title, 160);
1359
- if (title)
1360
- snippets.push({ label: "Page title", value: title });
1544
+ if (title) {
1545
+ snippets.push({
1546
+ label: t("agentChat.composer.pageTitle", {
1547
+ defaultValue: "Page title",
1548
+ }),
1549
+ value: title,
1550
+ });
1551
+ }
1361
1552
  }
1362
1553
  if (typeof window !== "undefined") {
1363
1554
  const route = trimVoiceContextValue(window.location.pathname, 240);
1364
- if (route)
1365
- snippets.push({ label: "Route", value: route });
1555
+ if (route) {
1556
+ snippets.push({
1557
+ label: t("agentChat.composer.route", { defaultValue: "Route" }),
1558
+ value: route,
1559
+ });
1560
+ }
1366
1561
  }
1367
1562
  if (snippets.length === 0)
1368
1563
  return undefined;
@@ -1371,7 +1566,7 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1371
1566
  mode: "dictation",
1372
1567
  snippets,
1373
1568
  };
1374
- }, [adapters, contextItems, editor, slotReferences]);
1569
+ }, [adapters, contextItems, editor, slotReferences, t]);
1375
1570
  const voice = useVoiceDictation({
1376
1571
  onTranscript: insertTranscript,
1377
1572
  onLiveUpdate: handleLiveUpdate,
@@ -1576,6 +1771,7 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1576
1771
  const oversizedDocumentError = getOversizedDocumentAttachmentError(attachments, {
1577
1772
  maxBytes: maxDocumentAttachmentBytes,
1578
1773
  label: documentAttachmentLimitLabel,
1774
+ translate: t,
1579
1775
  });
1580
1776
  if (oversizedDocumentError) {
1581
1777
  onAttachmentErrorRef.current?.(oversizedDocumentError);
@@ -1627,15 +1823,22 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1627
1823
  return;
1628
1824
  // Composer mode: send with context via agent chat bridge
1629
1825
  if (composerMode) {
1630
- const config = COMPOSER_MODE_CONFIGS[composerMode];
1826
+ const config = localizedComposerModeConfig(composerMode, t);
1631
1827
  config.beforeSend?.();
1632
1828
  const message = displayableComposerModeMessage({
1633
1829
  messagePrefix: config.messagePrefix,
1634
1830
  trimmedText: trimmed,
1635
1831
  attachmentCount: attachments.length,
1832
+ attachedContextFallback: t("agentChat.composer.useAttachedContext", {
1833
+ defaultValue: "Use the attached context.",
1834
+ }),
1636
1835
  });
1637
1836
  const modePrompt = trimmed ||
1638
- (attachments.length > 0 ? "Use the attached context." : "");
1837
+ (attachments.length > 0
1838
+ ? t("agentChat.composer.useAttachedContext", {
1839
+ defaultValue: "Use the attached context.",
1840
+ })
1841
+ : "");
1639
1842
  if (attachments.length > 0) {
1640
1843
  composerRuntime.setText(`${message}\n\n<context>\n${config.getContext(modePrompt)}\n</context>`);
1641
1844
  composerRuntime.send();
@@ -1712,6 +1915,7 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1712
1915
  syncComposerState,
1713
1916
  voice,
1714
1917
  allSlashCommands,
1918
+ t,
1715
1919
  ]);
1716
1920
  // Helper functions that operate on the editor view directly
1717
1921
  // These are called from handleKeyDown which can't use React state
@@ -1899,13 +2103,19 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1899
2103
  if (isComposerEditorUsable(editor)) {
1900
2104
  editor.commands.focus("end");
1901
2105
  }
1902
- }, "aria-label": `Remove ${slotReferenceTitle(ref)} reference`, className: "ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconX, { className: "h-3 w-3" }) })] }, ref.slotKey))), contextItems.map((item) => (_jsxs("span", { "data-state": selectedContextItemKey === item.key ? "selected" : undefined, className: `inline-flex max-w-full items-center gap-1.5 rounded-md border px-2 py-1 text-[11px] font-medium text-foreground ${selectedContextItemKey === item.key
2106
+ }, "aria-label": t("agentChat.composer.removeReference", {
2107
+ defaultValue: "Remove {{name}} reference",
2108
+ name: slotReferenceTitle(ref),
2109
+ }), className: "ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconX, { className: "h-3 w-3" }) })] }, ref.slotKey))), contextItems.map((item) => (_jsxs("span", { "data-state": selectedContextItemKey === item.key ? "selected" : undefined, className: `inline-flex max-w-full items-center gap-1.5 rounded-md border px-2 py-1 text-[11px] font-medium text-foreground ${selectedContextItemKey === item.key
1903
2110
  ? "border-ring bg-accent ring-2 ring-ring/40"
1904
2111
  : "border-border bg-muted/50"}`, children: [_jsx(IconClipboardList, { className: "h-3 w-3 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 truncate", children: item.title }), _jsx("button", { type: "button", onClick: () => {
1905
2112
  selectedContextItemKeyRef.current = null;
1906
2113
  setSelectedContextItemKey(null);
1907
2114
  onRemoveContextItem?.(item.key);
1908
- }, "aria-label": `Remove ${item.title} context`, className: "ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconX, { className: "h-3 w-3" }) })] }, item.key)))] })), _jsx("div", { "data-agent-composer-variant": layoutVariant, "data-agent-composer-slot": "editor-wrap", className: `agent-composer-editor-wrap ${composerMode || hasContextRows ? "px-2 pt-1 pb-1" : "px-2 pt-2 pb-1"}`, children: _jsx(EditorContent, { editor: editor, "data-agent-composer-variant": layoutVariant, "data-agent-composer-slot": "editor", className: "agent-composer-editor aui-composer flex-1 min-w-0 [&_.ProseMirror]:outline-none [&_.ProseMirror_p]:m-0 px-0.5" }) }), voiceEnabled && _jsx(VoiceRecordingOverlay, { voice: voice }), _jsxs("div", { "data-agent-composer-variant": layoutVariant, "data-agent-composer-slot": "toolbar", className: "agent-composer-toolbar flex items-center gap-1 px-2 py-1.5", children: [attachButton ??
2115
+ }, "aria-label": t("agentChat.composer.removeContext", {
2116
+ defaultValue: "Remove {{name}} context",
2117
+ name: item.title,
2118
+ }), className: "ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconX, { className: "h-3 w-3" }) })] }, item.key)))] })), _jsx("div", { "data-agent-composer-variant": layoutVariant, "data-agent-composer-slot": "editor-wrap", className: `agent-composer-editor-wrap ${composerMode || hasContextRows ? "px-2 pt-1 pb-1" : "px-2 pt-2 pb-1"}`, children: _jsx(EditorContent, { editor: editor, "data-agent-composer-variant": layoutVariant, "data-agent-composer-slot": "editor", className: "agent-composer-editor aui-composer flex-1 min-w-0 [&_.ProseMirror]:outline-none [&_.ProseMirror_p]:m-0 px-0.5" }) }), voiceEnabled && _jsx(VoiceRecordingOverlay, { voice: voice }), _jsxs("div", { "data-agent-composer-variant": layoutVariant, "data-agent-composer-slot": "toolbar", className: "agent-composer-toolbar flex items-center gap-1 px-2 py-1.5", children: [attachButton ??
1909
2119
  (plusMenuMode === "hidden" ? null : (_jsx(ComposerPlusMenu, { onSelectMode: handleSelectMode, mode: plusMenuMode, extensionTools: extensionTools, onAttachmentError: onAttachmentError }))), toolbarSlot ?? modeControl, _jsx("div", { "data-agent-composer-slot": "toolbar-spacer", className: "flex-1" }), selectedModel && availableModels && onModelChange && (_jsx(ModelSelector, { model: selectedModel, effort: selectedEffort, engines: availableModels, showAutoModelOption: showAutoModelOption, modelListLoading: modelListLoading, onChange: onModelChange, onEffortChange: onEffortChange, providerConnectStatusEnabled: providerConnectStatusEnabled, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, imageModel: imageModelMenu })), execMode && onExecModeChange && (_jsx(ModeSelector, { mode: execMode, onChange: onExecModeChange, planModeDisabled: planModeDisabled, planModeDisabledReason: planModeDisabledReason })), actionButton ?? (_jsxs(_Fragment, { children: [extraActionButton, voiceEnabled && (_jsx(VoiceButton, { voice: voice, isMac: isMac, disabled: disabled })), primaryAction === "stop" ? (stopButton) : (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: () => void submitComposer("immediate"), disabled: !canSend, "data-agent-composer-slot": "send-button", className: "agent-composer-send-button shrink-0 flex h-7 w-7 items-center justify-center rounded-md bg-primary text-primary-foreground hover:opacity-90 transition-[opacity,transform] duration-150 active:scale-[0.97] disabled:opacity-30 disabled:cursor-not-allowed", children: _jsx(IconArrowUp, { className: "h-3.5 w-3.5" }) }) }), _jsx(TooltipContent, { children: sendButtonTooltip })] }))] }))] }), _jsx(MentionPopover, { ref: popoverRef, type: popover?.type ?? "@", position: popover?.position ?? null, mentionItems: filteredMentionItems, skills: filteredSkills, commands: filteredCommands, hint: hint, isLoading: popover?.type === "@" ? mentionsLoading : skillsLoading, query: popover?.query ?? "", onSelectMention: handleSelectMention, onSelectSkill: handleSelectSkill, onSelectCommand: handleSelectCommand, onClose: closePopover })] }));
1910
2120
  }
1911
2121
  //# sourceMappingURL=TiptapComposer.js.map