@hanzo/build 0.2.80 → 0.2.82

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 (168) hide show
  1. package/README.md +13 -0
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +1 -20
  5. package/lib/api/agents.js +16 -8
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/links.d.ts +1 -1
  29. package/lib/api/links.js +1 -1
  30. package/lib/api/machines.d.ts +0 -15
  31. package/lib/api/machines.js +6 -5
  32. package/lib/api/mcp.d.ts +0 -9
  33. package/lib/api/mcp.js +3 -2
  34. package/lib/api/members.d.ts +0 -15
  35. package/lib/api/members.js +4 -3
  36. package/lib/api/memory.d.ts +0 -11
  37. package/lib/api/memory.js +2 -1
  38. package/lib/api/places.d.ts +1 -9
  39. package/lib/api/places.js +12 -1
  40. package/lib/api/plugins.d.ts +1 -16
  41. package/lib/api/plugins.js +4 -3
  42. package/lib/api/profile.d.ts +0 -10
  43. package/lib/api/profile.js +5 -4
  44. package/lib/api/projects.d.ts +0 -14
  45. package/lib/api/projects.js +11 -10
  46. package/lib/api/provider.d.ts +2 -14
  47. package/lib/api/provider.js +12 -10
  48. package/lib/api/sandbox.d.ts +0 -17
  49. package/lib/api/sandbox.js +5 -4
  50. package/lib/api/sessions.d.ts +0 -17
  51. package/lib/api/sessions.js +5 -4
  52. package/lib/api/skills.d.ts +1 -15
  53. package/lib/api/skills.js +5 -4
  54. package/lib/api/studio.d.ts +0 -14
  55. package/lib/api/studio.js +6 -5
  56. package/lib/api/turn.d.ts +6 -28
  57. package/lib/api/turn.js +105 -49
  58. package/lib/api/verdict.d.ts +0 -9
  59. package/lib/api/verdict.js +2 -1
  60. package/lib/api/webhooks.d.ts +1 -14
  61. package/lib/api/webhooks.js +5 -4
  62. package/lib/ask.js +7 -4
  63. package/lib/automations.d.ts +5 -4
  64. package/lib/automations.js +95 -74
  65. package/lib/back.js +10 -4
  66. package/lib/builder.js +7 -5
  67. package/lib/carry.js +20 -16
  68. package/lib/customize/agents.js +78 -31
  69. package/lib/customize/connectors.js +62 -48
  70. package/lib/customize/index.js +43 -10
  71. package/lib/customize/plugins.js +34 -22
  72. package/lib/customize/say.d.ts +13 -1
  73. package/lib/customize/say.js +15 -21
  74. package/lib/customize/skills.js +31 -22
  75. package/lib/customize/ui.d.ts +5 -9
  76. package/lib/customize/ui.js +26 -16
  77. package/lib/data.d.ts +1 -1
  78. package/lib/data.js +3 -2
  79. package/lib/desk.js +45 -33
  80. package/lib/door.js +35 -10
  81. package/lib/environment.js +25 -19
  82. package/lib/files.js +43 -34
  83. package/lib/find.js +12 -9
  84. package/lib/foot.js +7 -4
  85. package/lib/forge.js +61 -40
  86. package/lib/gear.d.ts +2 -1
  87. package/lib/gear.js +6 -4
  88. package/lib/git.js +29 -19
  89. package/lib/landing.d.ts +5 -5
  90. package/lib/landing.js +52 -44
  91. package/lib/library.js +73 -51
  92. package/lib/merge.d.ts +2 -2
  93. package/lib/meter.d.ts +1 -1
  94. package/lib/meter.js +14 -5
  95. package/lib/mic.js +12 -5
  96. package/lib/mind.d.ts +1 -0
  97. package/lib/mind.js +5 -3
  98. package/lib/nav.d.ts +6 -5
  99. package/lib/nav.js +37 -30
  100. package/lib/panel.d.ts +2 -1
  101. package/lib/panel.js +52 -36
  102. package/lib/plan.d.ts +4 -20
  103. package/lib/plan.js +26 -10
  104. package/lib/plans.js +49 -30
  105. package/lib/plugins/design.js +3 -2
  106. package/lib/plugins/index.d.ts +23 -4
  107. package/lib/plugins/index.js +11 -26
  108. package/lib/plugins/pdf.js +2 -1
  109. package/lib/plugins/project.js +7 -4
  110. package/lib/plugins/research.js +3 -2
  111. package/lib/plugins/sketch.js +6 -3
  112. package/lib/plugins/slides.js +3 -2
  113. package/lib/plugins/template.js +6 -3
  114. package/lib/plus.js +25 -13
  115. package/lib/prefs.d.ts +0 -14
  116. package/lib/prefs.js +2 -1
  117. package/lib/project.js +69 -62
  118. package/lib/prompt.d.ts +12 -11
  119. package/lib/prompt.js +38 -20
  120. package/lib/publish.js +17 -12
  121. package/lib/reply.js +6 -3
  122. package/lib/run.js +72 -54
  123. package/lib/say.d.ts +2 -0
  124. package/lib/say.js +7 -0
  125. package/lib/scheduled.js +54 -43
  126. package/lib/section.d.ts +1 -0
  127. package/lib/section.js +8 -2
  128. package/lib/settings/account.d.ts +1 -0
  129. package/lib/settings/account.js +31 -21
  130. package/lib/settings/billing.js +55 -28
  131. package/lib/settings/capabilities.js +26 -15
  132. package/lib/settings/card.js +19 -12
  133. package/lib/settings/code.js +22 -11
  134. package/lib/settings/credits.d.ts +4 -4
  135. package/lib/settings/credits.js +34 -21
  136. package/lib/settings/environments.js +21 -14
  137. package/lib/settings/general.js +26 -14
  138. package/lib/settings/index.js +6 -3
  139. package/lib/settings/integrations.js +42 -28
  140. package/lib/settings/keys.js +37 -19
  141. package/lib/settings/list.d.ts +0 -5
  142. package/lib/settings/list.js +24 -15
  143. package/lib/settings/machines.d.ts +3 -0
  144. package/lib/settings/machines.js +63 -38
  145. package/lib/settings/members.js +23 -14
  146. package/lib/settings/memory.js +12 -6
  147. package/lib/settings/notifications.js +32 -19
  148. package/lib/settings/plan.d.ts +1 -1
  149. package/lib/settings/plan.js +40 -17
  150. package/lib/settings/privacy.js +26 -13
  151. package/lib/settings/ui.d.ts +3 -2
  152. package/lib/settings/ui.js +9 -3
  153. package/lib/settings/usage.js +7 -3
  154. package/lib/shelf.js +20 -17
  155. package/lib/side.d.ts +1 -26
  156. package/lib/side.js +11 -5
  157. package/lib/standing.d.ts +2 -10
  158. package/lib/standing.js +5 -3
  159. package/lib/suggest.d.ts +8 -28
  160. package/lib/suggest.js +39 -4
  161. package/lib/suggestions.js +14 -5
  162. package/lib/summary.d.ts +1 -1
  163. package/lib/summary.js +4 -1
  164. package/lib/sync.js +22 -12
  165. package/lib/tools.d.ts +4 -3
  166. package/lib/tools.js +10 -7
  167. package/lib/transcript.js +10 -8
  168. package/package.json +4 -2
