@hanzo/build 0.2.62 → 0.2.63

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.
package/lib/mic.d.ts CHANGED
@@ -2,9 +2,11 @@ import { type Machine } from '@hanzo/voice';
2
2
  export interface DictationProps {
3
3
  voice: Machine;
4
4
  disabled?: boolean;
5
+ /** A phone: both halves take a finger's width. */
6
+ narrow?: boolean;
5
7
  /** The language dictation listens in, or null for Auto (the browser's own). */
6
8
  language?: string | null;
7
9
  onLanguage?: (id: string | null) => void;
8
10
  }
9
11
  /** The mic and its chevron, one control on the composer's row. */
10
- export declare function Dictation({ voice, disabled, language, onLanguage }: DictationProps): import("react").JSX.Element;
12
+ export declare function Dictation({ voice, disabled, narrow, language, onLanguage }: DictationProps): import("react").JSX.Element;
package/lib/mic.js CHANGED
@@ -4,18 +4,43 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
4
4
  * microphones this browser has, the chosen one's live level, and the language
5
5
  * dictation listens in (Auto is the browser's own).
6
6
  *
7
- * The microphones are listed as the browser names them, the system's own
8
- * input first; dictation opens that one. The meter listens only while the menu
9
- * is open, and only where the person has already let the page use a
10
- * microphone: opening a menu never asks for permission.
7
+ * The chosen microphone is this browser's (a device id means nothing on
8
+ * another), kept in localStorage and handed to @hanzo/voice (`choose`), so
9
+ * every capture opens it; unchosen, the system's own input. The meter listens
10
+ * only while the menu is open, and only where the person has already let the
11
+ * page use a microphone: opening a menu never asks for permission.
11
12
  */
12
13
  import { SizableText, XStack, YStack } from '@hanzo/gui';
13
14
  import { Check, ChevronDown, Mic as MicIcon } from '@hanzogui/lucide-icons-2';
14
15
  import { Control } from '@hanzo/composer';
15
16
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@hanzo/ui';
16
- import { Voice } from '@hanzo/voice';
17
+ import { choose, Voice } from '@hanzo/voice';
17
18
  import { useEffect, useRef, useState } from 'react';
18
19
  import { LANGUAGES } from './voice.js';
20
+ const KEY = 'hanzo.mic';
21
+ function kept() {
22
+ try {
23
+ return window.localStorage.getItem(KEY);
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function keep(id) {
30
+ try {
31
+ if (id)
32
+ window.localStorage.setItem(KEY, id);
33
+ else
34
+ window.localStorage.removeItem(KEY);
35
+ }
36
+ catch {
37
+ // Kept for the page: `choose` holds it.
38
+ }
39
+ choose(id);
40
+ }
41
+ // The page's choice reaches every capture from the first one.
42
+ if (typeof window !== 'undefined')
43
+ choose(kept());
19
44
  /** The mic is a plain mark until it is pointed at. */
20
45
  const PLAIN = { bg: 'transparent', color: 'var(--foreground)', hoverStyle: { bg: '$hover' } };
21
46
  /** The microphones this browser lists; labels only once the page may use one. */
@@ -42,8 +67,8 @@ function useInputs(open) {
42
67
  }, [open]);
43
68
  return { inputs, allowed, using };
44
69
  }
