@hanzo/build 0.2.82 → 0.2.84
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/README.md +17 -10
- package/lib/carry.d.ts +3 -4
- package/lib/carry.js +2 -2
- package/lib/index.d.ts +5 -7
- package/lib/index.js +5 -7
- package/lib/landing.js +53 -47
- package/lib/mind.d.ts +32 -9
- package/lib/mind.js +11 -35
- package/lib/plugins/index.d.ts +1 -1
- package/lib/project.js +8 -8
- package/lib/run.js +7 -8
- package/lib/settings/code.js +1 -1
- package/lib/settings/general.js +2 -1
- package/lib/tools.d.ts +9 -6
- package/lib/tools.js +5 -14
- package/lib/transcript.js +1 -2
- package/lib/voice.d.ts +4 -4
- package/lib/voice.js +11 -11
- package/package.json +3 -3
- package/lib/mic.d.ts +0 -12
- package/lib/mic.js +0 -139
- package/lib/plus.d.ts +0 -46
- package/lib/plus.js +0 -72
- package/lib/prompt.d.ts +0 -131
- package/lib/prompt.js +0 -273
package/lib/run.js
CHANGED
|
@@ -29,7 +29,7 @@ import { useI18n } from '@hanzo/i18n/react';
|
|
|
29
29
|
import { ChevronDown, ChevronRight, Clock, Copy, ExternalLink, GitPullRequest, Globe, Pencil, Share } from '@hanzogui/lucide-icons-2';
|
|
30
30
|
import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from '@hanzo/ui';
|
|
31
31
|
import { Steer } from '@hanzo/ui/agents';
|
|
32
|
-
import { Failure } from '@hanzo/ui/chat';
|
|
32
|
+
import { Composer, Failure, measure } from '@hanzo/ui/chat';
|
|
33
33
|
import { useLimits } from '@hanzo/ui/product/useLimits';
|
|
34
34
|
import { useDictation, useVoice } from '@hanzo/voice';
|
|
35
35
|
import { useEffect, useMemo, useState } from 'react';
|
|
@@ -41,18 +41,17 @@ import { verdict } from './api/verdict.js';
|
|
|
41
41
|
import { useKept, useProjects, useRead, useRun } from './data.js';
|
|
42
42
|
import { Desk, SEED } from './desk.js';
|
|
43
43
|
import { useHost, useTarget } from './host.js';
|
|
44
|
-
import { useMind } from './mind.js';
|
|
44
|
+
import { level, pacing, useMind } from './mind.js';
|
|
45
45
|
import { Out } from './out.js';
|
|
46
46
|
import { Gutter } from './gutter.js';
|
|
47
47
|
import { gap, pane } from './pane.js';
|
|
48
|
-
import {
|
|
49
|
-
import { useSpoken } from './voice.js';
|
|
48
|
+
import { useEar, useSpoken } from './voice.js';
|
|
50
49
|
import { speaker } from './reply.js';
|
|
51
50
|
import { PanelToggle } from './panel.js';
|
|
52
51
|
import { schedule } from './scheduled.js';
|
|
53
52
|
import { SideProvider, useSplit } from './side.js';
|
|
54
53
|
import { PAGE, useDeck, useKey, usePanel } from './tabs.js';
|
|
55
|
-
import {
|
|
54
|
+
import { carrying, compose, pick } from './tools.js';
|
|
56
55
|
import { Transcript } from './transcript.js';
|
|
57
56
|
const LIVE = new Set(['running', 'paused', '']);
|
|
58
57
|
/** A run's status as the line under its title says it; one the service adds later is drawn as it comes. */
|
|
@@ -206,7 +205,7 @@ export function Run({ id }) {
|
|
|
206
205
|
* itself: a run that names none takes the person's saved coding default
|
|
207
206
|
* (apps/coding start.go), which is not what the composer shows.
|
|
208
207
|
*/
|
|
209
|
-
const tuned = (a) => ({ ...a, model: mind.model, effort: mind.effort });
|
|
208
|
+
const tuned = (a) => ({ ...a, model: mind.model, effort: level(mind.effort) });
|
|
210
209
|
// A setup run's work IS its environment: the panel opens on it.
|
|
211
210
|
useEffect(() => {
|
|
212
211
|
if (mode === 'setup')
|
|
@@ -384,9 +383,9 @@ export function Run({ id }) {
|
|
|
384
383
|
: say('This run is still working.') }), _jsx(Steer, { onCommand: command, withhold: [
|
|
385
384
|
...(paused ? ['pause'] : ['resume']),
|
|
386
385
|
...(busy || waiting ? ['pause', 'resume', 'stop'] : []),
|
|
387
|
-
] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? say('You will be notified') : say('Notify me') })] })) : null] }), _jsx(
|
|
386
|
+
] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? say('You will be notified') : say('Notify me') })] })) : null] }), _jsx(Composer, { column: true, framed: true, value: draft, onChange: setDraft, onSend: send, ready: Boolean(draft.trim()), disabled: !signed || busy || waiting || !(running || finished), placeholder: placeholder, label: steering ? say('Steer this run') : say('Follow up on this run'), plus: { upload: () => pick(files, setFiles, setNote, say) }, carried: carrying(files, setFiles),
|
|
388
387
|
// What a new run from here is made with; words that steer this run use neither.
|
|
389
|
-
mind: signed && !steering ? mind : null, limits: limits, voice: voice })] })] }), desk && !wide ? desk_(true) : null, desk && wide ? (_jsxs(YStack, { "data-slot": "desk", ref: split.aside, ...pane,
|
|
388
|
+
mind: signed && !steering ? mind : null, effort: signed && !steering ? pacing(mind) : null, limits: limits, voice: voice })] })] }), desk && !wide ? desk_(true) : null, desk && wide ? (_jsxs(YStack, { "data-slot": "desk", ref: split.aside, ...pane,
|
|
390
389
|
// A pane of its own beside the run's, the gutter between them (useSplit reads it as the inline-start margin).
|
|
391
390
|
marginInlineStart: gap, position: "relative", shrink: 0, minH: 0, style: split.asideStyle, children: [split.grip ? (_jsx(Gutter, { ...split.grip, label: say('Resize the side panel'), inset: pane.borderWidth })) : null, _jsx(SideProvider, { value: split.side, children: desk_(false) })] })) : null, _jsx(Dialog, { open: naming, onOpenChange: setNaming, children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: say('Rename this run') }), _jsx(Input, { autoFocus: true, value: name, onChangeText: setName, "aria-label": say('The run’s name'), maxLength: 512, onKeyDown: (e) => {
|
|
392
391
|
if (e.key === 'Enter')
|
package/lib/settings/code.js
CHANGED
|
@@ -60,7 +60,7 @@ export function Code() {
|
|
|
60
60
|
// The choices in the reader's words, kept by id.
|
|
61
61
|
const efforts = EFFORTS.map((e) => ({ ...e, label: t(e.label) }));
|
|
62
62
|
const modes = MODES.map((m) => ({ ...m, label: t(m.label), hint: t(m.hint) }));
|
|
63
|
-
const effort = efforts.find((e) => e.id === choice.effort);
|
|
63
|
+
const effort = efforts.find((e) => e.id === choice.effort) ?? efforts[0];
|
|
64
64
|
const mode = modes.find((m) => m.id === now.mode);
|
|
65
65
|
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: t('Code'), detail: t('How a new run starts. The model and effort are the ones Chat uses too.') }), _jsxs(Group, { title: t('Defaults'), children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: t('Model'), detail: t('Enso picks one for each step; any other model runs them all.'), trailing: _jsx(ModelPicker, { size: "sm", name: t('Default model'), models: offered, scope: "chat", value: choice.model || ENSO, onChange: (id) => choose({ model: id }), loading: catalog.loading, error: catalog.error }) }), _jsx(Row, { title: t('Effort'), detail: t('How long the agent thinks before it acts.'), trailing: _jsx(ChipSelect, { name: t('Default effort'), label: effort.label, chosen: effort, items: efforts, onChange: (e) => choose({ effort: e.id }), placement: "bottom-end", width: 180 }) }), _jsx(Row, { title: t('Mode'), detail: mode.hint, trailing: _jsx(ChipSelect, { name: t('Default mode'), label: mode.label, chosen: mode, items: modes, onChange: (m) => keep({ mode: m.id }), placement: "bottom-end", width: 260 }) }), _jsx(Row, { title: t('Where it runs'), detail: t('The Hanzo sandbox, or one of your organization’s machines.'), trailing: _jsx(ChipSelect, { name: t('Default place'), label: place.label, chosen: place, items: where, onChange: (w) => keep({ place: w.place }), placeholder: t('Search machines…'), placement: "bottom-end", loading: places.loading, error: places.error ? told(places.error, t) : null }) })] }), _jsx(Note, { children: unhonoured(now.mode, now.place, t) })] }), _jsx(Group, { title: t('Workspace'), children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: t('Environments'), detail: t('What a run installs and starts in each codebase, and the secrets it exports.'), trailing: _jsx(Link, { label: t('Open Environments'), onPress: () => go('environments') }) }), _jsx(Row, { title: t('Machines'), detail: t('Your organization’s own computers that take runs.'), trailing: _jsx(Link, { label: t('Open Machines'), onPress: () => go('machines') }) })] }) }), _jsx(Note, { children: note })] }));
|
|
66
66
|
}
|
package/lib/settings/general.js
CHANGED
|
@@ -15,7 +15,8 @@ import { useState } from 'react';
|
|
|
15
15
|
import { told } from '../api/call.js';
|
|
16
16
|
import { useHost } from '../host.js';
|
|
17
17
|
import { usePrefs } from '../prefs.js';
|
|
18
|
-
import { LANGUAGES
|
|
18
|
+
import { LANGUAGES } from '@hanzo/ui/chat/pure';
|
|
19
|
+
import { spoken } from '../voice.js';
|
|
19
20
|
import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
20
21
|
/** The choices, each word English and marked (`phrase`): drawn through `t`. */
|
|
21
22
|
const THEMES = [
|
package/lib/tools.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
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
|
|
16
|
-
export declare function
|
|
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 {
|
|
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
|
|
58
|
-
export function
|
|
59
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
3
|
-
* (
|
|
4
|
-
* built-in recognizer, which ships the audio to
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
{
|
|
10
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.84",
|
|
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.
|
|
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.
|
|
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,131 +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 and effort. Null where the next send uses neither (steering a run). */
|
|
61
|
-
mind?: Mind | null;
|
|
62
|
-
limits?: PauseSource | null;
|
|
63
|
-
/** A model the plan does not grant was asked for: the way to the plan that does. */
|
|
64
|
-
onUpgrade?: (model: string) => void;
|
|
65
|
-
/** The `+` menu. Absent, there is none. */
|
|
66
|
-
plus?: PlusProps;
|
|
67
|
-
/** What the conversation carries, as chips after `+`. */
|
|
68
|
-
on?: Chip[];
|
|
69
|
-
/** `/` lists these (the skills) above the frame. */
|
|
70
|
-
slash?: Shelf;
|
|
71
|
-
/** `@` lists these (the plugins) above the frame. */
|
|
72
|
-
at?: Shelf;
|
|
73
|
-
/** The mode, as the pill under the frame. */
|
|
74
|
-
mode?: {
|
|
75
|
-
value: Mode;
|
|
76
|
-
set: (m: Mode) => void;
|
|
77
|
-
items?: readonly Mode[];
|
|
78
|
-
} | null;
|
|
79
|
-
/** At the start of the row under the frame: Dev's where and which repository. */
|
|
80
|
-
where?: ReactNode;
|
|
81
|
-
/** Under the row (above the frame on a phone): the person's suggestions. */
|
|
82
|
-
below?: ReactNode;
|
|
83
|
-
/** Dictation into the draft (@hanzo/voice `useVoice` + `useDictation`). */
|
|
84
|
-
voice?: Machine;
|
|
85
|
-
/** The language dictation listens in, null for Auto, and how to change it. */
|
|
86
|
-
language?: string | null;
|
|
87
|
-
onLanguage?: (id: string | null) => void;
|
|
88
|
-
/** A spoken conversation (@hanzo/voice `useTalk`): the round button while the draft is empty. */
|
|
89
|
-
talk?: Machine;
|
|
90
|
-
/** Drawn on a pane: the frame is cut as a pane of its own (`cut`). */
|
|
91
|
-
framed?: boolean;
|
|
92
|
-
/** Centred just above the frame: what every message is sent under. */
|
|
93
|
-
note?: ReactNode;
|
|
94
|
-
/** A phone: suggestions go above the frame and every target is 44px. */
|
|
95
|
-
narrow?: boolean;
|
|
96
|
-
}
|
|
97
|
-
/** The row under the frame: where (Dev), then the model, the effort and the mode. */
|
|
98
|
-
export declare function Tune({ mind, mode, where, onUpgrade, limits }: {
|
|
99
|
-
mind?: Mind | null;
|
|
100
|
-
mode?: PromptOptions['mode'];
|
|
101
|
-
where?: ReactNode;
|
|
102
|
-
onUpgrade?: (model: string) => void;
|
|
103
|
-
limits?: PauseSource | null;
|
|
104
|
-
}): import("react").JSX.Element;
|
|
105
|
-
/** The `/` or `@` the caret ends on, and the words typed after it. */
|
|
106
|
-
export declare function trigger(value: string): {
|
|
107
|
-
char: '/' | '@';
|
|
108
|
-
query: string;
|
|
109
|
-
at: number;
|
|
110
|
-
} | null;
|
|
111
|
-
/** What `useMention` hands `prompt`: the lit row of a `/` or `@` list, and the reader's `t`. */
|
|
112
|
-
export interface Mentioned {
|
|
113
|
-
index: number;
|
|
114
|
-
closed: string | null;
|
|
115
|
-
t?: Say;
|
|
116
|
-
}
|
|
117
|
-
/** @hanzo/ui `Composer` props for the one composer. */
|
|
118
|
-
export declare function prompt(o: PromptOptions, state?: Mentioned): ComposerProps;
|
|
119
|
-
/** Ctrl+U (⌘U) uploads, while a composer with an upload is on the page. */
|
|
120
|
-
export declare function useUpload(upload: (() => void) | undefined): void;
|
|
121
|
-
/**
|
|
122
|
-
* The composer's own state — which row of a `/` or `@` list is lit, and the
|
|
123
|
-
* reader's `t` — and the keys that move it. A surface that hands `prompt` to
|
|
124
|
-
* `Chat` calls this and passes its answer through; `<Prompt>` does it for itself.
|
|
125
|
-
*/
|
|
126
|
-
export declare function useMention(o: PromptOptions): {
|
|
127
|
-
state: Mentioned;
|
|
128
|
-
field: ComposerProps['field'];
|
|
129
|
-
};
|
|
130
|
-
/** The composer drawn on its own. */
|
|
131
|
-
export declare function Prompt(o: PromptOptions): import("react").JSX.Element;
|