@hanzo/build 0.2.82 → 0.2.83
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 +11 -7
- package/lib/host.d.ts +2 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/landing.js +4 -4
- package/lib/mind.d.ts +5 -5
- package/lib/mind.js +24 -23
- package/lib/project.js +3 -3
- package/lib/prompt.d.ts +8 -5
- package/lib/prompt.js +11 -12
- package/lib/run.js +3 -3
- package/lib/settings/code.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,9 +118,12 @@ French and right to left in Arabic.
|
|
|
118
118
|
One of each, shared with Chat (@hanzo/rooms imports them):
|
|
119
119
|
|
|
120
120
|
- `useMind` — the model and effort: `GET /v1/models` read once per platform,
|
|
121
|
-
and the choice kept in this browser (`hanzo.mind`, Enso and
|
|
122
|
-
changed). `Tune` is the
|
|
123
|
-
|
|
121
|
+
and the choice kept in this browser (`hanzo.mind`, Enso and Auto until
|
|
122
|
+
changed). `Tune` is the row that changes it: the effort (Auto, High, XHigh,
|
|
123
|
+
Max) always, the model chip only where the host shows it (`Host.models`, a
|
|
124
|
+
composer's `model`) — on hanzo.ai, Settings' model selector. Settings → Code
|
|
125
|
+
writes the same choice. `@hanzo/build/mind` serves the store alone, without
|
|
126
|
+
the builder.
|
|
124
127
|
- `prompt` / `Prompt` — the composer, as wide as the text above it (`measure`).
|
|
125
128
|
- `Reply` — copy, Listen and Open under an answer.
|
|
126
129
|
- `Panel` over `useDeck` — the side panel and its kept tabs.
|
|
@@ -255,8 +258,8 @@ machines follow under remote control, each linked with `hanzo link`), the
|
|
|
255
258
|
codebase and the branch — the forge's repositories, filtered as you type, read
|
|
256
259
|
again with Refresh list, and a way to Sync for a GitHub repository that is not
|
|
257
260
|
there yet — then the ask, in the composer Chat draws too (`prompt`): the paperclip
|
|
258
|
-
and Build or Plan at its start; the model
|
|
259
|
-
send at its end. A codebase can be added to a project. Opening a codebase or an
|
|
261
|
+
and Build or Plan at its start; the effort, the model where the host shows it
|
|
262
|
+
(`Tune`), dictation and send at its end. A codebase can be added to a project. Opening a codebase or an
|
|
260
263
|
issue from its own screen lands here with that choice already made.
|
|
261
264
|
|
|
262
265
|
**A run.** The transcript as it streams, drawn by what each part is: what the
|
|
@@ -351,7 +354,8 @@ picker and open-in-tab; Share and Publish; the console dock under it.
|
|
|
351
354
|
`mode`, `model` and `effort` are sent with a run as asked — Enso as
|
|
352
355
|
`enso-auto`, never left out — and `issue`, the number a run started from an
|
|
353
356
|
issue closes; the platform honours them where it does and nothing here
|
|
354
|
-
simulates them.
|
|
357
|
+
simulates them. A run takes `low`, `medium` or `high`, so Auto names `medium`
|
|
358
|
+
and High, XHigh and Max name `high` (`level`).
|
|
355
359
|
|
|
356
360
|
## Layout
|
|
357
361
|
|
|
@@ -369,7 +373,7 @@ src/
|
|
|
369
373
|
prose.tsx markdown, drawn as text (markdown.ts reads it)
|
|
370
374
|
desk.tsx the pane beside a run
|
|
371
375
|
mind.ts the model and effort, one choice with Chat
|
|
372
|
-
prompt.tsx the composer, and Tune, the
|
|
376
|
+
prompt.tsx the composer, and Tune, the effort and model row
|
|
373
377
|
reply.tsx the row under an answer
|
|
374
378
|
panel.tsx the side panel; tabs.ts its kept tabs
|
|
375
379
|
side.tsx the column it stands in: width, the middle column, full screen
|
package/lib/host.d.ts
CHANGED
|
@@ -107,6 +107,8 @@ export interface Host {
|
|
|
107
107
|
browser?: Browser;
|
|
108
108
|
/** The host's sidebar lists Settings' sections and Customize's tabs, so the pages draw neither. */
|
|
109
109
|
sections?: boolean;
|
|
110
|
+
/** The composer names the model and offers its picker beside the effort: the person turned the model selector on in the host's settings. Absent, the effort alone. */
|
|
111
|
+
models?: boolean;
|
|
110
112
|
/** The default agents Customize › Agents offers to start from. Absent, none. */
|
|
111
113
|
cast?: readonly Cast[];
|
|
112
114
|
/** Every persona Customize › Agents offers to start from, read when Browse opens. Absent, none. */
|
package/lib/index.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export { href, parse, path, PLACES, route, SESSION, SLUG, type Route, type Scree
|
|
|
49
49
|
* (`prompt`), the row under an answer (`Reply`), and the side panel (`Panel`
|
|
50
50
|
* over a `Deck` of tabs).
|
|
51
51
|
*/
|
|
52
|
-
export { catalog, EFFORTS, FIRST, FREE, grants, isFree, keep, kept, nameOf,
|
|
52
|
+
export { catalog, EFFORTS, FIRST, FREE, grants, isFree, keep, kept, nameOf, useCatalog, useChoice, useMind, type Choice, type Effort, type Mind } from './mind.ts';
|
|
53
53
|
export { ASK, cut, GAP, MEASURE, measure, Prompt, prompt, ROUND, trigger, Tune, useEar, useMention, useUpload, type Chip, type PromptOptions } from './prompt.tsx';
|
|
54
54
|
export { Plus, type Link, type PlusProps, type Row as ShelfRow, type Shelf, type Switch } from './plus.tsx';
|
|
55
55
|
export { BARE, CHAT_MODES, DEV_MODES, gearOf, MODES, useGear, useLanguage, type Gear, type GearState, type Mode as GearMode } from './gear.ts';
|
package/lib/index.js
CHANGED
|
@@ -44,7 +44,7 @@ export { href, parse, path, PLACES, route, SESSION, SLUG } from './route.js';
|
|
|
44
44
|
* (`prompt`), the row under an answer (`Reply`), and the side panel (`Panel`
|
|
45
45
|
* over a `Deck` of tabs).
|
|
46
46
|
*/
|
|
47
|
-
export { catalog, EFFORTS, FIRST, FREE, grants, isFree, keep, kept, nameOf,
|
|
47
|
+
export { catalog, EFFORTS, FIRST, FREE, grants, isFree, keep, kept, nameOf, useCatalog, useChoice, useMind } from './mind.js';
|
|
48
48
|
export { ASK, cut, GAP, MEASURE, measure, Prompt, prompt, ROUND, trigger, Tune, useEar, useMention, useUpload } from './prompt.js';
|
|
49
49
|
export { Plus } from './plus.js';
|
|
50
50
|
export { BARE, CHAT_MODES, DEV_MODES, gearOf, MODES, useGear, useLanguage } from './gear.js';
|
package/lib/landing.js
CHANGED
|
@@ -36,7 +36,7 @@ import { ready, SANDBOX } from './api/places.js';
|
|
|
36
36
|
import { asHub, isForge, isHub, settle } from './choice.js';
|
|
37
37
|
import { useCached, useKept, usePlaces, useRead } from './data.js';
|
|
38
38
|
import { useHost, useTarget } from './host.js';
|
|
39
|
-
import { useMind } from './mind.js';
|
|
39
|
+
import { level, useMind } from './mind.js';
|
|
40
40
|
import { usePrefs } from './prefs.js';
|
|
41
41
|
import { Publish } from './publish.js';
|
|
42
42
|
import { path } from './route.js';
|
|
@@ -231,7 +231,7 @@ export function Landing({ onStarted }) {
|
|
|
231
231
|
targetId: place.id,
|
|
232
232
|
mode,
|
|
233
233
|
model: mind.model,
|
|
234
|
-
effort: mind.effort,
|
|
234
|
+
effort: level(mind.effort),
|
|
235
235
|
issue,
|
|
236
236
|
});
|
|
237
237
|
setDraft('');
|
|
@@ -297,7 +297,7 @@ export function Landing({ onStarted }) {
|
|
|
297
297
|
};
|
|
298
298
|
/** Start the agent that finds a codebase's environment, and open it: its run is the progress. A refusal is the dialog's to say. */
|
|
299
299
|
const setup = async (repo) => {
|
|
300
|
-
const run = await start(target, { prompt: SETUP, repo, mode: 'setup', model: mind.model, effort: mind.effort });
|
|
300
|
+
const run = await start(target, { prompt: SETUP, repo, mode: 'setup', model: mind.model, effort: level(mind.effort) });
|
|
301
301
|
onStarted(run.session);
|
|
302
302
|
};
|
|
303
303
|
/** The codebase a dialog chose, chosen here too. */
|
|
@@ -367,7 +367,7 @@ export function Landing({ onStarted }) {
|
|
|
367
367
|
plugins: carry.plugins,
|
|
368
368
|
devices,
|
|
369
369
|
memory: { on: carry.gear.gear.memory, set: (on) => carry.gear.set({ memory: on }) },
|
|
370
|
-
}, on: carry.on, slash: carry.skills, at: carry.plugins, mode: carry.mode, where: where, below: _jsx(Suggestions, { rows: hints.rows, onAct: act, onDismiss: hints.dismiss }), mind: mind, limits: limits, onUpgrade: () => host.go(path({ kind: 'screen', screen: 'plans' })), voice: voice, language: prefs.language ?? null, onLanguage: (id) => void savePrefs({ language: id }), talk: talk })] })] }), _jsx(Publish, { source: adding, onClose: () => setAdding(null) }), carry.dialog, codebase ? (_jsx(SetupDialog, { repo: codebase, homes: homes, open: offering, onOpenChange: setOffering, onStart: setup, onRepo: choose, onSaved: () => {
|
|
370
|
+
}, on: carry.on, slash: carry.skills, at: carry.plugins, mode: carry.mode, where: where, below: _jsx(Suggestions, { rows: hints.rows, onAct: act, onDismiss: hints.dismiss }), model: host.models ? mind : null, effort: mind, limits: limits, onUpgrade: () => host.go(path({ kind: 'screen', screen: 'plans' })), voice: voice, language: prefs.language ?? null, onLanguage: (id) => void savePrefs({ language: id }), talk: talk })] })] }), _jsx(Publish, { source: adding, onClose: () => setAdding(null) }), carry.dialog, codebase ? (_jsx(SetupDialog, { repo: codebase, homes: homes, open: offering, onOpenChange: setOffering, onStart: setup, onRepo: choose, onSaved: () => {
|
|
371
371
|
setOffering(false);
|
|
372
372
|
env.reload();
|
|
373
373
|
} })) : null] }));
|
package/lib/mind.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type Target } from './api/call.ts';
|
|
2
2
|
import { type Model } from './api/models.ts';
|
|
3
|
-
export type Effort = '
|
|
4
|
-
/** The efforts, in English and marked (`phrase`): the composer draws each word through `t`. */
|
|
3
|
+
export type Effort = 'auto' | 'high' | 'xhigh' | 'max';
|
|
4
|
+
/** The efforts, in order, in English and marked (`phrase`): the composer draws each word through `t`. */
|
|
5
5
|
export declare const EFFORTS: readonly {
|
|
6
6
|
id: Effort;
|
|
7
7
|
label: string;
|
|
8
8
|
note: string;
|
|
9
9
|
}[];
|
|
10
|
+
/** The effort a coding run names (apps/coding takes low, medium or high): medium for Auto, high for the rest. */
|
|
11
|
+
export declare const level: (effort: Effort) => 'medium' | 'high';
|
|
10
12
|
/** What a person chose: the model (Enso routes) and the effort. */
|
|
11
13
|
export interface Choice {
|
|
12
14
|
model: string;
|
|
@@ -17,14 +19,12 @@ export declare const FIRST: Choice;
|
|
|
17
19
|
export declare const FREE = "enso-free";
|
|
18
20
|
/** Whether a plan grants only the free lane. An unknown plan is not taken for Free. */
|
|
19
21
|
export declare const isFree: (plan: string | null | undefined) => boolean;
|
|
20
|
-
/** The kept choice, Enso and
|
|
22
|
+
/** The kept choice, Enso and Auto where nothing is kept. Stable while the stored text is. Low and Medium, offered once, read as Auto. */
|
|
21
23
|
export declare function kept(): Choice;
|
|
22
24
|
/** Keep a change, in this browser, for every surface that reads it. */
|
|
23
25
|
export declare function keep(patch: Partial<Choice>): void;
|
|
24
26
|
/** The kept choice and the way to change it. The server and the first render read the default. */
|
|
25
27
|
export declare function useChoice(): readonly [Choice, (patch: Partial<Choice>) => void];
|
|
26
|
-
/** Whether `model` takes an effort: the router and an undescribed model do, a listed one only if it reasons. */
|
|
27
|
-
export declare function reasons(model: string, catalog: readonly Model[]): boolean;
|
|
28
28
|
/** A model's name as the catalog spells it; Enso for the router. */
|
|
29
29
|
export declare function nameOf(model: string, catalog: readonly Model[]): string;
|
|
30
30
|
/** An id said as a name: its last path part, each word capitalised (`enso-flash` → Enso Flash). */
|
package/lib/mind.js
CHANGED
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
* GET /v1/models the catalog, read once per platform and shared
|
|
6
6
|
* hanzo.mind the person's choice, in this browser: {model, effort}
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* The effort is always under the composer; the model chip only where the host
|
|
9
|
+
* shows it (Hanzo's Advanced settings › model selector, `Host.models`). Enso
|
|
10
|
+
* routes every ask — a message in Chat, a step in Dev — so naming a model is an
|
|
11
|
+
* override, and the panel says so before it offers one. A premium model is
|
|
12
|
+
* picked for the screen it was picked on (`usePick`) and never kept, so the next
|
|
13
|
+
* conversation or run opens on what was kept, Enso until something else is.
|
|
13
14
|
*
|
|
14
|
-
* The efforts are
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* The efforts are Auto, High, XHigh and Max, Chat's words (Hanzo's
|
|
16
|
+
* ThinkingEffort), Auto until another is chosen. A coding run takes low, medium
|
|
17
|
+
* or high (apps/coding `efforts`), so `level` is what a run names: medium for
|
|
18
|
+
* Auto, what a run took before anyone chose, and high for the rest, the most it
|
|
19
|
+
* takes. Auto names one rather than none, because a run that names none takes
|
|
20
|
+
* an effort the person saved to their account once (apps/coding start.go) and
|
|
21
|
+
* no screen shows any more.
|
|
18
22
|
*/
|
|
19
23
|
import { phrase } from '@hanzo/i18n';
|
|
20
24
|
import { parseModels } from '@hanzo/ui/models/catalog';
|
|
@@ -22,13 +26,16 @@ import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
|
22
26
|
import { Refusal } from './api/call.js';
|
|
23
27
|
import { ENSO, models as read } from './api/models.js';
|
|
24
28
|
import { usePick } from './pick.js';
|
|
25
|
-
/** The efforts, in English and marked (`phrase`): the composer draws each word through `t`. */
|
|
29
|
+
/** The efforts, in order, in English and marked (`phrase`): the composer draws each word through `t`. */
|
|
26
30
|
export const EFFORTS = [
|
|
27
|
-
{ id: '
|
|
28
|
-
{ id: '
|
|
29
|
-
{ id: '
|
|
31
|
+
{ id: 'auto', label: phrase('Auto'), note: phrase('Adaptive reasoning based on task complexity') },
|
|
32
|
+
{ id: 'high', label: phrase('High'), note: phrase('Deep reasoning for complex problems') },
|
|
33
|
+
{ id: 'xhigh', label: phrase('XHigh'), note: phrase('Extended multi-step reasoning for hard tasks') },
|
|
34
|
+
{ id: 'max', label: phrase('Max'), note: phrase('Maximum compute and exhaustive verification') },
|
|
30
35
|
];
|
|
31
|
-
|
|
36
|
+
/** The effort a coding run names (apps/coding takes low, medium or high): medium for Auto, high for the rest. */
|
|
37
|
+
export const level = (effort) => (effort === 'auto' ? 'medium' : 'high');
|
|
38
|
+
export const FIRST = { model: ENSO, effort: 'auto' };
|
|
32
39
|
/** What Free answers on: the catalog's free lane, Enso's first. `free` is an alias of it. */
|
|
33
40
|
export const FREE = 'enso-free';
|
|
34
41
|
/** Whether a plan grants only the free lane. An unknown plan is not taken for Free. */
|
|
@@ -49,7 +56,7 @@ let raw;
|
|
|
49
56
|
let held = FIRST;
|
|
50
57
|
/** A choice the browser would not store, held for the page. */
|
|
51
58
|
let memo = null;
|
|
52
|
-
/** The kept choice, Enso and
|
|
59
|
+
/** The kept choice, Enso and Auto where nothing is kept. Stable while the stored text is. Low and Medium, offered once, read as Auto. */
|
|
53
60
|
export function kept() {
|
|
54
61
|
if (memo)
|
|
55
62
|
return memo;
|
|
@@ -74,7 +81,8 @@ export function kept() {
|
|
|
74
81
|
}
|
|
75
82
|
/** Keep a change, in this browser, for every surface that reads it. */
|
|
76
83
|
export function keep(patch) {
|
|
77
|
-
const
|
|
84
|
+
const was = kept();
|
|
85
|
+
const next = { ...was, ...patch, effort: patch.effort === undefined ? was.effort : effortOf(patch.effort) };
|
|
78
86
|
try {
|
|
79
87
|
store()?.setItem(KEY, JSON.stringify(next));
|
|
80
88
|
memo = null;
|
|
@@ -98,13 +106,6 @@ function subscribe(change) {
|
|
|
98
106
|
export function useChoice() {
|
|
99
107
|
return [useSyncExternalStore(subscribe, kept, () => FIRST), keep];
|
|
100
108
|
}
|
|
101
|
-
/** Whether `model` takes an effort: the router and an undescribed model do, a listed one only if it reasons. */
|
|
102
|
-
export function reasons(model, catalog) {
|
|
103
|
-
if (model === ENSO)
|
|
104
|
-
return true;
|
|
105
|
-
const row = catalog.find((m) => m.id === model);
|
|
106
|
-
return !row || row.supports_reasoning === true;
|
|
107
|
-
}
|
|
108
109
|
/** A model's name as the catalog spells it; Enso for the router. */
|
|
109
110
|
export function nameOf(model, catalog) {
|
|
110
111
|
if (model === ENSO)
|
package/lib/project.js
CHANGED
|
@@ -64,7 +64,7 @@ import { useKept, useProjects, useRead, useRun } from './data.js';
|
|
|
64
64
|
import { Merge } from './git.js';
|
|
65
65
|
import { Gutter } from './gutter.js';
|
|
66
66
|
import { useHost, useTarget } from './host.js';
|
|
67
|
-
import { useMind } from './mind.js';
|
|
67
|
+
import { level, useMind } from './mind.js';
|
|
68
68
|
import { Out } from './out.js';
|
|
69
69
|
import { Prompt, useEar } from './prompt.js';
|
|
70
70
|
import { useSpoken } from './voice.js';
|
|
@@ -353,7 +353,7 @@ export function Project({ at }) {
|
|
|
353
353
|
mode,
|
|
354
354
|
// Enso named as itself: a run that names none takes the person's saved coding default.
|
|
355
355
|
model: mind.model,
|
|
356
|
-
effort: mind.effort,
|
|
356
|
+
effort: level(mind.effort),
|
|
357
357
|
});
|
|
358
358
|
setDraft('');
|
|
359
359
|
setPicked([]);
|
|
@@ -424,7 +424,7 @@ export function Project({ at }) {
|
|
|
424
424
|
? t('{repo} is open on {branch}. Say what to change and it gets built there.', { repo: `${named.org}/${named.name}`, branch: home })
|
|
425
425
|
: t('{name} is loaded. Say what to change and it gets built.', { name: slug }) })) : (ordered.map((r) => (_jsxs(YStack, { gap: "$2", children: [_jsx(Turn, { run: r, open: r.id === current, onOpen: () => setChosen(r.id), project: slug }), r.id === current ? (_jsxs(YStack, { gap: "$1", paddingInlineStart: "$2", borderInlineStartWidth: 1, borderColor: "$borderColor", children: [fold(run.events.map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e, run.record?.mode, t) })).filter((b) => b.text)).map((b) => (_jsx(SizableText, { size: "$1", color: "$soft", children: b.text }, b.key))), pr.href ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsxs(Out, { href: pr.href, label: t('Open pull request {pr}', { pr: pr.label }), children: [_jsx(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: t('Pull request {pr}', { pr: pr.label }) }), _jsx(ExternalLink, { size: 11 })] }), _jsx(Merge, { session: r.id, open: proposing, onMerged: merged })] })) : null] })) : null] }, r.id)))) }), _jsxs(YStack, { "data-slot": "project-ask", px: "$3", pb: "$3", gap: "$2", minW: 0, $md: { pr: 0 }, children: [note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, picked.length ? _jsx(Attachments, { items: picked, onRemove: () => setPicked([]) }) : null, attached.length ? (_jsx(XStack, { gap: 6, flexWrap: "wrap", children: _jsx(Files, { files: attached, onFiles: setAttached }) })) : null, _jsx(Prompt, { framed: true, value: draft, onChange: setDraft, onSend: () => void (current && running ? steer(current) : send()), onStop: current
|
|
426
426
|
? () => void stop(target, current).then(() => setNote(t('Stop requested — the run keeps its work on its branch')), (e) => setNote(e.message))
|
|
427
|
-
: undefined, busy: busy || (running && !draft.trim()), placeholder: running ? t('Steer this run') : t('Ask Hanzo for edits'), label: t('Ask Hanzo for edits'), plus: { upload: () => pick(attached, setAttached, setNote, t) }, mode: running ? null : { value: mode === 'plan' ? 'plan' : 'auto', set: (m) => setMode(m === 'plan' ? 'plan' : 'build'), items: ['auto', 'plan'] }, mind: running ? null : mind, voice: voice })] }), wide && !shut ? (_jsx(Gutter, { side: "left", span: across, floor: CHAT_FLOOR, ceil: most, reset: CHAT_WIDTH, onSpan: setSplit, label: t('Resize the chat'), gap: GUTTER })) : null] }));
|
|
427
|
+
: undefined, busy: busy || (running && !draft.trim()), placeholder: running ? t('Steer this run') : t('Ask Hanzo for edits'), label: t('Ask Hanzo for edits'), plus: { upload: () => pick(attached, setAttached, setNote, t) }, mode: running ? null : { value: mode === 'plan' ? 'plan' : 'auto', set: (m) => setMode(m === 'plan' ? 'plan' : 'build'), items: ['auto', 'plan'] }, model: running || !host.models ? null : mind, effort: running ? null : mind, voice: voice })] }), wide && !shut ? (_jsx(Gutter, { side: "left", span: across, floor: CHAT_FLOOR, ceil: most, reset: CHAT_WIDTH, onSpan: setSplit, label: t('Resize the chat'), gap: GUTTER })) : null] }));
|
|
428
428
|
// The preview stays loaded while another view is open, so coming back to it
|
|
429
429
|
// is the page as it was left.
|
|
430
430
|
const preview = (_jsxs(YStack, { "data-slot": "preview", flex: 1, minH: 0, minW: 0, gap: "$2", display: view === 'preview' ? 'flex' : 'none', children: [_jsxs(XStack, { "data-slot": "preview-bar", items: "center", gap: "$1.5", minW: 0, shrink: 0, "$max-md": { px: '$2' }, children: [_jsx(XStack, { render: "button", "aria-label": t('Reload the preview'), onPress: () => frame.current?.reload(), p: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 16 }) }), _jsx(PageSelect, { pages: pages, value: page, onChange: setPage, shrink: 1 }), _jsx(XStack, { flex: 1 }), _jsx(Views, { views: DEVICES, value: device, onChange: (d) => setDevice(d), label: t('Device'), labels: "none" }), src ? (_jsx(Out, { href: src, label: t('Open in a new tab'), children: _jsx(XStack, { p: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 16 }) }) })) : null] }), _jsx(PreviewFrame, { ref: frame, src: src, title: t('{title} preview', { title }), device: device, onBridge: onBridge, empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: stalled
|
package/lib/prompt.d.ts
CHANGED
|
@@ -57,8 +57,10 @@ export interface PromptOptions {
|
|
|
57
57
|
/** Above the frame, at its width: what the draft is sent with. */
|
|
58
58
|
head?: ReactNode;
|
|
59
59
|
field?: ComposerProps['field'];
|
|
60
|
-
/** The model
|
|
61
|
-
|
|
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;
|
|
62
64
|
limits?: PauseSource | null;
|
|
63
65
|
/** A model the plan does not grant was asked for: the way to the plan that does. */
|
|
64
66
|
onUpgrade?: (model: string) => void;
|
|
@@ -94,9 +96,10 @@ export interface PromptOptions {
|
|
|
94
96
|
/** A phone: suggestions go above the frame and every target is 44px. */
|
|
95
97
|
narrow?: boolean;
|
|
96
98
|
}
|
|
97
|
-
/** The row under the frame: where (Dev), then the model, the effort and the mode. */
|
|
98
|
-
export declare function Tune({
|
|
99
|
-
|
|
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;
|
|
100
103
|
mode?: PromptOptions['mode'];
|
|
101
104
|
where?: ReactNode;
|
|
102
105
|
onUpgrade?: (model: string) => void;
|
package/lib/prompt.js
CHANGED
|
@@ -6,10 +6,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* one round button that is voice mode while the draft is empty, send once it
|
|
7
7
|
* has words, and stop while a turn runs. Nothing else is in the frame.
|
|
8
8
|
*
|
|
9
|
-
* Under the frame, at its end, one quiet row: the model
|
|
10
|
-
* the mode as a pill, each opening its choices;
|
|
11
|
-
* and its repository at the row's start (`where`).
|
|
12
|
-
* suggestions (`below`; above the frame on a phone).
|
|
9
|
+
* Under the frame, at its end, one quiet row: the model (only where the host
|
|
10
|
+
* shows it), the effort (dim) and the mode as a pill, each opening its choices;
|
|
11
|
+
* Dev puts where the run goes and its repository at the row's start (`where`).
|
|
12
|
+
* Under that, the person's suggestions (`below`; above the frame on a phone).
|
|
13
13
|
*
|
|
14
14
|
* `/` opens the skills and `@` the plugins, in a list just above the frame.
|
|
15
15
|
*
|
|
@@ -37,7 +37,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
37
37
|
import { ENSO } from './api/models.js';
|
|
38
38
|
import { MODES } from './gear.js';
|
|
39
39
|
import { Dictation } from './mic.js';
|
|
40
|
-
import { EFFORTS, grants, nameOf,
|
|
40
|
+
import { EFFORTS, grants, nameOf, ROUTER_IDS } from './mind.js';
|
|
41
41
|
import { Plus } from './plus.js';
|
|
42
42
|
import { english } from './say.js';
|
|
43
43
|
/** The reading measure: @hanzo/design's prose width, which Width (appearance) retunes. */
|
|
@@ -119,15 +119,14 @@ function Model({ mind, onUpgrade, limits }) {
|
|
|
119
119
|
return (_jsx(DropdownMenuItem, { "data-slot": "model-row", "data-model": m.id, "data-paused": paused ? 'true' : undefined, onSelect: () => (ok ? mind.pick(m.id) : onUpgrade?.(m.id)), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: ok ? '$ink' : '$hint', numberOfLines: 1, flex: 1, children: nameOf(m.id, mind.models) }), ok ? null : (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 6, py: 1, rounded: 6, borderWidth: 1, borderColor: "$borderColor", children: t('Upgrade') })), ok && paused ? (_jsx(SizableText, { fontSize: 12, color: "$hint", children: t('Paused') })) : null, m.id === mind.model ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, m.id));
|
|
120
120
|
})] }, g.key))), more > 0 ? (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 8, py: 8, children: t('Type to search {count} more models', { count: more }) })) : null] })] })] }));
|
|
121
121
|
}
|
|
122
|
-
/** The row under the frame: where (Dev), then the model, the effort and the mode. */
|
|
123
|
-
export function Tune({
|
|
122
|
+
/** The row under the frame: where (Dev), then the model where the host shows it, the effort and the mode. */
|
|
123
|
+
export function Tune({ model, effort, mode, where, onUpgrade, limits, }) {
|
|
124
124
|
const { t } = useI18n();
|
|
125
|
-
const
|
|
126
|
-
const pace = mind ? EFFORTS.find((e) => e.id === mind.effort) : null;
|
|
125
|
+
const pace = effort ? EFFORTS.find((e) => e.id === effort.effort) : null;
|
|
127
126
|
const now = mode ? MODES.find((m) => m.id === mode.value) : undefined;
|
|
128
127
|
const way = now ? t(now.label) : '';
|
|
129
|
-
return (_jsxs(XStack, { "data-slot": "tune", width: "100%", items: "center", gap: 2, flexWrap: "wrap", rowGap: 4, children: [where ? (_jsx(XStack, { items: "center", gap: 2, flexWrap: "wrap", minW: 0, "$max-sm": { width: '100%' }, children: where })) : null, _jsx(XStack, { flex: 1, "$max-sm": { display: 'none' } }), _jsxs(XStack, { items: "center", gap: 2, marginInlineStart: "auto", shrink: 0, children: [
|
|
130
|
-
|
|
128
|
+
return (_jsxs(XStack, { "data-slot": "tune", width: "100%", items: "center", gap: 2, flexWrap: "wrap", rowGap: 4, children: [where ? (_jsx(XStack, { items: "center", gap: 2, flexWrap: "wrap", minW: 0, "$max-sm": { width: '100%' }, children: where })) : null, _jsx(XStack, { flex: 1, "$max-sm": { display: 'none' } }), _jsxs(XStack, { items: "center", gap: 2, marginInlineStart: "auto", shrink: 0, children: [model ? _jsx(Model, { mind: model, onUpgrade: onUpgrade, limits: limits }) : null, effort && pace ? (_jsx(Opens, { label: t('Effort: {level}', { level: t(pace.label) }), word: t(pace.label), dim: true, children: (close) => (_jsx(Choices, { title: t('Effort'), items: EFFORTS, value: effort.effort, onPick: (e) => {
|
|
129
|
+
effort.pace(e);
|
|
131
130
|
close();
|
|
132
131
|
} })) })) : null, mode ? (_jsxs(Popover, { allowFlip: true, placement: "top-end", children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(XStack, { render: "button", "data-slot": "mode", "aria-label": t('Mode: {mode}', { mode: way }), items: "center", height: 24, px: 8, marginInlineStart: 4, rounded: 6, bg: "$hover", cursor: "pointer", hoverStyle: { bg: '$raised' }, focusVisibleStyle: { outlineColor: '$outlineColor', outlineWidth: 2, outlineStyle: 'solid' }, "$max-sm": { height: 44, px: 12 }, children: _jsx(SizableText, { fontSize: 14, color: "$ink", children: way }) }) }), _jsx(PopoverContent, { solid: true, p: 6, width: 288, maxW: "calc(100vw - 24px)", children: _jsx(Choices, { title: t('Mode'), items: mode.items ? MODES.filter((m) => mode.items.includes(m.id)) : MODES, value: mode.value, onPick: mode.set }) })] })) : null] })] }));
|
|
133
132
|
}
|
|
@@ -163,7 +162,7 @@ export function prompt(o, state) {
|
|
|
163
162
|
};
|
|
164
163
|
const mention = listed && shelf && typed ? (_jsx(Mention, { shelf: shelf, rows: listed, index: index, onPick: choose, skills: typed.char === '/' })) : null;
|
|
165
164
|
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;
|
|
166
|
-
const foot = o.
|
|
165
|
+
const foot = o.model || o.effort || o.mode || o.where || (!narrow && o.below) ? (_jsxs(YStack, { ...measure, gap: 10, "data-slot": "prompt-foot", children: [_jsx(Tune, { model: o.model, effort: o.effort, mode: o.mode, where: o.where, onUpgrade: o.onUpgrade, limits: o.limits }), narrow ? null : o.below] })) : undefined;
|
|
167
166
|
const round = (_jsx(Control, { type: "button", size: narrow ? TAP : ROUND, fill: true, rounded: 999, "data-slot": "composer-send", "aria-label": t(busy ? 'Stop' : 'Send'), "aria-disabled": !live || undefined, opacity: live ? 1 : 0.4, onClick: () => {
|
|
168
167
|
if (!live)
|
|
169
168
|
return;
|
package/lib/run.js
CHANGED
|
@@ -41,7 +41,7 @@ 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, 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';
|
|
@@ -206,7 +206,7 @@ export function Run({ id }) {
|
|
|
206
206
|
* itself: a run that names none takes the person's saved coding default
|
|
207
207
|
* (apps/coding start.go), which is not what the composer shows.
|
|
208
208
|
*/
|
|
209
|
-
const tuned = (a) => ({ ...a, model: mind.model, effort: mind.effort });
|
|
209
|
+
const tuned = (a) => ({ ...a, model: mind.model, effort: level(mind.effort) });
|
|
210
210
|
// A setup run's work IS its environment: the panel opens on it.
|
|
211
211
|
useEffect(() => {
|
|
212
212
|
if (mode === 'setup')
|
|
@@ -386,7 +386,7 @@ export function Run({ id }) {
|
|
|
386
386
|
...(busy || waiting ? ['pause', 'resume', 'stop'] : []),
|
|
387
387
|
] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? say('You will be notified') : say('Notify me') })] })) : null] }), _jsx(Prompt, { 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'), head: files.length ? _jsx(XStack, { gap: "$1.5", flexWrap: "wrap", children: _jsx(Files, { files: files, onFiles: setFiles }) }) : undefined, plus: { upload: () => pick(files, setFiles, setNote, say) },
|
|
388
388
|
// 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,
|
|
389
|
+
model: signed && !steering && host.models ? mind : null, effort: signed && !steering ? mind : null, limits: limits, voice: voice })] })] }), desk && !wide ? desk_(true) : null, desk && wide ? (_jsxs(YStack, { "data-slot": "desk", ref: split.aside, ...pane,
|
|
390
390
|
// A pane of its own beside the run's, the gutter between them (useSplit reads it as the inline-start margin).
|
|
391
391
|
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
392
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.83",
|
|
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.",
|