45
- /** The microphone's level while the menu is open, drawn without re-rendering. */
46
- function Meter({ on }) {
70
+ /** The chosen microphone's level while the menu is open, drawn without re-rendering. */
71
+ function Meter({ device, on }) {
47
72
  const bar = useRef(null);
48
73
  useEffect(() => {
49
74
  if (!on)
@@ -53,7 +78,7 @@ function Meter({ on }) {
53
78
  let frame = 0;
54
79
  let live = true;
55
80
  void navigator.mediaDevices
56
- .getUserMedia({ audio: true })
81
+ .getUserMedia({ audio: device ? { deviceId: { exact: device } } : true })
57
82
  .then((s) => {
58
83
  if (!live)
59
84
  return s.getTracks().forEach((t) => t.stop());
@@ -81,15 +106,22 @@ function Meter({ on }) {
81
106
  stream?.getTracks().forEach((t) => t.stop());
82
107
  void ctx?.close();
83
108
  };
84
- }, [on]);
109
+ }, [device, on]);
85
110
  return (_jsxs(XStack, { items: "center", gap: 10, px: 8, py: 8, "data-slot": "mic-meter", "aria-hidden": true, children: [_jsx(MicIcon, { size: 14, color: "$hint" }), _jsx(YStack, { flex: 1, height: 4, rounded: 2, bg: "$hover", overflow: "hidden", children: _jsx(YStack, { ref: bar, height: 4, width: "100%", bg: "$ink",
86
111
  // inline-style: the level is written per frame onto the node; a style prop would mint a class per value.
87
112
  style: { transform: 'scaleX(0)', transformOrigin: 'left' } }) })] }));
88
113
  }
89
114
  /** The mic and its chevron, one control on the composer's row. */
90
- export function Dictation({ voice, disabled = false, language = null, onLanguage }) {
115
+ export function Dictation({ voice, disabled = false, narrow = false, language = null, onLanguage }) {
91
116
  const [open, setOpen] = useState(false);
92
117
  const { inputs, allowed, using } = useInputs(open);
118
+ const [device, setDevice] = useState(() => (typeof window === 'undefined' ? null : kept()));
93
119
  const spoken = LANGUAGES.find((l) => l.id === language);
94
- return (_jsxs(XStack, { "data-slot": "dictation", items: "center", rounded: 8, borderWidth: 1, borderColor: open ? '$borderColor' : 'transparent', hoverStyle: { borderColor: '$borderColor' }, shrink: 0, children: [_jsx(Control, { asChild: true, size: 30, ...PLAIN, children: _jsx(Voice, { voice: voice, disabled: disabled, says: { idle: 'Dictate', listening: 'Dictating — click to stop' }, children: (state) => _jsx(MicIcon, { size: 16, color: "var(--foreground)", fill: state === 'idle' ? 'none' : 'var(--foreground)' }) }) }), _jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, disabled: disabled, children: _jsx(XStack, { render: "button", "aria-label": "Dictation settings", "data-slot": "dictation-menu", width: 16, height: 30, items: "center", justify: "center", rounded: 6, cursor: "pointer", bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, "$max-sm": { width: 28, height: 44 }, children: _jsx(ChevronDown, { size: 14, color: "$aside" }) }) }), _jsxs(DropdownMenuContent, { align: "end", minW: 200, "data-slot": "dictation-panel", children: [_jsx(Meter, { on: open && allowed }), inputs.length === 0 ? (_jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 14, color: "$hint", children: "No devices found" }) })) : (inputs.map((d, i) => (_jsx(DropdownMenuLabel, { children: _jsxs(XStack, { items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", numberOfLines: 1, flex: 1, children: d.label || `Microphone ${i + 1}` }), d.groupId === using ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-label": "In use" }) : null] }) }, d.deviceId || i)))), onLanguage ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 13, color: "$hint", children: "Dictation settings" }) }), _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { "data-slot": "dictation-language", children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: "Language" }), _jsx(SizableText, { fontSize: 14, color: "$hint", children: spoken?.label ?? 'Auto' })] }) }), _jsx(DropdownMenuSubContent, { minW: 160, children: [{ id: '', label: 'Auto' }, ...LANGUAGES].map((l) => (_jsx(DropdownMenuItem, { onSelect: () => onLanguage(l.id || null), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: l.label }), (language ?? '') === l.id ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, l.id || 'auto'))) })] })] })) : null] })] })] }));
120
+ return (_jsxs(XStack, { "data-slot": "dictation", items: "center", rounded: 8, borderWidth: 1, borderColor: open ? '$borderColor' : 'transparent', hoverStyle: { borderColor: '$borderColor' }, shrink: 0, children: [_jsx(Control, { asChild: true, size: narrow ? 44 : 30, ...PLAIN, children: _jsx(Voice, { voice: voice, disabled: disabled, says: { idle: 'Dictate', listening: 'Dictating — click to stop' }, children: (state) => _jsx(MicIcon, { size: 16, color: "var(--foreground)", fill: state === 'idle' ? 'none' : 'var(--foreground)' }) }) }), _jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, disabled: disabled, children: _jsx(XStack, { render: "button", "aria-label": "Dictation settings", "data-slot": "dictation-menu", width: 16, height: 30, items: "center", justify: "center", rounded: 6, cursor: "pointer", bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, ...(narrow ? { width: 44, height: 44 } : null), children: _jsx(ChevronDown, { size: 14, color: "$aside" }) }) }), _jsxs(DropdownMenuContent, { align: "end", minW: 200, "data-slot": "dictation-panel", children: [_jsx(Meter, { device: device, on: open && allowed }), inputs.length === 0 ? (_jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 14, color: "$hint", children: "No devices found" }) })) : (inputs.map((d, i) => {
121
+ const on = device ? d.deviceId === device : d.groupId === using;
122
+ return (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => {
123
+ setDevice(d.deviceId);
124
+ keep(d.deviceId);
125
+ }, children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", numberOfLines: 1, flex: 1, children: d.label || `Microphone ${i + 1}` }), on ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, d.deviceId || i));
126
+ })), onLanguage ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 13, color: "$hint", children: "Dictation settings" }) }), _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { "data-slot": "dictation-language", children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: "Language" }), _jsx(SizableText, { fontSize: 14, color: "$hint", children: spoken?.label ?? 'Auto' })] }) }), _jsx(DropdownMenuSubContent, { minW: 160, children: [{ id: '', label: 'Auto' }, ...LANGUAGES].map((l) => (_jsx(DropdownMenuItem, { onSelect: () => onLanguage(l.id || null), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: l.label }), (language ?? '') === l.id ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, l.id || 'auto'))) })] })] })) : null] })] })] }));
95
127
  }