package/lib/prompt.js CHANGED
@@ -17,8 +17,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
17
  * composer through `Chat` (its `composer` prop) and one that draws it directly
18
18
  * (`<Prompt>`) draw the same thing. The frame is as wide as the text above it
19
19
  * (`measure`).
20
+ *
21
+ * Its words are the reader's language (@hanzo/i18n): the components here ask
22
+ * `useI18n` themselves, and `prompt`, which is not a component, takes `t` from
23
+ * `useMention`'s state, English without one. Model names, a host's own words
24
+ * (its placeholder, a shelf's rows and links) and what a person typed are
25
+ * drawn as given.
20
26
  */
21
27
  import { SizableText, useMedia, XStack, YStack } from '@hanzo/gui';
28
+ import { phrase } from '@hanzo/i18n';
29
+ import { useI18n } from '@hanzo/i18n/react';
22
30
  import { ArrowUp, AudioLines, Check, Square, X } from '@hanzogui/lucide-icons-2';
23
31
  import { Control } from '@hanzo/composer';
24
32
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSearch, DropdownMenuTrigger, Popover, PopoverContent, PopoverTrigger } from '@hanzo/ui';
@@ -31,6 +39,7 @@ import { MODES } from './gear.js';
31
39
  import { Dictation } from './mic.js';
32
40
  import { EFFORTS, grants, nameOf, reasons, ROUTER_IDS } from './mind.js';
33
41
  import { Plus } from './plus.js';
42
+ import { english } from './say.js';
34
43
  /** The reading measure: @hanzo/design's prose width, which Width (appearance) retunes. */
35
44
  export const MEASURE = 'var(--container-prose, 48rem)';
36
45
  /** The space between turns, px: one rhythm for Chat and Dev. */
@@ -50,7 +59,7 @@ const QUIET = { bg: 'transparent', color: 'var(--foreground)', hoverStyle: { bg:
50
59
  /** The model rows the menu draws before anything is typed. */
51
60
  const SHOWN = 40;
52
61
  /** What every Composer placeholder says, unless a surface names who is listening. */
53
- export const ASK = 'Type / for skills';
62
+ export const ASK = phrase('Type / for skills');
54
63
  /**
55
64
  * The frame as a pane of its own, floating on the pane it is drawn in: the
56
65
  * `--pane-*` cut on design's first paper rung.
@@ -71,9 +80,10 @@ export function useEar(t) {
71
80
  function Word({ children, dim = false, label, open }) {
72
81
  return (_jsx(XStack, { render: "button", "aria-label": label, "aria-expanded": open, items: "center", height: 24, px: 6, rounded: 6, cursor: "pointer", bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineColor: '$outlineColor', outlineWidth: 2, outlineStyle: 'solid' }, "$max-sm": { height: 44, px: 8 }, children: _jsx(SizableText, { fontSize: 14, fontWeight: dim ? '400' : '500', color: dim ? '$hint' : '$ink', numberOfLines: 1, children: children }) }));
73
82
  }
74
- /** A titled list of choices, each with a line saying what it does and a check on the one in force. */
83
+ /** A titled list of choices, each with a line saying what it does and a check on the one in force. The items' words are marked (`phrase`); the title comes translated. */
75
84
  function Choices({ title, items, value, onPick, }) {
76
- return (_jsxs(YStack, { role: "listbox", "aria-label": title, gap: 2, "data-slot": "choices", children: [_jsx(SizableText, { fontSize: 13, color: "$hint", px: 10, pt: 6, pb: 4, children: title }), items.map((c) => (_jsxs(XStack, { render: "button", role: "option", "aria-selected": c.id === value, "aria-disabled": c.off || undefined, onPress: () => !c.off && onPick(c.id), items: "flex-start", gap: 12, px: 10, py: 8, rounded: 8, width: "100%", cursor: c.off ? 'default' : 'pointer', opacity: c.off ? 0.5 : 1, hoverStyle: c.off ? undefined : { bg: '$hover' }, children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", gap: 2, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", style: { textAlign: 'left' }, children: c.label }), _jsx(SizableText, { fontSize: 13, color: "$hint", style: { textAlign: 'left' }, children: c.note })] }), c.id === value ? _jsx(Check, { size: 16, color: "var(--primary)", mt: 2, "aria-hidden": true }) : null] }, c.id)))] }));
85
+ const { t } = useI18n();
86
+ return (_jsxs(YStack, { role: "listbox", "aria-label": title, gap: 2, "data-slot": "choices", children: [_jsx(SizableText, { fontSize: 13, color: "$hint", px: 10, pt: 6, pb: 4, children: title }), items.map((c) => (_jsxs(XStack, { render: "button", role: "option", "aria-selected": c.id === value, "aria-disabled": c.off || undefined, onPress: () => !c.off && onPick(c.id), items: "flex-start", gap: 12, px: 10, py: 8, rounded: 8, width: "100%", cursor: c.off ? 'default' : 'pointer', opacity: c.off ? 0.5 : 1, hoverStyle: c.off ? undefined : { bg: '$hover' }, children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", gap: 2, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", text: "start", children: t(c.label) }), _jsx(SizableText, { fontSize: 13, color: "$hint", text: "start", children: t(c.note) })] }), c.id === value ? _jsx(Check, { size: 16, color: "var(--primary)", mt: 2, "aria-hidden": true }) : null] }, c.id)))] }));
77
87
  }
