@hanzo/build 0.2.76 → 0.2.78

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.
@@ -35,8 +35,9 @@ export interface Start {
35
35
  }
36
36
  /**
37
37
  * A design system: one of Studio's brands, the one store for them. Its tokens
38
- * are W3C design tokens (color, font, type, space, radius) and two groups of our
39
- * own, `components` and `voice`, each a line a model follows.
38
+ * are CSS custom properties (--background, --primary, --font-sans, --radius-md,
39
+ * …, the form Studio's renderer reads), with `--component-*` and `--voice-*` as
40
+ * quoted lines a model follows (design.ts `tokensOf`).
40
41
  *
41
42
  * GET /v1/studio/brands {data: [brand]} the ones you may read
42
43
  * GET /v1/studio/brands/{id} one
package/lib/carry.js CHANGED
@@ -110,8 +110,7 @@ export function useCarry(t, key, customize, lent, modes = DEV_MODES) {
110
110
  const named = kits.value.find((b) => b.id === design);
111
111
  const designs = {
112
112
  rows: kits.value.map((b) => {
113
- const c = (b.tokens.color ?? {});
114
- return { id: b.id, label: b.name, note: [c.primary?.$value, c.accent?.$value].filter(Boolean).join(' '), icon: _jsx(Palette, { size: 16 }), on: b.id === design };
113
+ return { id: b.id, label: b.name, note: [b.tokens['--primary'], b.tokens['--accent']].filter((x) => typeof x === 'string').join(' '), icon: _jsx(Palette, { size: 16 }), on: b.id === design };
115
114
  }),
116
115
  pick: (id) => gear.set({ design: design === id ? '' : id }),
117
116
  loading: kits.loading,
package/lib/design.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * A design system as the composer handles it: drafted by a model, kept as a
3
- * Studio brand (W3C design tokens plus `components` and `voice`), said to the
3
+ * Studio brand (CSS custom properties, the form Studio's renderer reads), said to the
4
4
  * model on every message of a conversation it is attached to, and drawn as a
5
5
  * page to look at. Pure, so it is tested without a browser.
6
6
  */
@@ -126,8 +126,14 @@ export declare const SCHEMA: {
126
126
  };
127
127
  /** A model's answer as a Draft, or why it is not one. */
128
128
  export declare function drafted(raw: unknown): Draft | string;
129
- /** A Draft as W3C design tokens, with our two groups beside them. */
130
- export declare function tokensOf(d: Draft): Record<string, unknown>;
129
+ /**
130
+ * A Draft as design tokens: CSS custom properties, the names @hanzo/design uses
131
+ * (--background, --foreground, --primary, --font-sans, …). That is the one form
132
+ * Studio's renderer takes (hanzo/render `tokens`: every key `--name`, every value
133
+ * one declaration), so a deck, a document and a page draw from the same map.
134
+ * The components and the voice ride along as quoted strings under their own names.
135
+ */
136
+ export declare function tokensOf(d: Draft): Record<string, string>;
131
137
  /** What a model is told on every message of a conversation the system is attached to. */
132
138
  export declare function said(b: Brand): string;
133
139
  /** The system drawn as a page: its palette, type, a button, a card, an input, its voice. */
package/lib/design.js CHANGED
@@ -54,40 +54,92 @@ export function drafted(raw) {
54
54
  voice: { tone: str(v.tone), do: lines(v.do), dont: lines(v.dont) },
55
55
  };
56
56
  }