package/lib/prompt.d.ts CHANGED
@@ -16,7 +16,7 @@ export declare const measure: {
16
16
  readonly maxW: "calc(var(--container-prose, 48rem) - 1.5rem)";
17
17
  readonly mx: 'auto';
18
18
  };
19
- /** The round controls' box, px. */
19
+ /** The round controls' box, px, and a phone's, a finger's width. */
20
20
  export declare const ROUND = 32;
21
21
  /** What every Composer placeholder says, unless a surface names who is listening. */
22
22
  export declare const ASK = "Type / for skills";
package/lib/prompt.js CHANGED
@@ -37,8 +37,9 @@ export const MEASURE = 'var(--container-prose, 48rem)';
37
37
  export const GAP = 20;
38
38
  /** The composer's width: the measure less the thread column's 0.75rem gutters, centred, so its edges are the text's. */
39
39
  export const measure = { width: 'calc(100% - 1.5rem)', maxW: `calc(${MEASURE} - 1.5rem)`, mx: 'auto' };
40
- /** The round controls' box, px. */
40
+ /** The round controls' box, px, and a phone's, a finger's width. */
41
41
  export const ROUND = 32;
42
+ const TAP = 44;
42
43
  /** The frame's corner, px: 12 on a laptop, 22 on a phone. */
43
44
  const CORNER = 12;
44
45
  const CORNER_PHONE = 22;
@@ -46,6 +47,8 @@ const CORNER_PHONE = 22;
46
47
  const ON = 'var(--primary-foreground)';
47
48
  /** A round control with no ground of its own until it is pointed at. */
48
49
  const QUIET = { bg: 'transparent', color: 'var(--foreground)', hoverStyle: { bg: '$hover' } };
50
+ /** The model rows the menu draws before anything is typed. */
51
+ const SHOWN = 40;
49
52
  /** What every Composer placeholder says, unless a surface names who is listening. */
50
53
  export const ASK = 'Type / for skills';