78
88
  /** A word in the row that opens a panel of choices. */
79
89
  function Opens({ label, word, dim, children }) {
@@ -82,6 +92,7 @@ function Opens({ label, word, dim, children }) {
82
92
  }
83
93
  /** The model's name, and the catalog behind it: Free's lane to pick from, the rest an upgrade. */
84
94
  function Model({ mind, onUpgrade, limits }) {
95
+ const { t } = useI18n();
85
96
  const [open, setOpen] = useState(false);
86
97
  const [q, setQ] = useState('');
87
98
  // The catalog is hundreds long, and drawing all of it is what made the menu slow
@@ -101,33 +112,39 @@ function Model({ mind, onUpgrade, limits }) {
101
112
  return { groups: cut, more: list.length - cut.reduce((n, g) => n + g.models.length, 0) };
102
113
  }, [mind.models, q]);
103
114
  const routed = mind.model === ENSO;
104
- return (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, placement: "top-end", children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(XStack, { "data-slot": "mind", children: _jsx(Word, { label: `Model: ${nameOf(mind.model, mind.models)}`, open: open, children: nameOf(mind.model, mind.models) }) }) }), _jsxs(DropdownMenuContent, { align: "end", minW: 280, "data-slot": "model-menu", children: [_jsx(DropdownMenuSearch, { value: q, onValueChange: setQ, placeholder: "Search models" }), _jsxs(YStack, { maxH: "min(420px, 60vh)", overflowY: "auto", overflowX: "hidden", "data-slot": "model-rows", children: [!mind.free && !q.trim() ? (_jsx(DropdownMenuItem, { onSelect: () => mind.pick(ENSO), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsxs(YStack, { flex: 1, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", children: "Enso" }), _jsx(SizableText, { fontSize: 12, color: "$hint", children: "Picks the model for each message and step" })] }), routed ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) })) : null, groups.map((g) => (_jsxs(YStack, { role: "group", "aria-label": g.label, children: [_jsx(SizableText, { fontSize: 12, color: "$hint", px: 8, pt: 8, pb: 2, children: g.label }), g.models.map((m) => {
115
+ return (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, placement: "top-end", children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(XStack, { "data-slot": "mind", children: _jsx(Word, { label: t('Model: {name}', { name: nameOf(mind.model, mind.models) }), open: open, children: nameOf(mind.model, mind.models) }) }) }), _jsxs(DropdownMenuContent, { align: "end", minW: 280, "data-slot": "model-menu", children: [_jsx(DropdownMenuSearch, { value: q, onValueChange: setQ, placeholder: t('Search models') }), _jsxs(YStack, { maxH: "min(420px, 60vh)", overflowY: "auto", overflowX: "hidden", "data-slot": "model-rows", children: [!mind.free && !q.trim() ? (_jsx(DropdownMenuItem, { onSelect: () => mind.pick(ENSO), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsxs(YStack, { flex: 1, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", children: "Enso" }), _jsx(SizableText, { fontSize: 12, color: "$hint", children: t('Picks the model for each message and step') })] }), routed ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) })) : null, groups.map((g) => (_jsxs(YStack, { role: "group", "aria-label": g.label, children: [_jsx(SizableText, { fontSize: 12, color: "$hint", px: 8, pt: 8, pb: 2, children: g.label }), g.models.map((m) => {
105
116
  const ok = grants(mind.free, m.id, mind.models);
106
117
  // Paused by the plan's usage: still listed and still picked; the gateway answers from Enso.
107
118
  const paused = isPaused(m, limits);
108
- return (_jsx(DropdownMenuItem, { "data-slot": "model-row", "data-model": m.id, "data-paused": paused ? 'true' : undefined, onSelect: () => (ok ? mind.pick(m.id) : onUpgrade?.(m.id)), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: ok ? '$ink' : '$hint', numberOfLines: 1, flex: 1, children: nameOf(m.id, mind.models) }), ok ? null : (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 6, py: 1, rounded: 6, borderWidth: 1, borderColor: "$borderColor", children: "Upgrade" })), ok && paused ? (_jsx(SizableText, { fontSize: 12, color: "$hint", children: "Paused" })) : null, m.id === mind.model ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, m.id));
109
- })] }, g.key))), more > 0 ? (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 8, py: 8, children: `Type to search ${more} more models` })) : null] })] })] }));
119
+ return (_jsx(DropdownMenuItem, { "data-slot": "model-row", "data-model": m.id, "data-paused": paused ? 'true' : undefined, onSelect: () => (ok ? mind.pick(m.id) : onUpgrade?.(m.id)), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: ok ? '$ink' : '$hint', numberOfLines: 1, flex: 1, children: nameOf(m.id, mind.models) }), ok ? null : (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 6, py: 1, rounded: 6, borderWidth: 1, borderColor: "$borderColor", children: t('Upgrade') })), ok && paused ? (_jsx(SizableText, { fontSize: 12, color: "$hint", children: t('Paused') })) : null, m.id === mind.model ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, m.id));
120
+ })] }, g.key))), more > 0 ? (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 8, py: 8, children: t('Type to search {count} more models', { count: more }) })) : null] })] })] }));
110
121
  }
