@hanzo/build 0.2.83 → 0.2.85

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/tools.d.ts CHANGED
@@ -1,4 +1,10 @@
1
- import { type Say } from '@hanzo/i18n/react';
1
+ /**
2
+ * Files with an ask, for every composer the builder draws: they ride the
3
+ * prompt as text — nothing is uploaded anywhere else. The paperclip is the
4
+ * composer's (@hanzo/ui/chat); this reads what it picks and says what is held.
5
+ */
6
+ import type { Say } from '@hanzo/i18n/react';
7
+ import type { Carried } from '@hanzo/ui/chat';
2
8
  export interface Attached {
3
9
  name: string;
4
10
  text: string;
@@ -12,8 +18,5 @@ export declare function read(have: Attached[], list: FileList, t?: Say): Promise
12
18
  }>;
13
19
  /** Ask the browser for files and read them in, within the caps; the picker is minted per press. */
14
20
  export declare function pick(have: Attached[], onFiles: (f: Attached[]) => void, onNote: (n: string) => void, t?: Say): void;
15
- /** The attached files as small removable marks. */
16
- export declare function Files({ files, onFiles }: {
17
- files: Attached[];
18
- onFiles: (f: Attached[]) => void;
19
- }): import("react").JSX.Element;
21
+ /** The attached files as what the composer carries: a removable chip each. */
22
+ export declare function carrying(files: Attached[], onFiles: (f: Attached[]) => void): Carried[];
package/lib/tools.js CHANGED
@@ -1,13 +1,5 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * Files with an ask, for every composer the builder draws: they ride the
4
- * prompt as text — nothing is uploaded anywhere else. The paperclip is the
5
- * composer's (prompt.tsx); this reads what it picks and draws the chips.
6
- */
7
- import { SizableText, XStack } from '@hanzo/gui';
8
- import { useI18n } from '@hanzo/i18n/react';
9
- import { Paperclip, X } from '@hanzogui/lucide-icons-2';
10
- import { ComposerTool } from '@hanzo/ui/chat';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Paperclip } from '@hanzogui/lucide-icons-2';
11
3
  import { english } from './say.js';
12
4
  const EACH = 100_000;
13
5
  const ALL = 200_000;
@@ -54,8 +46,7 @@ export function pick(have, onFiles, onNote, t = english) {
54
46
  };
55
47
  ask.click();
56
48
  }
