@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
@@ -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,21 +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
- return gpt56Variant[0].toUpperCase() + gpt56Variant.slice(1).toLowerCase();
607
+ const variant = gpt56Variant[0].toUpperCase() + gpt56Variant.slice(1).toLowerCase();
608
+ return `GPT-5.6 ${variant}`;
503
609
  }
504
- return friendlyModelName(model);
610
+ return friendlyModelName(model, t);
505
611
  }
506
- export function compactComposerReasoningEffortLabel(effort) {
507
- if (effort === "medium" || effort === "auto")
508
- return "Med";
509
- if (effort === "minimal")
510
- return "Min";
511
- if (effort === "xhigh")
512
- return "XHigh";
513
- 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
+ }
514
651
  }
515
652
  /**
516
653
  * Deduplicate models to only the latest version per family.
@@ -561,16 +698,21 @@ const MODEL_COST_TIERS = [
561
698
  ["flash", 1],
562
699
  ["pro", 3],
563
700
  ];
564
- const COST_TIER_LABELS = { 1: "Lower", 2: "Medium", 3: "Higher" };
565
701
  export function composerModelCostTier(model) {
566
702
  const normalized = model.toLowerCase();
567
703
  return MODEL_COST_TIERS.find(([token]) => normalized.includes(token))?.[1];
568
704
  }
569
705
  function ModelCostTier({ model }) {
706
+ const t = useComposerRuntimeAdapters().translate;
570
707
  const tier = composerModelCostTier(model);
571
708
  if (!tier)
572
709
  return null;
573
- 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 })] }));
574
716
  }
575
717
  export function getComposerReasoningEffortOptions(model) {
576
718
  return model === "auto"
@@ -596,7 +738,7 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
596
738
  getComposerReasoningEffortOptions(model);
597
739
  const selectedEffort = reasoning?.resolve?.(model, effort) ??
598
740
  resolveReasoningEffortSelection(model, effort ?? defaultEffort);
599
- const effortLabel = reasoning?.label ?? reasoningEffortLabel;
741
+ const effortLabel = useCallback((value) => localizedReasoningEffortLabel(t, value, (reasoning?.label ?? reasoningEffortLabel)(value)), [reasoning?.label, t]);
600
742
  // Collapse non-selected families by default. The family containing the
601
743
  // currently-selected model stays expanded so the user sees their pick at
602
744
  // a glance; clicking another family's header expands it inline.
@@ -668,9 +810,13 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
668
810
  onConnectLocalRuntime?.(engine);
669
811
  setOpen(false);
670
812
  }, [onConnectLocalRuntime]);
671
- 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
672
- ? `. Effort: ${effortLabel(selectedEffort)}`
673
- : ""}`, 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: () => {
674
820
  if (onConnectProvider) {
675
821
  onConnectProvider();
676
822
  }
@@ -689,14 +835,17 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
689
835
  defaultValue: "Add your own keys",
690
836
  }) }), _jsx("span", { className: "block text-[11px] text-muted-foreground", children: t("agentPanel.configureProviderKeys", {
691
837
  defaultValue: "Choose a cloud, gateway, or local provider",
692
- }) })] })] }), !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 &&
693
842
  imageModel.options.map((option) => (_jsxs("button", { type: "button", onClick: () => {
694
843
  imageModel.onChange(option.value);
695
844
  setImageExpanded(false);
696
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: () => {
697
846
  onChange("auto", autoModelGroup.engine);
698
847
  setOpen(false);
699
- }, 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 &&
700
849
  providerGroups.length > 0 &&
701
850
  !onlyConnectPathAvailable && (_jsx("div", { className: "my-1 border-t border-border" })), !onlyConnectPathAvailable &&
702
851
  providerGroups.map((group) => {
@@ -709,10 +858,12 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
709
858
  const statusLabel = group.statusLabel ??
710
859
  (!group.configured
711
860
  ? canConnectLocalRuntime
712
- ? "sign in"
713
- : "needs API key"
861
+ ? t("agentChat.auth.logIn", { defaultValue: "Sign in" })
862
+ : t("agentChat.composer.needsApiKey", {
863
+ defaultValue: "needs API key",
864
+ })
714
865
  : undefined);
715
- 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
716
867
  ? connectLocalRuntime(group.engine)
717
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 &&
718
869
  models.map((m) => (_jsxs("button", { type: "button", onClick: () => {
@@ -734,16 +885,23 @@ function ModelSelector({ model, effort, engines, showAutoModelOption = true, mod
734
885
  setOpen(false);
735
886
  }, className: `flex w-full items-center gap-3 ps-7 pe-3 py-1.5 text-start ${group.configured
736
887
  ? "hover:bg-accent/50"
737
- : "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));
738
889
  }), !showModelListSkeleton &&
739
890
  !onlyConnectPathAvailable &&
740
- 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 &&
741
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)))] }))] })] }));
742
895
  }
743
896
  function ModelSelectorSkeleton() {
744
- 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)))] }));
745
903
  }
746
- 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, }) {
747
905
  const adapters = useComposerRuntimeAdapters();
748
906
  const t = adapters.translate;
749
907
  const sendButtonTooltip = t(getComposerSendTooltipKey(willQueue), {
@@ -787,9 +945,9 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
787
945
  const filteredMentionItems = useMemo(() => filterMentionItemsForSlots(mentionItems, slotReferences), [mentionItems, slotReferences]);
788
946
  const { skills, hint, isLoading: skillsLoading, } = useSkills(includeDefaultSlashSkills && popover?.type === "/");
789
947
  const allSlashCommands = useMemo(() => mergeSlashCommands([
790
- ...(includeDefaultSlashCommands ? BUILT_IN_COMMANDS : []),
948
+ ...(includeDefaultSlashCommands ? builtInCommands(t) : []),
791
949
  ...slashCommands,
792
- ]), [includeDefaultSlashCommands, slashCommands]);
950
+ ]), [includeDefaultSlashCommands, slashCommands, t]);
793
951
  const allSlashSkills = useMemo(() => mergeSlashSkills([
794
952
  ...(includeDefaultSlashSkills ? skills : []),
795
953
  ...slashSkills,
@@ -855,12 +1013,14 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
855
1013
  }, [composerRuntime]);
856
1014
  // Tiptap reads extension config once at init; ref keeps runtime prop
857
1015
  // changes visible to Placeholder's function form.
858
- const placeholderRef = useRef(placeholder);
859
- useEffect(() => {
860
- placeholderRef.current = composerMode
861
- ? COMPOSER_MODE_CONFIGS[composerMode].placeholder
862
- : placeholder;
863
- }, [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;
864
1024
  const editor = useEditor({
865
1025
  extensions: createTiptapComposerExtensions(() => placeholderRef.current),
866
1026
  editable: !disabled,
@@ -943,7 +1103,9 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
943
1103
  void Promise.all(attachments.map((file) => composerRuntime.addAttachment(file))).catch((error) => {
944
1104
  const msg = error instanceof Error
945
1105
  ? error.message
946
- : "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
+ });
947
1109
  onAttachmentErrorRef.current?.(msg);
948
1110
  });
949
1111
  return true;
@@ -962,7 +1124,9 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
962
1124
  .catch((error) => {
963
1125
  const msg = error instanceof Error
964
1126
  ? error.message
965
- : "Could not attach the pasted text.";
1127
+ : t("agentChat.composer.pastedTextError", {
1128
+ defaultValue: "Could not attach the pasted text.",
1129
+ });
966
1130
  onAttachmentErrorRef.current?.(msg);
967
1131
  });
968
1132
  return true;
@@ -979,7 +1143,9 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
979
1143
  onError: (error) => {
980
1144
  const msg = error instanceof Error
981
1145
  ? error.message
982
- : "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
+ });
983
1149
  onAttachmentErrorRef.current?.(msg);
984
1150
  },
985
1151
  });
@@ -1144,6 +1310,13 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1144
1310
  },
1145
1311
  },
1146
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]);
1147
1320
  // A tab can stay mounted while becoming the active composer later. Publish
1148
1321
  // its existing draft when the host starts observing it so contextual UI is
1149
1322
  // correct immediately after a tab switch, not only after the next keystroke.
@@ -1339,29 +1512,52 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1339
1512
  const activeContext = trimVoiceContextValue(adapters.agentChat.formatContextItems(contextItems) ||
1340
1513
  formatPromptContextItems(contextItems), 3200);
1341
1514
  if (activeContext) {
1342
- 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
+ });
1343
1521
  }
1344
1522
  const selectedReferences = trimVoiceContextValue(slotReferences.map((ref) => slotReferenceTitle(ref)).join(", "), 1200);
1345
1523
  if (selectedReferences) {
1346
1524
  snippets.push({
1347
- label: "Selected references",
1525
+ label: t("agentChat.composer.selectedReferences", {
1526
+ defaultValue: "Selected references",
1527
+ }),
1348
1528
  value: selectedReferences,
1349
1529
  });
1350
1530
  }
1351
1531
  const draft = isComposerEditorUsable(editor)
1352
1532
  ? trimVoiceContextValue(editor.state.doc.textContent, 1200)
1353
1533
  : null;
1354
- if (draft)
1355
- 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
+ }
1356
1542
  if (typeof document !== "undefined") {
1357
1543
  const title = trimVoiceContextValue(document.title, 160);
1358
- if (title)
1359
- 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
+ }
1360
1552
  }
1361
1553
  if (typeof window !== "undefined") {
1362
1554
  const route = trimVoiceContextValue(window.location.pathname, 240);
1363
- if (route)
1364
- 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
+ }
1365
1561
  }
1366
1562
  if (snippets.length === 0)
1367
1563
  return undefined;
@@ -1370,7 +1566,7 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1370
1566
  mode: "dictation",
1371
1567
  snippets,
1372
1568
  };
1373
- }, [adapters, contextItems, editor, slotReferences]);
1569
+ }, [adapters, contextItems, editor, slotReferences, t]);
1374
1570
  const voice = useVoiceDictation({
1375
1571
  onTranscript: insertTranscript,
1376
1572
  onLiveUpdate: handleLiveUpdate,
@@ -1575,6 +1771,7 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1575
1771
  const oversizedDocumentError = getOversizedDocumentAttachmentError(attachments, {
1576
1772
  maxBytes: maxDocumentAttachmentBytes,
1577
1773
  label: documentAttachmentLimitLabel,
1774
+ translate: t,
1578
1775
  });
1579
1776
  if (oversizedDocumentError) {
1580
1777
  onAttachmentErrorRef.current?.(oversizedDocumentError);
@@ -1626,15 +1823,22 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1626
1823
  return;
1627
1824
  // Composer mode: send with context via agent chat bridge
1628
1825
  if (composerMode) {
1629
- const config = COMPOSER_MODE_CONFIGS[composerMode];
1826
+ const config = localizedComposerModeConfig(composerMode, t);
1630
1827
  config.beforeSend?.();
1631
1828
  const message = displayableComposerModeMessage({
1632
1829
  messagePrefix: config.messagePrefix,
1633
1830
  trimmedText: trimmed,
1634
1831
  attachmentCount: attachments.length,
1832
+ attachedContextFallback: t("agentChat.composer.useAttachedContext", {
1833
+ defaultValue: "Use the attached context.",
1834
+ }),
1635
1835
  });
1636
1836
  const modePrompt = trimmed ||
1637
- (attachments.length > 0 ? "Use the attached context." : "");
1837
+ (attachments.length > 0
1838
+ ? t("agentChat.composer.useAttachedContext", {
1839
+ defaultValue: "Use the attached context.",
1840
+ })
1841
+ : "");
1638
1842
  if (attachments.length > 0) {
1639
1843
  composerRuntime.setText(`${message}\n\n<context>\n${config.getContext(modePrompt)}\n</context>`);
1640
1844
  composerRuntime.send();
@@ -1711,6 +1915,7 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1711
1915
  syncComposerState,
1712
1916
  voice,
1713
1917
  allSlashCommands,
1918
+ t,
1714
1919
  ]);
1715
1920
  // Helper functions that operate on the editor view directly
1716
1921
  // These are called from handleKeyDown which can't use React state
@@ -1898,13 +2103,19 @@ export function TiptapComposer({ placeholder = "Message agent...", disabled = fa
1898
2103
  if (isComposerEditorUsable(editor)) {
1899
2104
  editor.commands.focus("end");
1900
2105
  }
1901
- }, "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
1902
2110
  ? "border-ring bg-accent ring-2 ring-ring/40"
1903
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: () => {
1904
2112
  selectedContextItemKeyRef.current = null;
1905
2113
  setSelectedContextItemKey(null);
1906
2114
  onRemoveContextItem?.(item.key);
1907
- }, "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 ??
1908
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 })] }));
1909
2120
  }
1910
2121
  //# sourceMappingURL=TiptapComposer.js.map