111
122
  /** The row under the frame: where (Dev), then the model, the effort and the mode. */
112
123
  export function Tune({ mind, mode, where, onUpgrade, limits }) {
124
+ const { t } = useI18n();
113
125
  const thinks = mind ? reasons(mind.model, mind.models) : false;
114
126
  const pace = mind ? EFFORTS.find((e) => e.id === mind.effort) : null;
115
- return (_jsxs(XStack, { "data-slot": "tune", width: "100%", items: "center", gap: 2, flexWrap: "wrap", rowGap: 4, children: [where ? (_jsx(XStack, { items: "center", gap: 2, flexWrap: "wrap", minW: 0, "$max-sm": { width: '100%' }, children: where })) : null, _jsx(XStack, { flex: 1, "$max-sm": { display: 'none' } }), _jsxs(XStack, { items: "center", gap: 2, ml: "auto", shrink: 0, children: [mind ? _jsx(Model, { mind: mind, onUpgrade: onUpgrade, limits: limits }) : null, mind && thinks && pace ? (_jsx(Opens, { label: `Effort: ${pace.label}`, word: pace.label, dim: true, children: (close) => (_jsx(Choices, { title: "Effort", items: EFFORTS, value: mind.effort, onPick: (e) => {
127
+ const now = mode ? MODES.find((m) => m.id === mode.value) : undefined;
128
+ const way = now ? t(now.label) : '';
129
+ return (_jsxs(XStack, { "data-slot": "tune", width: "100%", items: "center", gap: 2, flexWrap: "wrap", rowGap: 4, children: [where ? (_jsx(XStack, { items: "center", gap: 2, flexWrap: "wrap", minW: 0, "$max-sm": { width: '100%' }, children: where })) : null, _jsx(XStack, { flex: 1, "$max-sm": { display: 'none' } }), _jsxs(XStack, { items: "center", gap: 2, marginInlineStart: "auto", shrink: 0, children: [mind ? _jsx(Model, { mind: mind, onUpgrade: onUpgrade, limits: limits }) : null, mind && thinks && pace ? (_jsx(Opens, { label: t('Effort: {level}', { level: t(pace.label) }), word: t(pace.label), dim: true, children: (close) => (_jsx(Choices, { title: t('Effort'), items: EFFORTS, value: mind.effort, onPick: (e) => {
116
130
  mind.pace(e);
117
131
  close();
118
- } })) })) : null, mode ? (_jsxs(Popover, { allowFlip: true, placement: "top-end", children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(XStack, { render: "button", "data-slot": "mode", "aria-label": `Mode: ${MODES.find((m) => m.id === mode.value)?.label}`, items: "center", height: 24, px: 8, ml: 4, rounded: 6, bg: "$hover", cursor: "pointer", hoverStyle: { bg: '$raised' }, focusVisibleStyle: { outlineColor: '$outlineColor', outlineWidth: 2, outlineStyle: 'solid' }, "$max-sm": { height: 44, px: 12 }, children: _jsx(SizableText, { fontSize: 14, color: "$ink", children: MODES.find((m) => m.id === mode.value)?.label }) }) }), _jsx(PopoverContent, { solid: true, p: 6, width: 288, maxW: "calc(100vw - 24px)", children: _jsx(Choices, { title: "Mode", items: mode.items ? MODES.filter((m) => mode.items.includes(m.id)) : MODES, value: mode.value, onPick: mode.set }) })] })) : null] })] }));
132
+ } })) })) : null, mode ? (_jsxs(Popover, { allowFlip: true, placement: "top-end", children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(XStack, { render: "button", "data-slot": "mode", "aria-label": t('Mode: {mode}', { mode: way }), items: "center", height: 24, px: 8, marginInlineStart: 4, rounded: 6, bg: "$hover", cursor: "pointer", hoverStyle: { bg: '$raised' }, focusVisibleStyle: { outlineColor: '$outlineColor', outlineWidth: 2, outlineStyle: 'solid' }, "$max-sm": { height: 44, px: 12 }, children: _jsx(SizableText, { fontSize: 14, color: "$ink", children: way }) }) }), _jsx(PopoverContent, { solid: true, p: 6, width: 288, maxW: "calc(100vw - 24px)", children: _jsx(Choices, { title: t('Mode'), items: mode.items ? MODES.filter((m) => mode.items.includes(m.id)) : MODES, value: mode.value, onPick: mode.set }) })] })) : null] })] }));
119
133
  }
120
134
  /** The `/` or `@` the caret ends on, and the words typed after it. */
121
135
  export function trigger(value) {
122
136
  const m = /(^|\s)([/@])([^\s/@]*)$/.exec(value);
123
137
  return m ? { char: m[2], query: m[3].toLowerCase(), at: m.index + m[1].length } : null;
124
138
  }