57
- /** The attached files as small removable marks. */
58
- export function Files({ files, onFiles }) {
59
- const { t } = useI18n();
60
- return (_jsx(_Fragment, { children: files.map((f) => (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", height: 24, rounded: "$2", bg: "$raised", children: [_jsx(Paperclip, { size: 12 }), _jsx(SizableText, { size: "$1", color: "$ink", numberOfLines: 1, maxW: 140, children: f.name }), _jsx(ComposerTool, { label: t('Remove {name}', { name: f.name }), icon: _jsx(X, { size: 12 }), onPress: () => onFiles(files.filter((x) => x !== f)) })] }, f.name))) }));
49
+ /** The attached files as what the composer carries: a removable chip each. */
50
+ export function carrying(files, onFiles) {
51
+ return files.map((f) => ({ id: `file:${f.name}`, label: f.name, icon: _jsx(Paperclip, { size: 14 }), onRemove: () => onFiles(files.filter((x) => x !== f)) }));
61
52
  }
package/lib/transcript.js CHANGED
@@ -20,9 +20,8 @@ import { SizableText, XStack, YStack } from '@hanzo/gui';
20
20
  import { useI18n } from '@hanzo/i18n/react';
21
21
  import { Button } from '@hanzo/ui';
22
22
  import { Feedback } from '@hanzo/ui/agents';
23
- import { Code, Message, Step, Thread } from '@hanzo/ui/chat';
23
+ import { Code, GAP, MEASURE, Message, Step, Thread } from '@hanzo/ui/chat';
24
24
  import { useState } from 'react';
25
- import { GAP, MEASURE } from './prompt.js';
26
25
  import { Prose } from './prose.js';
27
26
  import { artifacts, lead, Reply } from './reply.js';
28
27
  /** A card's output, its tail: what a command said last is why it stopped. */
package/lib/voice.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export declare const LANGUAGES: {
2
- id: string;
3
- label: string;
4
- }[];
1
+ import { type Speech } from '@hanzo/voice';
2
+ import type { Target } from './api/call.ts';
3
+ /** The platform's ear and voice for a target: dictation, talk and Listen all hear through it. */
4
+ export declare function useEar(t: Target): Speech;
5
5
  /** The language this browser is set to, when it is one dictation offers; English otherwise. */
6
6
  export declare function spoken(): string;
7
7
  /** The person's saved language (Settings → General), else this browser's, else English. */
package/lib/voice.js CHANGED
@@ -1,17 +1,17 @@
1
1
  /**
2
- * The language dictation listens in. The microphone itself is the composer's
3
- * (prompt.tsx, @hanzo/voice): heard by the platform, never by a browser's
4
- * built-in recognizer, which ships the audio to its vendor.
2
+ * The language dictation listens in, and the platform's ear. The microphone
3
+ * itself is the composer's (@hanzo/ui/chat, @hanzo/voice): heard by the
4
+ * platform, never by a browser's built-in recognizer, which ships the audio to
5
+ * its vendor.
5
6
  */
7
+ import { LANGUAGES } from '@hanzo/ui/chat/pure';
8
+ import { speech } from '@hanzo/voice';
9
+ import { useMemo } from 'react';
6
10
  import { usePrefs } from './prefs.js';
7
- export const LANGUAGES = [
8
- { id: 'en', label: 'English' },
9
- { id: 'es', label: 'Español' },
10
- { id: 'fr', label: 'Français' },
11
- { id: 'de', label: 'Deutsch' },
12
- { id: 'ja', label: '日本語' },
13
- { id: 'zh', label: '中文' },
14
- ];
11
+ /** The platform's ear and voice for a target: dictation, talk and Listen all hear through it. */
12
+ export function useEar(t) {
13
+ return useMemo(() => speech({ baseUrl: t.api, token: t.token, ...(t.org ? { org: t.org } : {}) }), [t]);
14
+ }
15
15
  /** The language this browser is set to, when it is one dictation offers; English otherwise. */
16
16
  export function spoken() {
17
17
  const nav = typeof navigator !== 'undefined' ? navigator.language.split('-')[0] : 'en';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.83",
3
+ "version": "0.2.85",
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.",
@@ -13,7 +13,7 @@
13
13
  "@hanzo/gui": "8.3.5",
14
14
  "@hanzo/i18n": "0.4.0",
15
15
  "@hanzo/iam": "0.21.28",
16
- "@hanzo/ui": "8.29.0",
16
+ "@hanzo/ui": "8.29.23",
17
17
  "@hanzo/vite": "0.1.4",
18
18
  "@hanzo/voice": "0.1.19",
19
19
  "@hanzogui/lucide-icons-2": "8.3.5",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "peerDependencies": {
75
75
  "@hanzo/gui": ">=8.3.5",
76
- "@hanzo/ui": ">=8.29.0",
76
+ "@hanzo/ui": ">=8.29.23",
77
77
  "@hanzogui/lucide-icons-2": ">=8.3.5",
78
78
  "react": ">=19.2.8",
79
79
  "@hanzo/voice": ">=0.1.19",
package/lib/mic.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import { type Machine } from '@hanzo/voice';
2
- export interface DictationProps {
3
- voice: Machine;
4
- disabled?: boolean;
5
- /** A phone: both halves take a finger's width. */
6
- narrow?: boolean;
7
- /** The language dictation listens in, or null for Auto (the browser's own). */
8
- language?: string | null;
9
- onLanguage?: (id: string | null) => void;
10
- }
11
- /** The mic and its chevron, one control on the composer's row. */
12
- export declare function Dictation({ voice, disabled, narrow, language, onLanguage }: DictationProps): import("react").JSX.Element;
package/lib/mic.js DELETED
@@ -1,139 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * Dictation: the mic, and beside it a chevron that opens its menu — the
4
- * microphones this browser has, the chosen one's live level, and the language
5
- * dictation listens in (Auto is the browser's own).
6
- *
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.
12
- *
13
- * Its words are the reader's language (@hanzo/i18n `t`); a microphone's name
14
- * is the device's and a language's is its own, drawn as given.
15
- */
16
- import { SizableText, XStack, YStack } from '@hanzo/gui';
17
- import { Check, ChevronDown, Mic as MicIcon } from '@hanzogui/lucide-icons-2';
18
- import { Control } from '@hanzo/composer';
19
- import { useI18n } from '@hanzo/i18n/react';
20
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@hanzo/ui';
21
- import { choose, Voice } from '@hanzo/voice';
22
- import { useEffect, useRef, useState } from 'react';
23
- import { LANGUAGES } from './voice.js';
24
- const KEY = 'hanzo.mic';
25
- function kept() {
26
- try {
27
- return window.localStorage.getItem(KEY);
28
- }
29
- catch {
30
- return null;
31
- }
32
- }
33
- function keep(id) {
34
- try {
35
- if (id)
36
- window.localStorage.setItem(KEY, id);
37
- else
38
- window.localStorage.removeItem(KEY);
39
- }
40
- catch {
41
- // Kept for the page: `choose` holds it.
42
- }
43
- choose(id);
44
- }
45
- // The page's choice reaches every capture from the first one.
46
- if (typeof window !== 'undefined')
47
- choose(kept());
48
- /** The mic is a plain mark until it is pointed at. */
49
- const PLAIN = { bg: 'transparent', color: 'var(--foreground)', hoverStyle: { bg: '$hover' } };
50
- /** The microphones this browser lists; labels only once the page may use one. */
51
- function useInputs(open) {
52
- const [inputs, setInputs] = useState([]);
53
- const [allowed, setAllowed] = useState(false);
54
- const [using, setUsing] = useState('');
55
- useEffect(() => {
56
- if (!open || typeof navigator === 'undefined' || !navigator.mediaDevices?.enumerateDevices)
57
- return;
58
- let live = true;
59
- void navigator.mediaDevices.enumerateDevices().then((all) => {
60
- if (!live)
61
- return;
62
- const mics = all.filter((d) => d.kind === 'audioinput' && d.deviceId !== 'default' && d.deviceId !== 'communications');
63
- setInputs(mics);
64
- // The system's own input: the group the browser's "default" entry belongs to, else the first.
65
- setUsing(all.find((d) => d.kind === 'audioinput' && d.deviceId === 'default')?.groupId || mics[0]?.groupId || '');
66
- setAllowed(mics.some((d) => d.label));
67
- });
68
- return () => {
69
- live = false;
70
- };
71
- }, [open]);
72
- return { inputs, allowed, using };
73
- }
74
- /** The chosen microphone's level while the menu is open, drawn without re-rendering. */
75
- function Meter({ device, on }) {
76
- // The level grows from the line's start: the right edge in a right-to-left language.
77
- const { dir } = useI18n();
78
- const bar = useRef(null);
79
- useEffect(() => {
80
- if (!on)
81
- return;
82
- let stream = null;
83
- let ctx = null;
84
- let frame = 0;
85
- let live = true;
86
- void navigator.mediaDevices
87
- .getUserMedia({ audio: device ? { deviceId: { exact: device } } : true })
88
- .then((s) => {
89
- if (!live)
90
- return s.getTracks().forEach((t) => t.stop());
91
- stream = s;
92
- ctx = new AudioContext();
93
- const tap = ctx.createAnalyser();
94
- tap.fftSize = 512;
95
- ctx.createMediaStreamSource(s).connect(tap);
96
- const buf = new Uint8Array(tap.fftSize);
97
- const draw = () => {
98
- tap.getByteTimeDomainData(buf);
99
- let peak = 0;
100
- for (const v of buf)
101
- peak = Math.max(peak, Math.abs(v - 128));
102
- if (bar.current)
103
- bar.current.style.transform = `scaleX(${Math.min(1, (peak / 128) * 2.5)})`;
104
- frame = requestAnimationFrame(draw);
105
- };
106
- draw();
107
- })
108
- .catch(() => { });
109
- return () => {
110
- live = false;
111
- cancelAnimationFrame(frame);
112
- stream?.getTracks().forEach((t) => t.stop());
113
- void ctx?.close();
114
- };
115
- }, [device, on]);
116
- 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",
117
- // Empty at rest; the level is written per frame onto the node's transform, so no class is minted per value.
118
- scaleX: 0, transformOrigin: dir === 'rtl' ? 'right' : 'left' }) })] }));
119
- }
120
- /** The mic and its chevron, one control on the composer's row. */
121
- export function Dictation({ voice, disabled = false, narrow = false, language = null, onLanguage }) {
122
- const { t } = useI18n();
123
- const [open, setOpen] = useState(false);
124
- // The Language list, drawn only while open (see plus.tsx).
125
- const [lang, setLang] = useState(false);
126
- const { inputs, allowed, using } = useInputs(open);
127
- const [device, setDevice] = useState(() => (typeof window === 'undefined' ? null : kept()));
128
- const spoken = LANGUAGES.find((l) => l.id === language);
129
- // The chevron is drawn while the mic is reached for (pointer, keyboard, menu open); at rest the row is the mic alone.
130
- const [near, setNear] = useState(false);
131
- const shown = open || near;
132
- return (_jsxs(XStack, { "data-slot": "dictation", items: "center", rounded: 8, borderWidth: 1, borderColor: open ? '$borderColor' : 'transparent', hoverStyle: { borderColor: '$borderColor' }, shrink: 0, onPointerEnter: () => setNear(true), onPointerLeave: () => setNear(false), onFocus: () => setNear(true), onBlur: () => setNear(false), children: [_jsx(Control, { asChild: true, size: narrow ? 44 : 30, ...PLAIN, children: _jsx(Voice, { voice: voice, disabled: disabled, says: { idle: t('Dictate'), listening: t('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": t('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: "$soft", opacity: shown || narrow ? 1 : 0 }) }) }), _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: t('No devices found') }) })) : (inputs.map((d, i) => {
133
- const on = device ? d.deviceId === device : d.groupId === using;
134
- return (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => {
135
- setDevice(d.deviceId);
136
- keep(d.deviceId);
137
- }, children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", numberOfLines: 1, flex: 1, children: d.label || t('Microphone {n}', { n: i + 1 }) }), on ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, d.deviceId || i));
138
- })), onLanguage ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 13, color: "$hint", children: t('Dictation settings') }) }), _jsxs(DropdownMenuSub, { open: lang, onOpenChange: setLang, children: [_jsx(DropdownMenuSubTrigger, { "data-slot": "dictation-language", onPress: () => setLang(true), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: t('Language') }), _jsx(SizableText, { fontSize: 14, color: "$hint", children: spoken?.label ?? t('Auto') })] }) }), lang ? (_jsx(DropdownMenuSubContent, { minW: 160, pointerEvents: "auto", children: [{ id: '', label: t('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] })] })) : null] })] })] }));
139
- }
package/lib/plus.d.ts DELETED
@@ -1,46 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- /** One thing a shelf holds. `on` marks what the conversation already has. */
3
- export interface Row {
4
- id: string;
5
- label: string;
6
- icon?: ReactNode;
7
- /** A few dim words beside the label, where they help. */
8
- note?: string;
9
- on?: boolean;
10
- }
11
- export interface Link {
12
- label: string;
13
- onPress: () => void;
14
- }
15
- export interface Shelf {
16
- rows: Row[];
17
- pick: (id: string) => void;
18
- loading?: boolean;
19
- error?: string | null;
20
- /** A search field over the rows, with this placeholder. */
21
- search?: string;
22
- /** Said when there are no rows and nothing is loading. */
23
- empty?: string;
24
- create?: Link;
25
- manage?: Link;
26
- browse?: Link;
27
- }
28
- export interface Switch {
29
- on: boolean;
30
- set: (on: boolean) => void;
31
- }
32
- export interface PlusProps {
33
- /** Upload or add files (Ctrl+U). */
34
- upload?: () => void;
35
- agents?: Shelf;
36
- skills?: Shelf;
37
- connectors?: Shelf;
38
- design?: Shelf;
39
- plugins?: Shelf;
40
- devices?: Shelf;
41
- research?: Switch;
42
- memory?: Switch;
43
- disabled?: boolean;
44
- }
45
- /** The `+` and its menu. */
46
- export declare function Plus(p: PlusProps): import("react").JSX.Element;
package/lib/plus.js DELETED
@@ -1,72 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * The composer's `+`: files, then what the conversation works with — agents,
4
- * skills, connectors, a design system, plugins, a device — then the Research
5
- * and Memory switches. A row is drawn only when the surface hands it a shelf:
6
- * a shelf is what is there (`rows`), what picking one does, and the ways to
7
- * make, manage and find more. A row with nothing behind it is not drawn.
8
- *
9
- * On a laptop each shelf opens beside the menu; on a phone it is a page of the
10
- * same menu, with Back.
11
- *
12
- * The menu's own words are the reader's language (@hanzo/i18n `t`); what a
13
- * shelf holds — its rows, its links, its search and empty words — is the
14
- * surface's, drawn as given.
15
- */
16
- import { SizableText, useMedia, XStack } from '@hanzo/gui';
17
- import { phrase } from '@hanzo/i18n';
18
- import { useI18n } from '@hanzo/i18n/react';
19
- import { Blocks, Bot, Briefcase, Brain, Monitor, Palette, Paperclip, Plug, Plus as PlusIcon, ScrollText, Telescope, } from '@hanzogui/lucide-icons-2';
20
- import { DropdownMenu, DropdownMenuBack, DropdownMenuContent, DropdownMenuEmpty, DropdownMenuItem, DropdownMenuLoading, DropdownMenuPage, DropdownMenuSearch, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@hanzo/ui';
21
- import { Check } from '@hanzogui/lucide-icons-2';
22
- import { useMemo, useState } from 'react';
23
- const ICON = 16;
24
- /**
25
- * The shelves, in the menu's order, with their names and marks, and the three
26
- * sentences a shelf says of itself: its search's name, while it reads, and
27
- * when it holds nothing. Whole sentences, so a language puts the noun where it goes.
28
- */
29
- const SHELVES = [
30
- { key: 'agents', label: phrase('Agents'), search: phrase('Search agents'), reading: phrase('Reading agents'), none: phrase('No agents yet'), icon: _jsx(Bot, { size: ICON }) },
31
- { key: 'skills', label: phrase('Skills'), search: phrase('Search skills'), reading: phrase('Reading skills'), none: phrase('No skills yet'), icon: _jsx(ScrollText, { size: ICON }) },
32
- { key: 'connectors', label: phrase('Connectors'), search: phrase('Search connectors'), reading: phrase('Reading connectors'), none: phrase('No connectors yet'), icon: _jsx(Blocks, { size: ICON }) },
33
- { key: 'design', label: phrase('Design system'), search: phrase('Search design system'), reading: phrase('Reading design system'), none: phrase('No design system yet'), icon: _jsx(Palette, { size: ICON }) },
34
- { key: 'plugins', label: phrase('Plugins'), search: phrase('Search plugins'), reading: phrase('Reading plugins'), none: phrase('No plugins yet'), icon: _jsx(Plug, { size: ICON }) },
35
- { key: 'devices', label: phrase('Devices'), search: phrase('Search devices'), reading: phrase('Reading devices'), none: phrase('No devices yet'), icon: _jsx(Monitor, { size: ICON }) },
36
- ];
37
- /** A row's anatomy: its mark, its label, a dim note, and a check when it is on. */
38
- function Line({ icon, label, note, on, tail }) {
39
- return (_jsxs(XStack, { flex: 1, minW: 0, items: "center", gap: 10, children: [icon ? (_jsx(XStack, { width: ICON, height: ICON, items: "center", justify: "center", shrink: 0, "aria-hidden": true, children: icon })) : null, _jsx(SizableText, { fontSize: 14, color: "$ink", numberOfLines: 1, shrink: 0, children: label }), note ? (_jsx(SizableText, { fontSize: 13, color: "$hint", numberOfLines: 1, flex: 1, minW: 0, children: note })) : (_jsx(XStack, { flex: 1 })), tail, on ? _jsx(Check, { size: ICON, color: "var(--primary)", "aria-hidden": true }) : null] }));
40
- }
41
- /** A shelf's rows, its status, then its ways to more. */
42
- function Contents({ shelf, words, close }) {
43
- const { t } = useI18n();
44
- const [q, setQ] = useState('');
45
- const needle = q.trim().toLowerCase();
46
- const rows = needle ? shelf.rows.filter((r) => `${r.label} ${r.note ?? ''}`.toLowerCase().includes(needle)) : shelf.rows;
47
- const links = [shelf.create, shelf.manage, shelf.browse].filter((l) => Boolean(l));
48
- return (_jsxs(_Fragment, { children: [shelf.search ? _jsx(DropdownMenuSearch, { value: q, onValueChange: setQ, placeholder: shelf.search, "aria-label": t(words.search) }) : null, shelf.loading && !shelf.rows.length ? _jsx(DropdownMenuLoading, { children: t(words.reading) }) : null, shelf.error ? _jsx(DropdownMenuEmpty, { children: shelf.error }) : null, !shelf.loading && !shelf.error && !rows.length ? _jsx(DropdownMenuEmpty, { children: needle ? t('Nothing matches') : (shelf.empty ?? t(words.none)) }) : null, rows.map((r) => (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => shelf.pick(r.id), "aria-checked": r.on ?? false, "data-slot": "plus-row", children: _jsx(Line, { icon: r.icon, label: r.label, note: r.note, on: r.on }) }, r.id))), links.length ? _jsx(DropdownMenuSeparator, {}) : null, links.map((l, i) => (_jsx(DropdownMenuItem, { onSelect: () => {
49
- close();
50
- l.onPress();
51
- }, children: _jsx(Line, { icon: l === shelf.manage ? _jsx(Briefcase, { size: ICON }) : _jsx(PlusIcon, { size: ICON }), label: l.label }) }, l.label)))] }));
52
- }
53
- /** The `+` and its menu. */
54
- export function Plus(p) {
55
- const { t } = useI18n();
56
- const media = useMedia();
57
- const wide = media.sm;
58
- const [open, setOpen] = useState(false);
59
- // The shelf open beside the menu.
60
- const [sub, setSub] = useState(null);
61
- const close = () => setOpen(false);
62
- const shelves = useMemo(() => SHELVES.filter((s) => p[s.key]), [p]);
63
- const switches = [
64
- p.research ? { key: 'research', label: t('Research'), icon: _jsx(Telescope, { size: ICON }), s: p.research } : null,
65
- p.memory ? { key: 'memory', label: t('Memory'), icon: _jsx(Brain, { size: ICON }), s: p.memory } : null,
66
- ].filter((x) => x !== null);
67
- const main = (_jsxs(_Fragment, { children: [p.upload ? (_jsx(DropdownMenuItem, { "data-slot": "plus-upload", onSelect: () => {
68
- close();
69
- p.upload?.();
70
- }, children: _jsx(Line, { icon: _jsx(Paperclip, { size: ICON }), label: t('Upload or add files'), tail: _jsx(DropdownMenuShortcut, { "data-i18n-ltr": true, children: "Ctrl+U" }) }) })) : null, p.upload && shelves.length ? _jsx(DropdownMenuSeparator, {}) : null, shelves.map((s) => wide ? (_jsxs(DropdownMenuSub, { open: sub === s.key, onOpenChange: (o) => setSub(o ? s.key : sub === s.key ? null : sub), children: [_jsx(DropdownMenuSubTrigger, { "data-slot": `plus-${s.key}`, onPress: () => setSub(s.key), children: _jsx(Line, { icon: s.icon, label: t(s.label) }) }), sub === s.key ? (_jsx(DropdownMenuSubContent, { minW: 200, maxW: 340, maxH: 420, pointerEvents: "auto", style: { overflowY: "auto" }, children: _jsx(Contents, { shelf: p[s.key], words: s, close: close }) })) : null] }, s.key)) : (_jsx(DropdownMenuItem, { page: s.key, "data-slot": `plus-${s.key}`, children: _jsx(Line, { icon: s.icon, label: t(s.label) }) }, s.key))), switches.length && (p.upload || shelves.length) ? _jsx(DropdownMenuSeparator, {}) : null, switches.map((w) => (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => w.s.set(!w.s.on), role: 'menuitemcheckbox', "aria-checked": w.s.on, "data-slot": `plus-${w.key}`, children: _jsx(Line, { icon: w.icon, label: w.label, on: w.s.on }) }, w.key)))] }));
71
- return (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, ...(wide ? null : { defaultPage: 'main' }), children: [_jsx(DropdownMenuTrigger, { asChild: true, disabled: p.disabled, children: _jsx(XStack, { render: "button", "data-slot": "plus", "aria-label": t('Add files, agents, skills and more'), width: 32, height: 32, rounded: 8, items: "center", justify: "center", shrink: 0, cursor: p.disabled ? 'default' : 'pointer', opacity: p.disabled ? 0.4 : 1, bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineColor: '$outlineColor', outlineWidth: 2, outlineStyle: 'solid' }, "$max-sm": { width: 44, height: 44, rounded: 22 }, children: _jsx(PlusIcon, { size: 18, color: "$ink" }) }) }), _jsx(DropdownMenuContent, { align: "start", minW: 240, "data-slot": "plus-menu", children: wide ? (main) : (_jsxs(_Fragment, { children: [_jsx(DropdownMenuPage, { id: "main", children: main }), shelves.map((s) => (_jsxs(DropdownMenuPage, { id: s.key, children: [_jsx(DropdownMenuBack, { children: t(s.label) }), _jsx(Contents, { shelf: p[s.key], words: s, close: close })] }, s.key)))] })) })] }));
72
- }
package/lib/prompt.d.ts DELETED
@@ -1,134 +0,0 @@
1
- import { type Say } from '@hanzo/i18n/react';
2
- import { type ComposerProps } from '@hanzo/ui/chat';
3
- import { type PauseSource } from '@hanzo/ui/models';
4
- import { type Machine, type Speech } from '@hanzo/voice';
5
- import { type ReactNode } from 'react';
6
- import type { Target } from './api/call.ts';
7
- import { type Mode } from './gear.ts';
8
- import { type Mind } from './mind.ts';
9
- import { type PlusProps, type Shelf } from './plus.tsx';
10
- /** The reading measure: @hanzo/design's prose width, which Width (appearance) retunes. */
11
- export declare const MEASURE = "var(--container-prose, 48rem)";
12
- /** The space between turns, px: one rhythm for Chat and Dev. */
13
- export declare const GAP = 20;
14
- /** The composer's width: the measure less the thread column's 0.75rem gutters, centred, so its edges are the text's. */
15
- export declare const measure: {
16
- readonly width: 'calc(100% - 1.5rem)';
17
- readonly maxW: "calc(var(--container-prose, 48rem) - 1.5rem)";
18
- readonly mx: 'auto';
19
- };
20
- /** The round controls' box, px, and a phone's, a finger's width. */
21
- export declare const ROUND = 32;
22
- /** What every Composer placeholder says, unless a surface names who is listening. */
23
- export declare const ASK: string;
24
- /**
25
- * The frame as a pane of its own, floating on the pane it is drawn in: the
26
- * `--pane-*` cut on design's first paper rung.
27
- */
28
- export declare const cut: {
29
- readonly bg: 'var(--pane-fill, var(--sheet-1))';
30
- readonly rounded: 'var(--pane-round, var(--radius-xl))';
31
- readonly borderWidth: 1;
32
- readonly borderColor: 'var(--pane-edge, var(--white-08))';
33
- readonly backdropFilter: 'var(--pane-blur, blur(20px) saturate(1.8))';
34
- readonly boxShadow: 'var(--shadow-sheet-1)';
35
- };
36
- /** The platform's ear and voice for a target: dictation, talk and Listen all hear through it. */
37
- export declare function useEar(t: Target): Speech;
38
- /** One thing the conversation carries, shown after `+`. */
39
- export interface Chip {
40
- id: string;
41
- label: string;
42
- icon?: ReactNode;
43
- onRemove: () => void;
44
- }
45
- export interface PromptOptions {
46
- value: string;
47
- onChange: (value: string) => void;
48
- onSend: () => void;
49
- /** Stop the turn in flight. Absent, busy only disables send. */
50
- onStop?: () => void;
51
- busy?: boolean;
52
- disabled?: boolean;
53
- /** Whether send is lit. Default: the draft has words. */
54
- ready?: boolean;
55
- placeholder?: string;
56
- label?: string;
57
- /** Above the frame, at its width: what the draft is sent with. */
58
- head?: ReactNode;
59
- field?: ComposerProps['field'];
60
- /** The model chip: given only where the person turned the model selector on (the host's setting). Absent, no model is named. */
61
- model?: Mind | null;
62
- /** The effort selector (Auto, High, XHigh, Max). Null where the next send takes none (steering a run). */
63
- effort?: Mind | null;
64
- limits?: PauseSource | null;
65
- /** A model the plan does not grant was asked for: the way to the plan that does. */
66
- onUpgrade?: (model: string) => void;
67
- /** The `+` menu. Absent, there is none. */
68
- plus?: PlusProps;
69
- /** What the conversation carries, as chips after `+`. */
70
- on?: Chip[];
71
- /** `/` lists these (the skills) above the frame. */
72
- slash?: Shelf;
73
- /** `@` lists these (the plugins) above the frame. */
74
- at?: Shelf;
75
- /** The mode, as the pill under the frame. */
76
- mode?: {
77
- value: Mode;
78
- set: (m: Mode) => void;
79
- items?: readonly Mode[];
80
- } | null;
81
- /** At the start of the row under the frame: Dev's where and which repository. */
82
- where?: ReactNode;
83
- /** Under the row (above the frame on a phone): the person's suggestions. */
84
- below?: ReactNode;
85
- /** Dictation into the draft (@hanzo/voice `useVoice` + `useDictation`). */
86
- voice?: Machine;
87
- /** The language dictation listens in, null for Auto, and how to change it. */
88
- language?: string | null;
89
- onLanguage?: (id: string | null) => void;
90
- /** A spoken conversation (@hanzo/voice `useTalk`): the round button while the draft is empty. */
91
- talk?: Machine;
92
- /** Drawn on a pane: the frame is cut as a pane of its own (`cut`). */
93
- framed?: boolean;
94
- /** Centred just above the frame: what every message is sent under. */
95
- note?: ReactNode;
96
- /** A phone: suggestions go above the frame and every target is 44px. */
97
- narrow?: boolean;
98
- }
99
- /** The row under the frame: where (Dev), then the model where the host shows it, the effort and the mode. */
100
- export declare function Tune({ model, effort, mode, where, onUpgrade, limits, }: {
101
- model?: Mind | null;
102
- effort?: Mind | null;
103
- mode?: PromptOptions['mode'];
104
- where?: ReactNode;
105
- onUpgrade?: (model: string) => void;
106
- limits?: PauseSource | null;
107
- }): import("react").JSX.Element;
108
- /** The `/` or `@` the caret ends on, and the words typed after it. */
109
- export declare function trigger(value: string): {
110
- char: '/' | '@';
111
- query: string;
112
- at: number;
113
- } | null;
114
- /** What `useMention` hands `prompt`: the lit row of a `/` or `@` list, and the reader's `t`. */
115
- export interface Mentioned {
116
- index: number;
117
- closed: string | null;
118
- t?: Say;
119
- }
120
- /** @hanzo/ui `Composer` props for the one composer. */
121
- export declare function prompt(o: PromptOptions, state?: Mentioned): ComposerProps;
122
- /** Ctrl+U (⌘U) uploads, while a composer with an upload is on the page. */
123
- export declare function useUpload(upload: (() => void) | undefined): void;
124
- /**
125
- * The composer's own state — which row of a `/` or `@` list is lit, and the
126
- * reader's `t` — and the keys that move it. A surface that hands `prompt` to
127
- * `Chat` calls this and passes its answer through; `<Prompt>` does it for itself.
128
- */
129
- export declare function useMention(o: PromptOptions): {
130
- state: Mentioned;
131
- field: ComposerProps['field'];
132
- };
133
- /** The composer drawn on its own. */
134
- export declare function Prompt(o: PromptOptions): import("react").JSX.Element;