@hanzo/build 0.2.61 → 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/gear.d.ts +2 -0
- package/lib/gear.js +20 -2
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/mic.d.ts +3 -1
- package/lib/mic.js +43 -11
- package/lib/prompt.d.ts +1 -1
- package/lib/prompt.js +28 -11
- package/package.json +4 -4
package/lib/gear.d.ts
CHANGED
|
@@ -49,3 +49,5 @@ export interface GearState {
|
|
|
49
49
|
* writes the default until it has an id). Signed out, it lives for the page.
|
|
50
50
|
*/
|
|
51
51
|
export declare function useGear(t: Target, key?: string): GearState;
|
|
52
|
+
/** The dictation language the person keeps, null for Auto, and how to change it. */
|
|
53
|
+
export declare function useLanguage(t: Target): readonly [string | null, (id: string | null) => void];
|
package/lib/gear.js
CHANGED
|
@@ -62,7 +62,7 @@ export function shelve(gears, key, g) {
|
|
|
62
62
|
}
|
|
63
63
|
return out;
|
|
64
64
|
}
|
|
65
|
-
let doc = { gear: BARE, gears: {} };
|
|
65
|
+
let doc = { gear: BARE, gears: {}, language: null };
|
|
66
66
|
let loaded = null;
|
|
67
67
|
const listeners = new Set();
|
|
68
68
|
const emit = () => listeners.forEach((f) => f());
|
|
@@ -80,7 +80,8 @@ function load(t) {
|
|
|
80
80
|
const raw = r?.prefs ?? {};
|
|
81
81
|
const gear = gearOf(raw.gear);
|
|
82
82
|
const all = raw.gears && typeof raw.gears === 'object' ? raw.gears : {};
|
|
83
|
-
|
|
83
|
+
const language = typeof raw.language === 'string' && raw.language ? raw.language : null;
|
|
84
|
+
doc = { gear, gears: Object.fromEntries(Object.entries(all).map(([k, v]) => [k, gearOf(v, gear)])), language };
|
|
84
85
|
emit();
|
|
85
86
|
})
|
|
86
87
|
.catch(() => {
|
|
@@ -121,3 +122,20 @@ export function useGear(t, key = '') {
|
|
|
121
122
|
},
|
|
122
123
|
}), [gear, error, write, t]);
|
|
123
124
|
}
|
|
125
|
+
/** The dictation language the person keeps, null for Auto, and how to change it. */
|
|
126
|
+
export function useLanguage(t) {
|
|
127
|
+
useEffect(() => load(t), [t]);
|
|
128
|
+
const now = useSyncExternalStore(subscribe, () => doc, () => doc);
|
|
129
|
+
const set = useCallback((id) => {
|
|
130
|
+
const was = doc.language;
|
|
131
|
+
doc = { ...doc, language: id };
|
|
132
|
+
emit();
|
|
133
|
+
// A refused save takes the choice back, so what is shown is what is kept.
|
|
134
|
+
if (t.token())
|
|
135
|
+
call(t, 'PATCH', '/v1/pref', { language: id }).catch(() => {
|
|
136
|
+
doc = { ...doc, language: was };
|
|
137
|
+
emit();
|
|
138
|
+
});
|
|
139
|
+
}, [t]);
|
|
140
|
+
return [now.language, set];
|
|
141
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export { href, parse, path, PLACES, route, SESSION, SLUG, type Route, type Scree
|
|
|
51
51
|
export { catalog, EFFORTS, FIRST, FREE, grants, isFree, keep, kept, nameOf, reasons, useCatalog, useChoice, useMind, type Choice, type Effort, type Mind } from './mind.ts';
|
|
52
52
|
export { ASK, cut, GAP, MEASURE, measure, Prompt, prompt, ROUND, trigger, Tune, useEar, useMention, useUpload, type Chip, type PromptOptions } from './prompt.tsx';
|
|
53
53
|
export { Plus, type Link, type PlusProps, type Row as ShelfRow, type Shelf, type Switch } from './plus.tsx';
|
|
54
|
-
export { BARE, gearOf, MODES, useGear, type Gear, type GearState, type Mode as GearMode } from './gear.ts';
|
|
54
|
+
export { BARE, gearOf, MODES, useGear, useLanguage, type Gear, type GearState, type Mode as GearMode } from './gear.ts';
|
|
55
55
|
export { useCarry, type Carry } from './carry.tsx';
|
|
56
56
|
export { Dictation } from './mic.tsx';
|
|
57
57
|
export type { Composer as PluginComposer, Plugin, Prep, Turn } from './plugin.ts';
|
package/lib/index.js
CHANGED
|
@@ -46,7 +46,7 @@ export { href, parse, path, PLACES, route, SESSION, SLUG } from './route.js';
|
|
|
46
46
|
export { catalog, EFFORTS, FIRST, FREE, grants, isFree, keep, kept, nameOf, reasons, useCatalog, useChoice, useMind } from './mind.js';
|
|
47
47
|
export { ASK, cut, GAP, MEASURE, measure, Prompt, prompt, ROUND, trigger, Tune, useEar, useMention, useUpload } from './prompt.js';
|
|
48
48
|
export { Plus } from './plus.js';
|
|
49
|
-
export { BARE, gearOf, MODES, useGear } from './gear.js';
|
|
49
|
+
export { BARE, gearOf, MODES, useGear, useLanguage } from './gear.js';
|
|
50
50
|
export { useCarry } from './carry.js';
|
|
51
51
|
export { Dictation } from './mic.js';
|
|
52
52
|
export { artifacts, blob, lead, renders, Reply, Served, speakable, speaker } from './reply.js';
|
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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, fill: state === 'idle' ? 'none' : '
|
|
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,13 +37,18 @@ 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
|
+
/** The ink on a filled round: the accent's own foreground (gui's icons default to the theme's text). */
|
|
47
|
+
const ON = 'var(--primary-foreground)';
|
|
45
48
|
/** A round control with no ground of its own until it is pointed at. */
|
|
46
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;
|
|
47
52
|
/** What every Composer placeholder says, unless a surface names who is listening. */
|
|
48
53
|
export const ASK = 'Type / for skills';
|
|
49
54
|
/**
|
|
@@ -79,15 +84,27 @@ function Opens({ label, word, dim, children }) {
|
|
|
79
84
|
function Model({ mind, onUpgrade }) {
|
|
80
85
|
const [open, setOpen] = useState(false);
|
|
81
86
|
const [q, setQ] = useState('');
|
|
82
|
-
|
|
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(() => {
|
|
83
91
|
const list = mind.models.filter((m) => !ROUTER_IDS.has(m.id) && (m.outputs?.includes('text') ?? true) && (!q.trim() || matchesModel(m, q)));
|
|
84
|
-
|
|
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) };
|
|
85
102
|
}, [mind.models, q]);
|
|
86
103
|
const routed = mind.model === ENSO;
|
|
87
|
-
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,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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] })] })] }));
|
|
91
108
|
}
|
|
92
109
|
/** The row under the frame: where (Dev), then the model, the effort and the mode. */
|
|
93
110
|
export function Tune({ mind, mode, where, onUpgrade }) {
|
|
@@ -127,15 +144,15 @@ export function prompt(o, state) {
|
|
|
127
144
|
};
|
|
128
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;
|
|
129
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;
|
|
130
|
-
const foot = o.mind || o.mode || o.where || (!narrow && o.below) ? (_jsxs(YStack, {
|
|
131
|
-
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: () => {
|
|
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;
|
|
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: () => {
|
|
132
149
|
if (!live)
|
|
133
150
|
return;
|
|
134
151
|
if (busy)
|
|
135
152
|
o.onStop?.();
|
|
136
153
|
else
|
|
137
154
|
o.onSend();
|
|
138
|
-
}, children: busy ? _jsx(Square, { size: 13, fill:
|
|
155
|
+
}, children: busy ? _jsx(Square, { size: 13, color: ON, fill: ON }) : _jsx(ArrowUp, { size: 16, strokeWidth: 2.5, color: ON }) }));
|
|
139
156
|
return {
|
|
140
157
|
value: o.value,
|
|
141
158
|
onChange: o.onChange,
|
|
@@ -171,7 +188,7 @@ export function prompt(o, state) {
|
|
|
171
188
|
'$max-sm': { rounded: CORNER_PHONE },
|
|
172
189
|
...(o.framed ? { boxShadow: 'var(--shadow-sheet-1)' } : null),
|
|
173
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)))] })),
|
|
174
|
-
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 }) }) })) : (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)] })),
|
|
175
192
|
};
|
|
176
193
|
}
|
|
177
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.
|
|
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.
|
|
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",
|
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"@hanzo/gui": ">=8.3.5",
|
|
75
|
-
"@hanzo/ui": ">=8.
|
|
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.
|
|
78
|
+
"@hanzo/voice": ">=0.1.19",
|
|
79
79
|
"@hanzo/composer": ">=0.2.0"
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|