125
- /** The list `/` or `@` opens above the frame. */
126
- function Mention({ shelf, rows, index, onPick, tag, name }) {
127
- return (_jsxs(YStack, { role: "listbox", "aria-label": name, "data-slot": "mention", width: "100%", maxH: 240, overflowY: "auto", overflowX: "hidden", p: 6, rounded: CORNER, borderWidth: 1, borderColor: "$borderColor", bg: "$background", "$max-sm": { rounded: CORNER_PHONE }, children: [_jsx(SizableText, { fontSize: 13, color: "$hint", px: 8, pt: 4, pb: 6, children: shelf.search ?? `Type to search ${name.toLowerCase()}` }), rows.length ? (rows.map((r, i) => (_jsxs(XStack, { render: "button", role: "option", "aria-selected": i === index, onPress: () => onPick(r.id), items: "center", gap: 10, px: 8, height: 36, rounded: 8, bg: i === index ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, cursor: "pointer", "$max-sm": { height: 46 }, children: [r.icon ? (_jsx(XStack, { width: 16, items: "center", justify: "center", "aria-hidden": true, children: r.icon })) : null, _jsx(SizableText, { fontSize: 15, color: "$ink", numberOfLines: 1, flex: 1, style: { textAlign: 'left' }, children: r.label }), _jsx(SizableText, { fontSize: 13, color: "$hint", children: r.on ? 'On' : tag })] }, r.id)))) : (_jsx(SizableText, { fontSize: 14, color: "$hint", px: 8, py: 8, children: shelf.loading ? 'Reading…' : 'Nothing matches' }))] }));
139
+ /** The list `/` (the skills) or `@` (the plugins) opens above the frame. */
140
+ function Mention({ shelf, rows, index, onPick, skills }) {
141
+ const { t } = useI18n();
142
+ const tag = t(skills ? 'Skill' : 'Plugin');
143
+ return (_jsxs(YStack, { role: "listbox", "aria-label": t(skills ? 'Skills' : 'Plugins'), "data-slot": "mention", width: "100%", maxH: 240, overflowY: "auto", overflowX: "hidden", p: 6, rounded: CORNER, borderWidth: 1, borderColor: "$borderColor", bg: "$background", "$max-sm": { rounded: CORNER_PHONE }, children: [_jsx(SizableText, { fontSize: 13, color: "$hint", px: 8, pt: 4, pb: 6, children: shelf.search ?? t(skills ? 'Type to search skills' : 'Type to search plugins') }), rows.length ? (rows.map((r, i) => (_jsxs(XStack, { render: "button", role: "option", "aria-selected": i === index, onPress: () => onPick(r.id), items: "center", gap: 10, px: 8, height: 36, rounded: 8, bg: i === index ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, cursor: "pointer", "$max-sm": { height: 46 }, children: [r.icon ? (_jsx(XStack, { width: 16, items: "center", justify: "center", "aria-hidden": true, children: r.icon })) : null, _jsx(SizableText, { fontSize: 15, color: "$ink", numberOfLines: 1, flex: 1, text: "start", children: r.label }), _jsx(SizableText, { fontSize: 13, color: "$hint", children: r.on ? t('On') : tag })] }, r.id)))) : (_jsx(SizableText, { fontSize: 14, color: "$hint", px: 8, py: 8, children: t(shelf.loading ? 'Reading…' : 'Nothing matches') }))] }));
128
144
  }
129
145
  /** @hanzo/ui `Composer` props for the one composer. */
130
146
  export function prompt(o, state) {
147
+ const t = state?.t ?? english;
131
148
  const lit = o.ready ?? Boolean(o.value.trim());
132
149
  const busy = Boolean(o.busy);
133
150
  const disabled = Boolean(o.disabled);
@@ -144,10 +161,10 @@ export function prompt(o, state) {
144
161
  o.onChange(o.value.slice(0, typed.at).trimEnd() + (typed.at > 0 ? ' ' : ''));
145
162
  shelf.pick(id);
146
163
  };
147
- const mention = listed && shelf && typed ? (_jsx(Mention, { shelf: shelf, rows: listed, index: index, onPick: choose, tag: typed.char === '/' ? 'Skill' : 'Plugin', name: typed.char === '/' ? 'Skills' : 'Plugins' })) : null;
164
+ const mention = listed && shelf && typed ? (_jsx(Mention, { shelf: shelf, rows: listed, index: index, onPick: choose, skills: typed.char === '/' })) : null;
148
165
  const head = o.head || o.note || mention || (narrow && o.below) ? (_jsxs(YStack, { ...measure, gap: "$1.5", "data-slot": "prompt-head", children: [narrow ? o.below : null, o.head, mention, o.note ? (_jsx(SizableText, { "data-slot": "prompt-note", fontSize: 12, color: "$soft", text: "center", self: "center", children: o.note })) : null] })) : undefined;
149
166
  const foot = o.mind || o.mode || o.where || (!narrow && o.below) ? (_jsxs(YStack, { ...measure, gap: 10, "data-slot": "prompt-foot", children: [_jsx(Tune, { mind: o.mind, mode: o.mode, where: o.where, onUpgrade: o.onUpgrade, limits: o.limits }), narrow ? null : o.below] })) : undefined;
150
- const round = (_jsx(Control, { type: "button", size: narrow ? TAP : ROUND, fill: true, rounded: 999, "data-slot": "composer-send", "aria-label": busy ? 'Stop' : 'Send', "aria-disabled": !live || undefined, opacity: live ? 1 : 0.4, onClick: () => {
167
+ const round = (_jsx(Control, { type: "button", size: narrow ? TAP : ROUND, fill: true, rounded: 999, "data-slot": "composer-send", "aria-label": t(busy ? 'Stop' : 'Send'), "aria-disabled": !live || undefined, opacity: live ? 1 : 0.4, onClick: () => {
151
168
  if (!live)
152
169
  return;
153
170
  if (busy)
@@ -166,7 +183,7 @@ export function prompt(o, state) {
166
183
  ...(o.onStop ? { onStop: o.onStop } : {}),
167
184
  busy,
168
185
  disabled,
169
- placeholder: o.placeholder ?? ASK,
186
+ placeholder: o.placeholder ?? t(ASK),
170
187
  ...(o.label ? { label: o.label } : {}),
171
188
  // At rest two rows: one line of text, then the tools. It grows with the text to the kit's ceiling, then scrolls.
172
189
  rows: 1,
@@ -191,8 +208,8 @@ export function prompt(o, state) {
191
208
  bg: '$edge',
192
209
  '$max-sm': { rounded: CORNER_PHONE },
193
210
  ...(o.framed ? { boxShadow: 'var(--shadow-sheet-1)' } : null),
194
- children: (_jsxs(XStack, { items: "center", gap: 4, "data-slot": "prompt-start", flex: 1, minW: 0, flexWrap: "wrap", children: [o.plus ? _jsx(Plus, { ...o.plus, disabled: disabled || o.plus.disabled }) : null, (o.on ?? []).map((c) => (_jsxs(XStack, { "data-slot": "prompt-chip", items: "center", gap: 6, pl: 8, pr: 4, height: 28, rounded: 8, bg: "$hover", maxW: 180, children: [c.icon ? _jsx(XStack, { "aria-hidden": true, children: c.icon }) : null, _jsx(SizableText, { fontSize: 13, color: "$ink", numberOfLines: 1, shrink: 1, children: c.label }), _jsx(XStack, { render: "button", "aria-label": `Remove ${c.label}`, onPress: c.onRemove, width: 20, height: 20, rounded: 6, items: "center", justify: "center", hoverStyle: { bg: '$raised' }, cursor: "pointer", children: _jsx(X, { size: 12, color: "$hint" }) })] }, c.id)))] })),
195
- send: (_jsxs(XStack, { items: "center", gap: 6, "data-slot": "prompt-end", shrink: 0, children: [o.voice ? _jsx(Dictation, { voice: o.voice, disabled: disabled, narrow: narrow, language: o.language ?? null, ...(o.onLanguage ? { onLanguage: o.onLanguage } : {}) }) : null, !busy && !lit && o.talk ? (_jsx(Control, { asChild: true, size: narrow ? TAP : ROUND, fill: narrow, rounded: 999, ...(narrow ? null : QUIET), children: _jsx(Voice, { voice: o.talk, disabled: disabled, "data-slot": "composer-talk", says: { idle: 'Voice mode', listening: 'In conversation — click to hang up', speaking: 'Hanzo is speaking — talk to interrupt' }, children: () => _jsx(AudioLines, { size: 16, color: narrow ? ON : 'var(--foreground)' }) }) })) : (round)] })),
211
+ children: (_jsxs(XStack, { items: "center", gap: 4, "data-slot": "prompt-start", flex: 1, minW: 0, flexWrap: "wrap", children: [o.plus ? _jsx(Plus, { ...o.plus, disabled: disabled || o.plus.disabled }) : null, (o.on ?? []).map((c) => (_jsxs(XStack, { "data-slot": "prompt-chip", items: "center", gap: 6, paddingInlineStart: 8, paddingInlineEnd: 4, height: 28, rounded: 8, bg: "$hover", maxW: 180, children: [c.icon ? _jsx(XStack, { "aria-hidden": true, children: c.icon }) : null, _jsx(SizableText, { fontSize: 13, color: "$ink", numberOfLines: 1, shrink: 1, children: c.label }), _jsx(XStack, { render: "button", "aria-label": t('Remove {name}', { name: c.label }), onPress: c.onRemove, width: 20, height: 20, rounded: 6, items: "center", justify: "center", hoverStyle: { bg: '$raised' }, cursor: "pointer", children: _jsx(X, { size: 12, color: "$hint" }) })] }, c.id)))] })),
212
+ send: (_jsxs(XStack, { items: "center", gap: 6, "data-slot": "prompt-end", shrink: 0, children: [o.voice ? _jsx(Dictation, { voice: o.voice, disabled: disabled, narrow: narrow, language: o.language ?? null, ...(o.onLanguage ? { onLanguage: o.onLanguage } : {}) }) : null, !busy && !lit && o.talk ? (_jsx(Control, { asChild: true, size: narrow ? TAP : ROUND, fill: narrow, rounded: 999, ...(narrow ? null : QUIET), children: _jsx(Voice, { voice: o.talk, disabled: disabled, "data-slot": "composer-talk", says: { idle: t('Voice mode'), listening: t('In conversation — click to hang up'), speaking: t('Hanzo is speaking — talk to interrupt') }, children: () => _jsx(AudioLines, { size: 16, color: narrow ? ON : 'var(--foreground)' }) }) })) : (round)] })),
196
213
  };
197
214
  }
198
215
  /** Ctrl+U (⌘U) uploads, while a composer with an upload is on the page. */
@@ -213,11 +230,12 @@ export function useUpload(upload) {
213
230
  }, [Boolean(upload)]); // eslint-disable-line react-hooks/exhaustive-deps
214
231
  }
215
232
  /**
216
- * The composer's own state — which row of a `/` or `@` list is lit — and the
217
- * keys that move it. A surface that hands `prompt` to `Chat` calls this and
218
- * passes its answer through; `<Prompt>` does it for itself.
233
+ * The composer's own state — which row of a `/` or `@` list is lit, and the
234
+ * reader's `t` — and the keys that move it. A surface that hands `prompt` to
235
+ * `Chat` calls this and passes its answer through; `<Prompt>` does it for itself.
219
236
  */
220
237
  export function useMention(o) {
238
+ const { t } = useI18n();
221
239
  const [index, setIndex] = useState(0);
222
240
  const [closed, setClosed] = useState(null);
223
241
  useUpload(o.plus?.upload);
@@ -245,7 +263,7 @@ export function useMention(o) {
245
263
  o.field?.onKeyDown?.(e);
246
264
  },
247
265
  };
248
- return { state: { index, closed }, field };
266
+ return { state: { index, closed, t }, field };
249
267
  }
250
268
  /** The composer drawn on its own. */
251
269
  export function Prompt(o) {
package/lib/publish.js CHANGED
@@ -16,9 +16,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  * project's address again.
17
17
  *
18
18
  * Every state shown is one the platform reported; a refusal shows the
19
- * platform's own sentence.
19
+ * platform's own sentence. The dialog's own words are the reader's language
20
+ * (@hanzo/i18n `t`).
20
21
  */
21
22
  import { SizableText, XStack, YStack } from '@hanzo/gui';
23
+ import { useI18n } from '@hanzo/i18n/react';
22
24
  import { ExternalLink } from '@hanzogui/lucide-icons-2';
23
25
  import { Button, Dialog, DialogContent, DialogDescription, DialogTitle, Input } from '@hanzo/ui';
24
26
  import { useEffect, useRef, useState } from 'react';
@@ -26,8 +28,9 @@ import { builds, declare, label } from './api/platform.js';
26
28
  import { useHost, useTarget } from './host.js';
27
29
  import { Out } from './out.js';
28
30
  export function Publish({ source, onClose, onLive }) {
29
- const t = useTarget();
31
+ const target = useTarget();
30
32
  const host = useHost();
33
+ const { t } = useI18n();
31
34
  const [project, setProject] = useState('');
32
35
  const [phase, setPhase] = useState('ask');
33
36
  const [out, setOut] = useState(null);
@@ -50,7 +53,7 @@ export function Publish({ source, onClose, onLive }) {
50
53
  let live = true;
51
54
  const tick = async () => {
52
55
  try {
53
- const row = (await builds(t)).find((b) => b.id === id);
56
+ const row = (await builds(target)).find((b) => b.id === id);
54
57
  if (live && row)
55
58
  setStatus(row.status);
56
59
  }
@@ -64,7 +67,7 @@ export function Publish({ source, onClose, onLive }) {
64
67
  live = false;
65
68
  clearInterval(every);
66
69
  };
67
- }, [t, out, phase]);
70
+ }, [target, out, phase]);
68
71
  const green = /^(succeeded|success|done|complete|completed|ok)$/i.test(status);
69
72
  // A site's build going green is the site going live, at the project's address.
70
73
  useEffect(() => {
@@ -77,7 +80,7 @@ export function Publish({ source, onClose, onLive }) {
77
80
  setPhase('sending');
78
81
  setError('');
79
82
  try {
80
- const d = await declare(t, { repo: source.repo, ref: source.ref, name: source.name, project: project || source.name, mode: 'branch' });
83
+ const d = await declare(target, { repo: source.repo, ref: source.ref, name: source.name, project: project || source.name, mode: 'branch' });
81
84
  setOut(d);
82
85
  setStatus(d.build?.status ?? '');
83
86
  setPhase('declared');
@@ -94,7 +97,7 @@ export function Publish({ source, onClose, onLive }) {
94
97
  setPhase('shipping');
95
98
  setError('');
96
99
  try {
97
- await declare(t, { repo: source.repo, ref: source.ref, name: source.name, project: project || source.name, mode: 'commit', tag: built });
100
+ await declare(target, { repo: source.repo, ref: source.ref, name: source.name, project: project || source.name, mode: 'commit', tag: built });
98
101
  setPhase('shipped');
99
102
  }
100
103
  catch (e) {
@@ -102,11 +105,13 @@ export function Publish({ source, onClose, onLive }) {
102
105
  setPhase('declared');
103
106
  }
104
107
  };
105
- return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && onClose(), children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: "Add to project" }), _jsx(DialogDescription, { children: out?.live
106
- ? `Build ${source.title} at ${source.ref} and publish it at its project’s address.`
107
- : `Build ${source.title} at ${source.ref} and declare it in a project. ${host.admin ? 'As an admin you can take a green build to main.' : 'It opens a review; merging it is what deploys.'}` }), _jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { render: "label", size: "$2", color: "$soft", htmlFor: "publish-project", children: "Project" }), _jsx(Input, { id: "publish-project", value: project, onChangeText: (v) => setProject(v), disabled: phase !== 'ask' && phase !== 'failed', placeholder: label(source.name), "aria-label": "Project" }), _jsxs(SizableText, { size: "$1", color: "$soft", children: ["Saved as ", label(project || source.name)] })] }), out ? (_jsxs(YStack, { gap: "$2", role: "status", children: [out.notice ? (_jsx(SizableText, { size: "$2", color: "$soft", children: out.notice })) : null, _jsx(SizableText, { size: "$2", color: "$ink", children: phase === 'shipped'
108
- ? 'Declared on main — CD applies it on its next pass.'
108
+ return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && onClose(), children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: t('Add to project') }), _jsx(DialogDescription, { children: out?.live
109
+ ? t('Build {repo} at {ref} and publish it at its project’s address.', { repo: source.title, ref: source.ref })
110
+ : `${t('Build {repo} at {ref} and declare it in a project.', { repo: source.title, ref: source.ref })} ${host.admin ? t('As an admin you can take a green build to main.') : t('It opens a review; merging it is what deploys.')}` }), _jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { render: "label", size: "$2", color: "$soft", htmlFor: "publish-project", children: t('Project') }), _jsx(Input, { id: "publish-project", value: project, onChangeText: (v) => setProject(v), disabled: phase !== 'ask' && phase !== 'failed', placeholder: label(source.name), "aria-label": t('Project') }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Saved as {name}', { name: label(project || source.name) }) })] }), out ? (_jsxs(YStack, { gap: "$2", role: "status", children: [out.notice ? (_jsx(SizableText, { size: "$2", color: "$soft", children: out.notice })) : null, _jsx(SizableText, { size: "$2", color: "$ink", children: phase === 'shipped'
111
+ ? t('Declared on main — CD applies it on its next pass.')
109
112
  : out.live && green
110
- ? `Build ${built} succeeded — it is live at the project’s address.`
111
- : `Build ${built}: ${status || 'building'}` }), out.review ? (_jsxs(Out, { href: out.review, children: [_jsx(SizableText, { size: "$2", color: "$ink", textDecorationLine: "underline", children: "Open the review" }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] })) : null] })) : null, error ? (_jsx(SizableText, { size: "$2", color: "$red10", role: "alert", children: error })) : null, _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { variant: "outline", size: "sm", onPress: onClose, children: phase === 'shipped' || phase === 'declared' ? 'Done' : 'Cancel' }), phase === 'ask' || phase === 'failed' || phase === 'sending' ? (_jsx(Button, { size: "sm", disabled: phase === 'sending', onPress: () => void send(), children: phase === 'sending' ? 'Adding…' : 'Add to project' })) : host.admin && !out?.live && (phase === 'declared' || phase === 'shipping') ? (_jsx(Button, { size: "sm", disabled: !green || phase === 'shipping', onPress: () => void ship(), children: phase === 'shipping' ? 'Shipping…' : green ? 'Ship to main' : 'Waiting for the build' })) : null] })] }) }));
113
+ ? t('Build {id} succeeded — it is live at the project’s address.', { id: built })
114
+ : status
115
+ ? t('Build {id}: {status}', { id: built, status })
116
+ : t('Build {id}: building', { id: built }) }), out.review ? (_jsxs(Out, { href: out.review, children: [_jsx(SizableText, { size: "$2", color: "$ink", textDecorationLine: "underline", children: t('Open the review') }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] })) : null] })) : null, error ? (_jsx(SizableText, { size: "$2", color: "$red10", role: "alert", children: error })) : null, _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { variant: "outline", size: "sm", onPress: onClose, children: phase === 'shipped' || phase === 'declared' ? t('Done') : t('Cancel') }), phase === 'ask' || phase === 'failed' || phase === 'sending' ? (_jsx(Button, { size: "sm", disabled: phase === 'sending', onPress: () => void send(), children: phase === 'sending' ? t('Adding…') : t('Add to project') })) : host.admin && !out?.live && (phase === 'declared' || phase === 'shipping') ? (_jsx(Button, { size: "sm", disabled: !green || phase === 'shipping', onPress: () => void ship(), children: phase === 'shipping' ? t('Shipping…') : green ? t('Ship to main') : t('Waiting for the build') })) : null] })] }) }));
112
117
  }