51
54
  /**
@@ -81,15 +84,27 @@ function Opens({ label, word, dim, children }) {
81
84
  function Model({ mind, onUpgrade }) {
82
85
  const [open, setOpen] = useState(false);
83
86
  const [q, setQ] = useState('');
84
- const groups = useMemo(() => {
87
+ // The catalog is hundreds long, and drawing all of it is what made the menu slow
88
+ // to open: until something is typed, the first `SHOWN` rows, Hanzo's families
89
+ // first, and a line saying the search reaches the rest.
90
+ const { groups, more } = useMemo(() => {
85
91
  const list = mind.models.filter((m) => !ROUTER_IDS.has(m.id) && (m.outputs?.includes('text') ?? true) && (!q.trim() || matchesModel(m, q)));
86
- return groupModels(list);
92
+ const all = groupModels(list);
93
+ let room = q.trim() ? Infinity : SHOWN;
94
+ const cut = all
95
+ .map((g) => {
96
+ const models = g.models.slice(0, Math.max(0, room));
97
+ room -= models.length;
98
+ return { ...g, models };
99
+ })
100
+ .filter((g) => g.models.length);
101
+ return { groups: cut, more: list.length - cut.reduce((n, g) => n + g.models.length, 0) };
87
102
  }, [mind.models, q]);
88
103
  const routed = mind.model === ENSO;
89
- 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, maxH: 420, "data-slot": "model-menu", children: [_jsx(DropdownMenuSearch, { value: q, onValueChange: setQ, placeholder: "Search models" }), !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) => {
90
- const ok = grants(mind.free, m.id, mind.models);
91
- return (_jsx(DropdownMenuItem, { "data-slot": "model-row", 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" })), m.id === mind.model ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, m.id));
92
- })] }, g.key)))] })] }));
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) => {
105
+ const ok = grants(mind.free, m.id, mind.models);
106
+ return (_jsx(DropdownMenuItem, { "data-slot": "model-row", 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" })), m.id === mind.model ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, m.id));
107
+ })] }, g.key))), more > 0 ? (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 8, py: 8, children: `Type to search ${more} more models` })) : null] })] })] }));
93
108
  }
94
109
  /** The row under the frame: where (Dev), then the model, the effort and the mode. */
95
110
  export function Tune({ mind, mode, where, onUpgrade }) {
@@ -130,7 +145,7 @@ export function prompt(o, state) {
130
145
  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;
131
146
  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;
132
147
  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 }), narrow ? null : o.below] })) : undefined;
133
- const round = (_jsx(Control, { type: "button", size: ROUND, fill: true, rounded: 999, "data-slot": "composer-send", "aria-label": busy ? 'Stop' : 'Send', "aria-disabled": !live || undefined, opacity: live ? 1 : 0.4, onClick: () => {
148
+ 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: () => {
134
149
  if (!live)
135
150
  return;
136
151
  if (busy)
@@ -173,7 +188,7 @@ export function prompt(o, state) {
173
188
  '$max-sm': { rounded: CORNER_PHONE },
174
189
  ...(o.framed ? { boxShadow: 'var(--shadow-sheet-1)' } : null),
175
190
  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)))] })),
176
- send: (_jsxs(XStack, { items: "center", gap: 6, "data-slot": "prompt-end", shrink: 0, children: [o.voice ? _jsx(Dictation, { voice: o.voice, disabled: disabled, language: o.language ?? null, ...(o.onLanguage ? { onLanguage: o.onLanguage } : {}) }) : null, !busy && !lit && o.talk ? (_jsx(Control, { asChild: true, size: 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)] })),
191
+ 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)] })),
177
192
  };
178
193
  }
179
194
  /** Ctrl+U (⌘U) uploads, while a composer with an upload is on the page. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.62",
3
+ "version": "0.2.63",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",
@@ -14,7 +14,7 @@
14
14
  "@hanzo/iam": "0.21.28",
15
15
  "@hanzo/ui": "8.29.0",
16
16
  "@hanzo/vite": "0.1.4",
17
- "@hanzo/voice": "0.1.18",
17
+ "@hanzo/voice": "0.1.19",
18
18
  "@hanzogui/lucide-icons-2": "8.3.5",
19
19
  "@hanzogui/next-theme": "8.3.5",
20
20
  "@playwright/test": "1.63.0",
@@ -75,7 +75,7 @@
75
75
  "@hanzo/ui": ">=8.29.0",
76
76
  "@hanzogui/lucide-icons-2": ">=8.3.5",
77
77
  "react": ">=19.2.8",
78
- "@hanzo/voice": ">=0.1.16",
78
+ "@hanzo/voice": ">=0.1.19",
79
79
  "@hanzo/composer": ">=0.2.0"
80
80
  },
81
81
  "scripts": {