57
- /** A Draft as W3C design tokens, with our two groups beside them. */
57
+ /**
58
+ * A Draft as design tokens: CSS custom properties, the names @hanzo/design uses
59
+ * (--background, --foreground, --primary, --font-sans, …). That is the one form
60
+ * Studio's renderer takes (hanzo/render `tokens`: every key `--name`, every value
61
+ * one declaration), so a deck, a document and a page draw from the same map.
62
+ * The components and the voice ride along as quoted strings under their own names.
63
+ */
58
64
  export function tokensOf(d) {
59
- const group = (rec, type, unit = '') => Object.fromEntries(Object.entries(rec).map(([k, v]) => [k, { $value: unit ? `${v}${unit}` : v, $type: type }]));
60
- return {
61
- color: group(d.colors, 'color'),
62
- font: group({ heading: d.fonts.heading, body: d.fonts.body, mono: d.fonts.mono }, 'fontFamily'),
63
- type: group(Object.fromEntries(d.scale.map((n, i) => [`step${i}`, n])), 'dimension', 'px'),
64
- space: group(Object.fromEntries(d.spacing.map((n, i) => [`s${i}`, n])), 'dimension', 'px'),
65
- radius: group(d.radius, 'dimension', 'px'),
66
- components: Object.fromEntries(Object.entries(d.components).map(([k, v]) => [k, { $value: v, $type: 'string' }])),
67
- voice: { tone: { $value: d.voice.tone, $type: 'string' }, do: { $value: d.voice.do, $type: 'string' }, dont: { $value: d.voice.dont, $type: 'string' } },
65
+ // Quoted, with nothing that would make JSON escape it or end the declaration.
66
+ const q = (v) => JSON.stringify(v.replace(/["\\;{}<>\u0000-\u001f]|\/\*/g, ' ').slice(0, 280));
67
+ const c = d.colors;
68
+ const out = {
69
+ '--background': c.background,
70
+ '--foreground': c.text,
71
+ '--primary': c.primary,
68
72
  };
73
+ const more = { '--secondary': c.secondary, '--accent': c.accent, '--card': c.surface, '--muted-foreground': c.muted, '--border': c.border };
74
+ for (const [k, v] of Object.entries(more))
75
+ if (v)
76
+ out[k] = v;
77
+ out['--font-display'] = d.fonts.heading;
78
+ out['--font-sans'] = d.fonts.body;
79
+ out['--font-mono'] = d.fonts.mono;
80
+ for (const [k, v] of Object.entries(d.radius))
81
+ out[`--radius-${k}`] = `${v}px`;
82
+ d.scale.forEach((n, i) => (out[`--text-${i}`] = `${n}px`));
83
+ d.spacing.forEach((n, i) => (out[`--space-${i}`] = `${n}px`));
84
+ for (const [k, v] of Object.entries(d.components))
85
+ out[`--component-${k.replace(/[^a-z0-9-]/gi, '-').toLowerCase()}`] = q(v);
86
+ if (d.voice.tone)
87
+ out['--voice-tone'] = q(d.voice.tone);
88
+ if (d.voice.do.length)
89
+ out['--voice-do'] = q(d.voice.do.join('; ').replace(/;/g, ','));
90
+ if (d.voice.dont.length)
91
+ out['--voice-dont'] = q(d.voice.dont.join('; ').replace(/;/g, ','));
92
+ return out;
69
93
  }
70
- /** One token group as `name value` pairs. */
71
- function pairs(tokens, group) {
72
- return Object.entries(obj(tokens[group])).map(([k, v]) => {
73
- const val = obj(v).$value;
74
- return [k, Array.isArray(val) ? val.join('; ') : String(val ?? '')];
75
- });
94
+ /** A token's value as written, a quoted string unquoted. */
95
+ const value = (v) => {
96
+ const s = typeof v === 'string' ? v : '';
97
+ try {
98
+ return s.startsWith('"') ? String(JSON.parse(s)) : s;
99
+ }
100
+ catch {
101
+ return s;
102
+ }
103
+ };
104
+ /** The tokens whose names start `prefix`, as `name value` pairs, the prefix dropped. */
105
+ function pairs(tokens, prefix) {
106
+ return Object.entries(tokens)
107
+ .filter(([k]) => k.startsWith(prefix))
108
+ .map(([k, v]) => [k.slice(prefix.length), value(v)]);
76
109
  }
110
+ /** The colours by the names a person reads them. */
111
+ const COLOURS = [
112
+ ['primary', '--primary'],
113
+ ['secondary', '--secondary'],
114
+ ['accent', '--accent'],
115
+ ['background', '--background'],
116
+ ['surface', '--card'],
117
+ ['text', '--foreground'],
118
+ ['muted', '--muted-foreground'],
119
+ ['border', '--border'],
120
+ ];
121
+ const colours = (t) => COLOURS.filter(([, k]) => t[k]).map(([n, k]) => [n, value(t[k])]);
77
122
  /** What a model is told on every message of a conversation the system is attached to. */
78
123
  export function said(b) {
79
124
  const t = b.tokens;
80
- const row = (g) => pairs(t, g).map(([k, v]) => `${k} ${v}`).join(', ');
125
+ const row = (list) => list.map(([k, v]) => `${k} ${v}`).join(', ');
126
+ const fonts = [
127
+ ['heading', value(t['--font-display'])],
128
+ ['body', value(t['--font-sans'])],
129
+ ['mono', value(t['--font-mono'])],
130
+ ].filter(([, v]) => v);
131
+ const comps = pairs(t, '--component-');
132
+ const voice = [value(t['--voice-tone']), value(t['--voice-do']) && `do: ${value(t['--voice-do'])}`, value(t['--voice-dont']) && `don't: ${value(t['--voice-dont'])}`].filter(Boolean);
81
133
  const parts = [
82
134
  `Design system "${b.name}" is attached to this conversation. Everything you make — pages, apps, components, slides, images and copy — follows it exactly; never invent other colours, fonts or radii.`,
83
- row('color') && `Colours: ${row('color')}.`,
84
- row('font') && `Fonts: ${row('font')}.`,
85
- row('type') && `Type scale: ${row('type')}.`,
86
- row('space') && `Spacing: ${row('space')}.`,
87
- row('radius') && `Radius: ${row('radius')}.`,
88
- row('components') && `Components: ${pairs(t, 'components').map(([k, v]) => `${k} — ${v}`).join(' ')}`,
89
- row('voice') && `Voice: ${pairs(t, 'voice').map(([k, v]) => `${k}: ${v}`).join(' | ')}`,
90
- 'In HTML, set these as CSS custom properties on :root and use them; load the fonts from Google Fonts when they are web fonts.',
135
+ colours(t).length && `Colours: ${row(colours(t))}.`,
136
+ fonts.length && `Fonts: ${row(fonts)}.`,
137
+ pairs(t, '--text-').length && `Type scale: ${row(pairs(t, '--text-'))}.`,
138
+ pairs(t, '--space-').length && `Spacing: ${row(pairs(t, '--space-'))}.`,
139
+ pairs(t, '--radius-').length && `Radius: ${row(pairs(t, '--radius-'))}.`,
140
+ comps.length && `Components: ${comps.map(([k, v]) => `${k} — ${v}`).join(' ')}`,
141
+ voice.length && `Voice: ${voice.join(' | ')}`,
142
+ `In HTML, declare these exact custom properties on :root and use them: ${Object.keys(t).filter((k) => !k.startsWith('--component-') && !k.startsWith('--voice-')).join(', ')}. Load the fonts from Google Fonts when they are web fonts.`,
91
143
  ];
92
144
  return parts.filter(Boolean).join('\n');
93
145
  }
@@ -95,29 +147,29 @@ const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>'
95
147
  /** The system drawn as a page: its palette, type, a button, a card, an input, its voice. */
96
148
  export function preview(b) {
97
149
  const t = b.tokens;
98
- const c = Object.fromEntries(pairs(t, 'color'));
99
- const f = Object.fromEntries(pairs(t, 'font'));
100
- const r = Object.fromEntries(pairs(t, 'radius'));
101
- const sizes = pairs(t, 'type').map(([, v]) => v);
102
- const voice = Object.fromEntries(pairs(t, 'voice'));
103
- const comps = pairs(t, 'components');
104
- const fonts = [f.heading, f.body].filter((x) => x && !/^ui-|system|sans-serif|serif$/i.test(x));
150
+ const v = (k, d) => value(t[k]) || d;
151
+ const sizes = pairs(t, '--text-').map(([, x]) => x);
152
+ const comps = pairs(t, '--component-');
153
+ const heading = v('--font-display', 'Inter');
154
+ const body = v('--font-sans', 'Inter');
155
+ const fonts = [heading, body].filter((x) => x && !/^ui-|system|sans-serif|serif$/i.test(x));
105
156
  const link = fonts.length ? `<link rel="stylesheet" href="https://fonts.googleapis.com/css2?${fonts.map((x) => `family=${encodeURIComponent(x)}:wght@400;600;700`).join('&')}&display=swap">` : '';
106
- const swatches = Object.entries(c).map(([k, v]) => `<div class="sw"><span style="background:${esc(v)}"></span><b>${esc(k)}</b><code>${esc(v)}</code></div>`).join('');
107
- const scale = sizes.map((s) => `<div style="font-size:${esc(s)}">${esc(b.name)} — ${esc(s)}</div>`).join('');
157
+ const swatches = colours(t).map(([k, c]) => `<div class="sw"><span style="background:${esc(c)}"></span><b>${esc(k)}</b><code>${esc(c)}</code></div>`).join('');
158
+ const scale = sizes.map((x) => `<div style="font-size:${esc(x)}">${esc(b.name)} — ${esc(x)}</div>`).join('');
159
+ const root = Object.entries(t).filter(([k]) => !k.startsWith('--component-') && !k.startsWith('--voice-')).map(([k, x]) => `${k}:${value(x)}`).join(';');
108
160
  return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(b.name)}</title>${link}
109
- <style>:root{--primary:${c.primary};--secondary:${c.secondary ?? c.primary};--accent:${c.accent ?? c.primary};--bg:${c.background};--surface:${c.surface ?? c.background};--text:${c.text};--muted:${c.muted ?? c.text};--border:${c.border ?? c.muted ?? c.text};--r-sm:${r.sm ?? '4px'};--r-md:${r.md ?? '8px'};--r-lg:${r.lg ?? '16px'}}
110
- body{margin:0;background:var(--bg);color:var(--text);font-family:'${esc(f.body ?? 'Inter')}',system-ui,sans-serif;padding:40px;line-height:1.5}
111
- h1,h2{font-family:'${esc(f.heading ?? 'Inter')}',system-ui,sans-serif;margin:0 0 12px}h2{margin-top:40px;font-size:14px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
112
- .row{display:flex;flex-wrap:wrap;gap:16px}.sw{display:flex;flex-direction:column;gap:4px;width:120px}.sw span{height:64px;border-radius:var(--r-md);border:1px solid var(--border)}.sw code{color:var(--muted);font-size:12px}
113
- .btn{background:var(--primary);color:var(--bg);border:0;border-radius:var(--r-md);padding:12px 20px;font:inherit;font-weight:600}.btn.ghost{background:transparent;color:var(--text);border:1px solid var(--border)}
114
- .card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:20px;max-width:320px}input{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:var(--r-sm);padding:10px 12px;font:inherit;width:260px}
115
- li{color:var(--muted)}</style></head><body>
116
- <h1 style="font-size:40px">${esc(b.name)}</h1><p style="color:var(--muted)">${esc(voice.tone ?? '')}</p>
161
+ <style>:root{${root}}
162
+ body{margin:0;background:var(--background);color:var(--foreground);font-family:'${esc(body)}',system-ui,sans-serif;padding:40px;line-height:1.5}
163
+ h1,h2{font-family:'${esc(heading)}',system-ui,sans-serif;margin:0 0 12px}h2{margin-top:40px;font-size:14px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted-foreground,var(--foreground))}
164
+ .row{display:flex;flex-wrap:wrap;gap:16px}.sw{display:flex;flex-direction:column;gap:4px;width:120px}.sw span{height:64px;border-radius:var(--radius-md,8px);border:1px solid var(--border,var(--foreground))}.sw code{color:var(--muted-foreground);font-size:12px}
165
+ .btn{background:var(--primary);color:var(--background);border:0;border-radius:var(--radius-md,8px);padding:12px 20px;font:inherit;font-weight:600}.btn.ghost{background:transparent;color:var(--foreground);border:1px solid var(--border,var(--foreground))}
166
+ .card{background:var(--card,var(--background));border:1px solid var(--border,var(--foreground));border-radius:var(--radius-lg,16px);padding:20px;max-width:320px}input{background:var(--card,var(--background));color:var(--foreground);border:1px solid var(--border,var(--foreground));border-radius:var(--radius-sm,4px);padding:10px 12px;font:inherit;width:260px}
167
+ li{color:var(--muted-foreground)}</style></head><body>
168
+ <h1 style="font-size:40px">${esc(b.name)}</h1><p style="color:var(--muted-foreground)">${esc(value(t['--voice-tone']))}</p>
117
169
  <h2>Colour</h2><div class="row">${swatches}</div>
118
170
  <h2>Type</h2>${scale}
119
- <h2>Components</h2><div class="row" style="align-items:flex-start"><button class="btn">Primary action</button><button class="btn ghost">Secondary</button><input placeholder="An input"><div class="card"><b>A card</b><p style="color:var(--muted);margin:8px 0 0">${esc(comps.find(([k]) => k === 'card')?.[1] ?? '')}</p></div></div>
120
- <ul>${comps.map(([k, v]) => `<li><b style="color:var(--text)">${esc(k)}</b> — ${esc(v)}</li>`).join('')}</ul>
121
- <h2>Voice</h2><p><b>Do</b>: ${esc(voice.do ?? '')}</p><p><b>Don't</b>: ${esc(voice.dont ?? '')}</p>
171
+ <h2>Components</h2><div class="row" style="align-items:flex-start"><button class="btn">Primary action</button><button class="btn ghost">Secondary</button><input placeholder="An input"><div class="card"><b>A card</b><p style="color:var(--muted-foreground);margin:8px 0 0">${esc(comps.find(([k]) => k === 'card')?.[1] ?? '')}</p></div></div>
172
+ <ul>${comps.map(([k, x]) => `<li><b style="color:var(--foreground)">${esc(k)}</b> — ${esc(x)}</li>`).join('')}</ul>
173
+ <h2>Voice</h2><p><b>Do</b>: ${esc(value(t['--voice-do']))}</p><p><b>Don't</b>: ${esc(value(t['--voice-dont']))}</p>
122
174
  </body></html>`;
123
175
  }
package/lib/prompt.d.ts CHANGED
@@ -94,11 +94,12 @@ export interface PromptOptions {
94
94
  narrow?: boolean;
95
95
  }
96
96
  /** The row under the frame: where (Dev), then the model, the effort and the mode. */
97
- export declare function Tune({ mind, mode, where, onUpgrade }: {
97
+ export declare function Tune({ mind, mode, where, onUpgrade, limits }: {
98
98
  mind?: Mind | null;
99
99
  mode?: PromptOptions['mode'];
100
100
  where?: ReactNode;
101
101
  onUpgrade?: (model: string) => void;
102
+ limits?: PauseSource | null;
102
103
  }): import("react").JSX.Element;
103
104
  /** The `/` or `@` the caret ends on, and the words typed after it. */
104
105
  export declare function trigger(value: string): {
package/lib/prompt.js CHANGED
@@ -23,7 +23,7 @@ import { ArrowUp, AudioLines, Check, Square, X } from '@hanzogui/lucide-icons-2'
23
23
  import { Control } from '@hanzo/composer';
24
24
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSearch, DropdownMenuTrigger, Popover, PopoverContent, PopoverTrigger } from '@hanzo/ui';
25
25
  import { Composer } from '@hanzo/ui/chat';
26
- import { groupModels, matchesModel } from '@hanzo/ui/models';
26
+ import { groupModels, isPaused, matchesModel } from '@hanzo/ui/models';
27
27
  import { speech, Voice } from '@hanzo/voice';
28
28
  import { useEffect, useMemo, useRef, useState } from 'react';
29
29
  import { ENSO } from './api/models.js';
@@ -81,7 +81,7 @@ function Opens({ label, word, dim, children }) {
81
81
  return (_jsxs(Popover, { open: open, onOpenChange: setOpen, allowFlip: true, placement: "top-end", children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(XStack, { children: _jsx(Word, { label: label, dim: dim, open: open, children: word }) }) }), _jsx(PopoverContent, { solid: true, p: 6, width: 288, maxW: "calc(100vw - 24px)", children: children(() => setOpen(false)) })] }));
82
82
  }
83
83
  /** The model's name, and the catalog behind it: Free's lane to pick from, the rest an upgrade. */
84
- function Model({ mind, onUpgrade }) {
84
+ function Model({ mind, onUpgrade, limits }) {
85
85
  const [open, setOpen] = useState(false);
86
86
  const [q, setQ] = useState('');
87
87
  // The catalog is hundreds long, and drawing all of it is what made the menu slow
@@ -103,14 +103,16 @@ function Model({ mind, onUpgrade }) {
103
103
  const routed = mind.model === ENSO;
104
104
  return (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, placement: "top-end", children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(XStack, { "data-slot": "mind", children: _jsx(Word, { label: `Model: ${nameOf(mind.model, mind.models)}`, open: open, children: nameOf(mind.model, mind.models) }) }) }), _jsxs(DropdownMenuContent, { align: "end", minW: 280, "data-slot": "model-menu", children: [_jsx(DropdownMenuSearch, { value: q, onValueChange: setQ, placeholder: "Search models" }), _jsxs(YStack, { maxH: "min(420px, 60vh)", overflowY: "auto", overflowX: "hidden", "data-slot": "model-rows", children: [!mind.free && !q.trim() ? (_jsx(DropdownMenuItem, { onSelect: () => mind.pick(ENSO), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsxs(YStack, { flex: 1, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", children: "Enso" }), _jsx(SizableText, { fontSize: 12, color: "$hint", children: "Picks the model for each message and step" })] }), routed ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) })) : null, groups.map((g) => (_jsxs(YStack, { role: "group", "aria-label": g.label, children: [_jsx(SizableText, { fontSize: 12, color: "$hint", px: 8, pt: 8, pb: 2, children: g.label }), g.models.map((m) => {
105
105
  const ok = grants(mind.free, m.id, mind.models);
106
- return (_jsx(DropdownMenuItem, { "data-slot": "model-row", onSelect: () => (ok ? mind.pick(m.id) : onUpgrade?.(m.id)), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: ok ? '$ink' : '$hint', numberOfLines: 1, flex: 1, children: nameOf(m.id, mind.models) }), ok ? null : (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 6, py: 1, rounded: 6, borderWidth: 1, borderColor: "$borderColor", children: "Upgrade" })), m.id === mind.model ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, m.id));
106
+ // Paused by the plan's usage: still listed and still picked; the gateway answers from Enso.
107
+ const paused = isPaused(m, limits);
108
+ 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: "Upgrade" })), ok && paused ? (_jsx(SizableText, { fontSize: 12, color: "$hint", children: "Paused" })) : null, m.id === mind.model ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, m.id));
107
109
  })] }, g.key))), more > 0 ? (_jsx(SizableText, { fontSize: 12, color: "$hint", px: 8, py: 8, children: `Type to search ${more} more models` })) : null] })] })] }));
108
110
  }
109
111
  /** The row under the frame: where (Dev), then the model, the effort and the mode. */
110
- export function Tune({ mind, mode, where, onUpgrade }) {
112
+ export function Tune({ mind, mode, where, onUpgrade, limits }) {
111
113
  const thinks = mind ? reasons(mind.model, mind.models) : false;
112
114
  const pace = mind ? EFFORTS.find((e) => e.id === mind.effort) : null;
113
- 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, ml: "auto", shrink: 0, children: [mind ? _jsx(Model, { mind: mind, onUpgrade: onUpgrade }) : null, mind && thinks && pace ? (_jsx(Opens, { label: `Effort: ${pace.label}`, word: pace.label, dim: true, children: (close) => (_jsx(Choices, { title: "Effort", items: EFFORTS, value: mind.effort, onPick: (e) => {
115
+ 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, ml: "auto", shrink: 0, children: [mind ? _jsx(Model, { mind: mind, onUpgrade: onUpgrade, limits: limits }) : null, mind && thinks && pace ? (_jsx(Opens, { label: `Effort: ${pace.label}`, word: pace.label, dim: true, children: (close) => (_jsx(Choices, { title: "Effort", items: EFFORTS, value: mind.effort, onPick: (e) => {
114
116
  mind.pace(e);
115
117
  close();
116
118
  } })) })) : null, mode ? (_jsxs(Popover, { allowFlip: true, placement: "top-end", children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(XStack, { render: "button", "data-slot": "mode", "aria-label": `Mode: ${MODES.find((m) => m.id === mode.value)?.label}`, items: "center", height: 24, px: 8, ml: 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: MODES.find((m) => m.id === mode.value)?.label }) }) }), _jsx(PopoverContent, { solid: true, p: 6, width: 288, maxW: "calc(100vw - 24px)", children: _jsx(Choices, { title: "Mode", items: mode.items ? MODES.filter((m) => mode.items.includes(m.id)) : MODES, value: mode.value, onPick: mode.set }) })] })) : null] })] }));
@@ -144,7 +146,7 @@ export function prompt(o, state) {
144
146
  };
145
147
  const mention = listed && shelf && typed ? (_jsx(Mention, { shelf: shelf, rows: listed, index: index, onPick: choose, tag: typed.char === '/' ? 'Skill' : 'Plugin', name: typed.char === '/' ? 'Skills' : 'Plugins' })) : null;
146
148
  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;
147
- const foot = o.mind || o.mode || o.where || (!narrow && o.below) ? (_jsxs(YStack, { ...measure, gap: 10, "data-slot": "prompt-foot", children: [_jsx(Tune, { mind: o.mind, mode: o.mode, where: o.where, onUpgrade: o.onUpgrade }), narrow ? null : o.below] })) : undefined;
149
+ const foot = o.mind || o.mode || o.where || (!narrow && o.below) ? (_jsxs(YStack, { ...measure, gap: 10, "data-slot": "prompt-foot", children: [_jsx(Tune, { mind: o.mind, mode: o.mode, where: o.where, onUpgrade: o.onUpgrade, limits: o.limits }), narrow ? null : o.below] })) : undefined;
148
150
  const round = (_jsx(Control, { type: "button", size: narrow ? TAP : ROUND, fill: true, rounded: 999, "data-slot": "composer-send", "aria-label": busy ? 'Stop' : 'Send', "aria-disabled": !live || undefined, opacity: live ? 1 : 0.4, onClick: () => {
149
151
  if (!live)
150
152
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.76",
3
+ "version": "0.2.78",
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.",