package/lib/reply.js CHANGED
@@ -9,6 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  * from its own bytes (`blob`).
10
10
  */
11
11
  import { SizableText, XStack } from '@hanzo/gui';
12
+ import { useI18n } from '@hanzo/i18n/react';
12
13
  import { Check, Copy, PanelRight, Square, Volume2 } from '@hanzogui/lucide-icons-2';
13
14
  import { mouth, speech } from '@hanzo/voice';
14
15
  import { useEffect, useRef, useState } from 'react';
@@ -97,6 +98,7 @@ function Act({ label, onPress, children, lit = false }) {
97
98
  }
98
99
  /** Copy, Listen and Open under an answer; `children` follow them. */
99
100
  export function Reply({ text, listen, onOpen, children }) {
101
+ const { t: say } = useI18n();
100
102
  const [took, setTook] = useState(false);
101
103
  const [speaking, setSpeaking] = useState(false);
102
104
  const stop = useRef(null);
@@ -107,7 +109,7 @@ export function Reply({ text, listen, onOpen, children }) {
107
109
  return () => clearTimeout(t);
108
110
  }, [took]);
109
111
  useEffect(() => () => stop.current?.(), []);
110
- return (_jsxs(XStack, { "data-slot": "reply", ml: "$-1", items: "center", gap: "$1.5", flexWrap: "wrap", children: [_jsx(Act, { label: took ? 'Copied' : 'Copy this answer', lit: took, onPress: () => void navigator.clipboard?.writeText(text).then(() => setTook(true), () => { }), children: took ? _jsx(Check, { size: 13 }) : _jsx(Copy, { size: 13 }) }), listen ? (_jsxs(Act, { label: speaking ? 'Stop reading' : 'Listen to this answer', lit: speaking, onPress: () => {
112
+ return (_jsxs(XStack, { "data-slot": "reply", ml: "$-1", items: "center", gap: "$1.5", flexWrap: "wrap", children: [_jsx(Act, { label: took ? say('Copied') : say('Copy this answer'), lit: took, onPress: () => void navigator.clipboard?.writeText(text).then(() => setTook(true), () => { }), children: took ? _jsx(Check, { size: 13 }) : _jsx(Copy, { size: 13 }) }), listen ? (_jsxs(Act, { label: speaking ? say('Stop reading') : say('Listen to this answer'), lit: speaking, onPress: () => {
111
113
  if (speaking) {
112
114
  stop.current?.();
113
115
  setSpeaking(false);
@@ -115,9 +117,10 @@ export function Reply({ text, listen, onOpen, children }) {
115
117
  }
116
118
  setSpeaking(true);
117
119
  stop.current = listen(text, () => setSpeaking(false));
118
- }, children: [speaking ? _jsx(Square, { size: 12 }) : _jsx(Volume2, { size: 13 }), _jsx(SizableText, { size: "$1", color: "$ink", children: speaking ? 'Stop' : 'Listen' })] })) : null, onOpen ? (_jsxs(Act, { label: "Open in the side panel", onPress: onOpen, children: [_jsx(PanelRight, { size: 13 }), _jsx(SizableText, { size: "$1", color: "$ink", children: "Open" })] })) : null, children] }));
120
+ }, children: [speaking ? _jsx(Square, { size: 12 }) : _jsx(Volume2, { size: 13 }), _jsx(SizableText, { size: "$1", color: "$ink", children: speaking ? say('Stop') : say('Listen') })] })) : null, onOpen ? (_jsxs(Act, { label: say('Open in the side panel'), onPress: onOpen, children: [_jsx(PanelRight, { size: 13 }), _jsx(SizableText, { size: "$1", color: "$ink", children: say('Open') })] })) : null, children] }));
119
121
  }
120
122
  /** Which model answered, where the gateway says it was not the one asked for or a router. */
121
123
  export function Served({ served, asked }) {
122
- return (_jsx(SizableText, { size: "$1", color: "$soft", "data-slot": "served", children: `answered by ${served}${asked && asked !== served ? ` · ${asked} was unavailable` : ''}` }));
124
+ const { t } = useI18n();
125
+ return (_jsx(SizableText, { size: "$1", color: "$soft", "data-slot": "served", children: asked && asked !== served ? t('answered by {model} · {asked} was unavailable', { model: served, asked }) : t('answered by {model}', { model: served }) }));
123
126
  }