@hanzo/build 0.2.51 → 0.2.53
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 +109 -55
- package/lib/api/billing.d.ts +39 -0
- package/lib/api/billing.js +41 -0
- package/lib/api/coding.d.ts +4 -1
- package/lib/api/coding.js +2 -0
- package/lib/api/environment.d.ts +12 -0
- package/lib/api/environment.js +39 -1
- package/lib/api/github.d.ts +42 -4
- package/lib/api/github.js +79 -16
- package/lib/api/limits.d.ts +69 -0
- package/lib/api/limits.js +86 -0
- package/lib/api/links.d.ts +42 -0
- package/lib/api/links.js +65 -0
- package/lib/api/pref.d.ts +5 -3
- package/lib/api/pref.js +0 -3
- package/lib/api/turn.d.ts +10 -1
- package/lib/api/turn.js +16 -4
- package/lib/api/work.d.ts +75 -24
- package/lib/api/work.js +132 -27
- package/lib/builder.d.ts +0 -10
- package/lib/builder.js +21 -54
- package/lib/choice.d.ts +21 -0
- package/lib/choice.js +40 -1
- package/lib/customize/agents.d.ts +1 -1
- package/lib/customize/agents.js +21 -19
- package/lib/customize/connectors.d.ts +1 -1
- package/lib/customize/connectors.js +80 -31
- package/lib/customize/index.d.ts +2 -0
- package/lib/customize/index.js +23 -19
- package/lib/customize/load.d.ts +9 -0
- package/lib/customize/load.js +22 -0
- package/lib/customize/plugins.d.ts +1 -1
- package/lib/customize/plugins.js +21 -19
- package/lib/customize/say.d.ts +1 -0
- package/lib/customize/say.js +38 -0
- package/lib/customize/skills.d.ts +1 -1
- package/lib/customize/skills.js +23 -17
- package/lib/customize/ui.d.ts +28 -11
- package/lib/customize/ui.js +29 -21
- package/lib/data.d.ts +13 -0
- package/lib/data.js +70 -0
- package/lib/desk.d.ts +6 -1
- package/lib/desk.js +43 -19
- package/lib/environment.d.ts +11 -3
- package/lib/environment.js +53 -13
- package/lib/foot.js +10 -6
- package/lib/forge.js +255 -92
- package/lib/host.d.ts +1 -1
- package/lib/index.d.ts +28 -2
- package/lib/index.js +26 -2
- package/lib/landing.d.ts +0 -10
- package/lib/landing.js +79 -47
- package/lib/merge.d.ts +223 -0
- package/lib/merge.js +380 -0
- package/lib/meter.d.ts +31 -0
- package/lib/meter.js +45 -0
- package/lib/mind.d.ts +60 -0
- package/lib/mind.js +187 -0
- package/lib/nav.d.ts +11 -0
- package/lib/nav.js +75 -0
- package/lib/panel.d.ts +45 -0
- package/lib/panel.js +115 -0
- package/lib/pick.d.ts +8 -0
- package/lib/pick.js +18 -1
- package/lib/plan.d.ts +78 -0
- package/lib/plan.js +133 -0
- package/lib/project.d.ts +16 -1
- package/lib/project.js +100 -32
- package/lib/prompt.d.ts +75 -0
- package/lib/prompt.js +125 -0
- package/lib/prose.js +14 -10
- package/lib/reply.d.ts +36 -0
- package/lib/reply.js +124 -0
- package/lib/route.d.ts +28 -4
- package/lib/route.js +48 -1
- package/lib/run.js +87 -52
- package/lib/section.d.ts +1 -1
- package/lib/section.js +7 -11
- package/lib/settings/billing.js +16 -9
- package/lib/settings/code.js +15 -12
- package/lib/settings/credits.d.ts +9 -0
- package/lib/settings/credits.js +110 -0
- package/lib/settings/plan.d.ts +17 -0
- package/lib/settings/plan.js +108 -0
- package/lib/settings/usage.js +11 -119
- package/lib/shelf.js +3 -3
- package/lib/standing.d.ts +44 -0
- package/lib/standing.js +64 -0
- package/lib/sync.js +23 -9
- package/lib/tabs.d.ts +53 -0
- package/lib/tabs.js +197 -0
- package/lib/tools.d.ts +2 -9
- package/lib/tools.js +20 -25
- package/lib/transcript.d.ts +10 -1
- package/lib/transcript.js +25 -10
- package/lib/voice.d.ts +2 -13
- package/lib/voice.js +7 -73
- package/package.json +20 -3
package/lib/run.js
CHANGED
|
@@ -16,45 +16,46 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
16
16
|
* (apps/coding steer.go), so what continues it is a new run: a paused run is
|
|
17
17
|
* carried on, and a finished one followed up, by `POST /v1/agent/coding` from
|
|
18
18
|
* the branch its work was kept on — the platform's own continue, done here —
|
|
19
|
-
* with the model and effort the composer
|
|
19
|
+
* with the model and effort the composer shows (mind.tsx), which Chat and New share.
|
|
20
20
|
*
|
|
21
21
|
* It reads as a chat: the person's ask opens it, each thing the agent said can
|
|
22
22
|
* be copied and judged (a verdict is `build.verdict` on the event bus, as a
|
|
23
23
|
* project's turns send it), and the title is the menu of what can be done to
|
|
24
24
|
* the run. Share opens its story to the public build route.
|
|
25
25
|
*/
|
|
26
|
-
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
27
|
-
import { ChevronDown, ChevronRight, Copy, ExternalLink, GitPullRequest, Globe,
|
|
26
|
+
import { SizableText, useMedia, XStack, YStack } from '@hanzo/gui';
|
|
27
|
+
import { ChevronDown, ChevronRight, Copy, ExternalLink, GitPullRequest, Globe, Pencil, Share } from '@hanzogui/lucide-icons-2';
|
|
28
28
|
import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from '@hanzo/ui';
|
|
29
29
|
import { Steer } from '@hanzo/ui/agents';
|
|
30
|
-
import {
|
|
31
|
-
import { ChipSelect } from '@hanzo/ui/product';
|
|
32
|
-
import { ModelPicker } from '@hanzo/ui/models';
|
|
30
|
+
import { Failure } from '@hanzo/ui/chat';
|
|
33
31
|
import { useLimits } from '@hanzo/ui/product/useLimits';
|
|
32
|
+
import { useDictation, useVoice } from '@hanzo/voice';
|
|
34
33
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
35
34
|
import { approve, followUp, headline, retry, start } from './api/coding.js';
|
|
36
|
-
import {
|
|
37
|
-
import { usePick } from './pick.js';
|
|
35
|
+
import { limits as readLimits } from './api/models.js';
|
|
38
36
|
import { list, message, pause, publish, rename, resume, stop, story, took } from './api/sessions.js';
|
|
39
37
|
import { answer, cards, outcome, pull, settled, steps } from './api/turn.js';
|
|
40
38
|
import { verdict } from './api/verdict.js';
|
|
41
39
|
import { useKept, useProjects, useRead, useRun } from './data.js';
|
|
42
|
-
import { Desk } from './desk.js';
|
|
40
|
+
import { Desk, SEED } from './desk.js';
|
|
43
41
|
import { Grip } from './grip.js';
|
|
44
42
|
import { useHost, useTarget } from './host.js';
|
|
45
|
-
import {
|
|
43
|
+
import { useMind } from './mind.js';
|
|
46
44
|
import { Out } from './out.js';
|
|
47
45
|
import { gap, pane } from './pane.js';
|
|
48
|
-
import {
|
|
46
|
+
import { measure, Prompt, useEar } from './prompt.js';
|
|
47
|
+
import { useSpoken } from './voice.js';
|
|
48
|
+
import { speaker } from './reply.js';
|
|
49
|
+
import { PanelToggle } from './panel.js';
|
|
50
|
+
import { PAGE, useDeck, useKey, usePanel } from './tabs.js';
|
|
51
|
+
import { compose, Files, pick } from './tools.js';
|
|
49
52
|
import { Transcript } from './transcript.js';
|
|
50
53
|
const LIVE = new Set(['running', 'paused', '']);
|
|
51
|
-
|
|
52
|
-
const text = (v) => (typeof v === 'string' ? v : '');
|
|
53
|
-
/** The side pane's width, px: its default, the floor a drag holds, and what the transcript keeps beside it. */
|
|
54
|
+
/** The side panel's width, px: its default, the floor a drag holds, and what the transcript keeps beside it. */
|
|
54
55
|
const DESK_WIDTH = 480;
|
|
55
56
|
const DESK_FLOOR = 240;
|
|
56
57
|
const TALK_FLOOR = 300;
|
|
57
|
-
/** The most the side
|
|
58
|
+
/** The most the side panel is drawn at before its row is measured, px. */
|
|
58
59
|
const DESK_CEIL = 960;
|
|
59
60
|
/**
|
|
60
61
|
* How wide an element is drawn, px, read before the first paint and again as it
|
|
@@ -82,17 +83,22 @@ export function Run({ id }) {
|
|
|
82
83
|
const signed = Boolean(host.person);
|
|
83
84
|
const { detail, record, events, status, refused } = useRun(t, signed ? id : null);
|
|
84
85
|
const [draft, setDraft] = useState('');
|
|
86
|
+
const [files, setFiles] = useState([]);
|
|
85
87
|
const [note, setNote] = useState('');
|
|
86
88
|
const [busy, setBusy] = useState(false);
|
|
87
89
|
const [notify, setNotify] = useState(false);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
// The side panel: open or shut as Dev was left, its tabs as they were arranged.
|
|
91
|
+
const wide = useMedia().md;
|
|
92
|
+
const [desk, setDesk] = usePanel('dev', true, wide);
|
|
93
|
+
const side = useDeck('dev', SEED);
|
|
94
|
+
useKey(() => setDesk(!desk));
|
|
95
|
+
// The side panel's width is the reader's, kept, and gives way so the
|
|
90
96
|
// transcript keeps its floor: bounded by the row it is drawn in once that is
|
|
91
97
|
// measured, and by its own `maxWidth` until then.
|
|
92
|
-
const [
|
|
93
|
-
const [room,
|
|
98
|
+
const [reach, setReach] = useKept('hanzo.build.desk.span', DESK_WIDTH);
|
|
99
|
+
const [room, size] = useWidth();
|
|
94
100
|
const most = room ? Math.max(DESK_FLOOR, room - TALK_FLOOR) : DESK_CEIL;
|
|
95
|
-
const across = Math.round(Math.min(most, Math.max(DESK_FLOOR, typeof
|
|
101
|
+
const across = Math.round(Math.min(most, Math.max(DESK_FLOOR, typeof reach === 'number' ? reach : DESK_WIDTH)));
|
|
96
102
|
// A drag repaints the pane's width directly and tells React once, on letting go.
|
|
97
103
|
const deskBox = useRef(null);
|
|
98
104
|
// Stop was pressed here: the run keeps its work after it answers, and says so with its status.
|
|
@@ -101,13 +107,11 @@ export function Run({ id }) {
|
|
|
101
107
|
const [name, setName] = useState('');
|
|
102
108
|
const [copied, setCopied] = useState('');
|
|
103
109
|
const [unfolded, setUnfolded] = useState(false);
|
|
104
|
-
// The model and effort a run started here uses:
|
|
105
|
-
const
|
|
106
|
-
const [chose, choose] = useKept(`hanzo.build.new.${host.org ?? 'none'}`, null);
|
|
107
|
-
const catalog = useRead(signed ? () => models(t) : null, [], [t, signed]);
|
|
110
|
+
// The model and effort a run started here uses: the one choice Chat and New share.
|
|
111
|
+
const mind = useMind(t);
|
|
108
112
|
const { limits } = useLimits(signed ? (signal) => readLimits(t, signal) : null, t);
|
|
109
|
-
const
|
|
110
|
-
const
|
|
113
|
+
const voice = useVoice({ speech: useEar(t), language: useSpoken(), ...useDictation(draft, setDraft) });
|
|
114
|
+
const listen = useMemo(() => speaker(t), [t]);
|
|
111
115
|
const mode = record?.mode ?? '';
|
|
112
116
|
// A run that narrated no ask of its own opens with the one its title records, once it has said anything.
|
|
113
117
|
const shown = useMemo(() => {
|
|
@@ -128,6 +132,15 @@ export function Run({ id }) {
|
|
|
128
132
|
if (end.published)
|
|
129
133
|
rebuilt();
|
|
130
134
|
}, [end.published, rebuilt]);
|
|
135
|
+
// What a run publishes opens in the panel: the Preview, chosen and shown.
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!end.published)
|
|
138
|
+
return;
|
|
139
|
+
side.open({ kind: 'preview', title: 'Preview' });
|
|
140
|
+
setDesk(true);
|
|
141
|
+
// On each publish, not on each render.
|
|
142
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
143
|
+
}, [end.published]);
|
|
131
144
|
const site = built.value.find((p) => p.slug === record?.project)?.live ?? '';
|
|
132
145
|
const pr = pull(record?.pr ?? '', record?.repo ?? '');
|
|
133
146
|
const live = LIVE.has(state);
|
|
@@ -207,8 +220,24 @@ export function Run({ id }) {
|
|
|
207
220
|
setBusy(false);
|
|
208
221
|
}
|
|
209
222
|
};
|
|
210
|
-
/**
|
|
211
|
-
|
|
223
|
+
/**
|
|
224
|
+
* A new run's ask with the model and effort the composer shows, Enso named as
|
|
225
|
+
* itself: a run that names none takes the person's saved coding default
|
|
226
|
+
* (apps/coding start.go), which is not what the composer shows.
|
|
227
|
+
*/
|
|
228
|
+
const tuned = (a) => ({ ...a, model: mind.model, effort: mind.effort });
|
|
229
|
+
// A setup run's work IS its environment: the panel opens on it.
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (mode === 'setup')
|
|
232
|
+
side.open({ kind: 'environment', title: 'Environment' });
|
|
233
|
+
// Once the run says what it is.
|
|
234
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
235
|
+
}, [mode]);
|
|
236
|
+
/** An answer's artifact, open in the panel as a page rendered from its own bytes. */
|
|
237
|
+
const look = (a) => {
|
|
238
|
+
side.open({ kind: PAGE, title: a.name, body: a.body, type: a.mime });
|
|
239
|
+
setDesk(true);
|
|
240
|
+
};
|
|
212
241
|
/**
|
|
213
242
|
* A new run from this one's work, opened. A paused run it carries on is let go
|
|
214
243
|
* first: the new run works in the same sandbox, where nothing else may be at work.
|
|
@@ -221,16 +250,18 @@ export function Run({ id }) {
|
|
|
221
250
|
const ask = how === 'approve' ? approve(earlier, said) : how === 'retry' ? retry(earlier, said) : followUp(earlier, said);
|
|
222
251
|
const run = await start(t, tuned(ask));
|
|
223
252
|
setDraft('');
|
|
253
|
+
setFiles([]);
|
|
224
254
|
host.go(run.session);
|
|
225
255
|
return '';
|
|
226
256
|
};
|
|
227
|
-
// The composer sends
|
|
257
|
+
// The composer sends words, with any files fenced after them, and only while it is enabled.
|
|
228
258
|
const send = () => {
|
|
229
|
-
const words = draft.trim();
|
|
259
|
+
const words = compose(draft.trim(), files);
|
|
230
260
|
if (steering) {
|
|
231
261
|
void act(async () => {
|
|
232
262
|
await message(t, id, words);
|
|
233
263
|
setDraft('');
|
|
264
|
+
setFiles([]);
|
|
234
265
|
return 'Sent — recorded on this run';
|
|
235
266
|
});
|
|
236
267
|
return;
|
|
@@ -325,9 +356,11 @@ export function Run({ id }) {
|
|
|
325
356
|
: finished
|
|
326
357
|
? 'Follow up — continues in a new run'
|
|
327
358
|
: 'Reading this run…';
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
359
|
+
const desk_ = (sheet) => (_jsx(Desk, { id: id, repo: record?.repo ?? '', branch: record?.branch ?? '', base: record?.base ?? '', environment: record?.environment ?? '', mode: mode, pr: pr, title: title, project: record?.project ?? '', sandbox: record?.sandbox ?? '', site: site, published: end.published, events: events, live: running, menu: [...manage, ...sharing], refused: signed ? refused || (detail.error ? detail.error.message : '') : 'Sign in to follow this run.', retry: signed ? 'Retry' : 'Sign in', onRetry: signed ? detail.reload : () => host.signIn?.(), side: side, sheet: sheet, onHide: () => setDesk(false) }));
|
|
360
|
+
return (_jsxs(XStack, { ref: size, "data-slot": "run-split", flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(YStack, { "data-slot": "run", ...pane, flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(XStack, { px: "$4", pt: "$3", pb: "$2", gap: "$2", items: "center", minH: 44, children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": "Manage this run", items: "center", gap: "$1.5", maxW: "100%", px: "$1.5", mx: "$-1.5", py: "$0.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { render: "h1", size: "$5", color: "$ink", numberOfLines: 1, minW: 0, children: title }), _jsx(ChevronDown, { size: 14 })] }), items: manage }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, maxW: "100%", children: [state || (detail.loading ? 'reading' : ''), record?.repo, record?.branch, record?.published ? 'shared' : ''].filter(Boolean).join(' · ') })] }), pr.href ? (_jsx(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: _jsxs(XStack, { items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(GitPullRequest, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: pr.label }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] }) })) : null, sharing.length ? (_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": "Share", items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(Share, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: "Share" })] }), items: sharing })) : null, _jsx(PanelToggle, { open: desk, onToggle: () => setDesk(!desk) })] }), _jsx(Transcript, { cards: shown, onOpen: look, onDiff: () => {
|
|
361
|
+
side.open({ kind: 'diff', title: 'Diff' });
|
|
362
|
+
setDesk(true);
|
|
363
|
+
}, listen: signed ? listen : undefined, end: failed ? (_jsx(Failure, { onRetry: busy ? undefined : () => void act(() => carry(again, 'retry')), children: end.problem ? 'This run stopped before it finished.' : 'This run stopped with an error. Its work so far is on its branch.' })) : null,
|
|
331
364
|
// A paused run's agent was stopped where it stood; what it was running is cut off.
|
|
332
365
|
live: running && !paused, header: hidden ? (_jsx(SizableText, { size: "$1", color: "$soft", children: `${hidden} earlier ${hidden === 1 ? 'turn is' : 'turns are'} not shown. The run’s Git tab has everything it pushed.` })) : null, onApprove: planned && earlier ? (p) => void act(() => carry(p, 'approve')) : undefined, approving: busy, onVerdict: (v) => verdict(t, id, record?.project ?? '', v), empty: signed ? (_jsx(SizableText, { size: "$2", color: "$soft", children: detail.error
|
|
333
366
|
? detail.error.message
|
|
@@ -337,29 +370,31 @@ export function Run({ id }) {
|
|
|
337
370
|
? setup
|
|
338
371
|
? 'Setting up environment'
|
|
339
372
|
: 'Starting…'
|
|
340
|
-
: 'Waiting for the run’s first step…' })) : (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "Sign in to follow this run." }), _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" })] })) }), _jsxs(YStack, { pb: "$3", pt: "$2", gap: "$2", children: [end.problem ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "The branch is pushed, and the pull request could not be opened." })) : null, note || (signed && refused) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || refused })) : null,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
373
|
+
: 'Waiting for the run’s first step…' })) : (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "Sign in to follow this run." }), _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" })] })) }), _jsxs(YStack, { pb: "$3", pt: "$2", gap: "$2", children: [_jsxs(YStack, { ...measure, gap: "$2", children: [end.problem ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "The branch is pushed, and the pull request could not be opened." })) : null, note || (signed && refused) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || refused })) : null, plan.length ? (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { render: "button", "aria-label": `Steps · ${plan.length}`, "aria-expanded": unfolded, onPress: () => setUnfolded(!unfolded), px: "$3", py: "$1.5", gap: "$2", items: "center", hoverStyle: { bg: '$hover' }, children: [unfolded ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: `Steps · ${plan.length}` }), current && !unfolded ? (_jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, style: { textAlign: 'right' }, children: current.name })) : null] }), unfolded
|
|
374
|
+
? plan.map((s) => {
|
|
375
|
+
const on = s === current;
|
|
376
|
+
return (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$1.5", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: s.done || on ? '$ink' : '$soft', children: s.done ? '✓' : on ? '●' : '○' }), _jsx(SizableText, { size: "$2", color: s.done ? '$soft' : '$ink', numberOfLines: 1, children: s.name })] }, s.name));
|
|
377
|
+
})
|
|
378
|
+
: null] })) : null, setup && running && !seen ? _jsx(Onboarding, { onDone: () => setSeen(true) }) : null, running ? (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", rowGap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 160, children: paused
|
|
379
|
+
? kept.pushed
|
|
380
|
+
? 'Paused — its work so far is on its branch.'
|
|
381
|
+
: 'Paused.'
|
|
382
|
+
: setup
|
|
383
|
+
? span
|
|
384
|
+
? `Environment setup takes ~${span[0] === span[1] ? span[0] : `${span[0]}–${span[1]}`} minutes.`
|
|
385
|
+
: 'Environment setup takes several minutes.'
|
|
386
|
+
: 'This run is still working.' }), _jsx(Steer, { onCommand: command, withhold: [
|
|
387
|
+
...(paused ? ['pause'] : ['resume']),
|
|
388
|
+
...(busy || waiting ? ['pause', 'resume', 'stop'] : []),
|
|
389
|
+
] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? 'You will be notified' : '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 ? 'Steer this run' : 'Follow up on this run', head: files.length ? _jsx(XStack, { gap: "$1.5", flexWrap: "wrap", children: _jsx(Files, { files: files, onFiles: setFiles }) }) : undefined, onAttach: () => pick(files, setFiles, setNote),
|
|
390
|
+
// What a new run from here is made with; words that steer this run use neither.
|
|
391
|
+
mind: signed && !steering ? mind : null, limits: limits, voice: voice }), _jsx(SizableText, { size: "$1", color: "$soft", style: { textAlign: 'center' }, children: "Hanzo is AI and can make mistakes." })] })] }), desk && !wide ? desk_(true) : null, desk && wide ? (_jsxs(YStack, { "data-slot": "desk", ref: (el) => {
|
|
357
392
|
deskBox.current = el instanceof HTMLElement ? el : null;
|
|
358
393
|
}, ...pane,
|
|
359
394
|
// A pane of its own beside the run's, the gutter between them.
|
|
360
|
-
ml: gap, position: "relative", shrink: 0, minH: 0,
|
|
395
|
+
ml: gap, position: "relative", shrink: 0, minH: 0, style: { width: across, maxWidth: `calc(100% - ${TALK_FLOOR}px)`, minWidth: DESK_FLOOR }, children: [_jsx(Grip, { side: "right", span: across, floor: DESK_FLOOR, ceil: most, reset: Math.min(most, DESK_WIDTH), onSpan: (n) => deskBox.current?.style.setProperty('width', `${n}px`), onKeep: setReach, onShut: () => setDesk(false), label: "Resize the side panel",
|
|
361
396
|
// In the gutter between the two panes; `left` is measured inside the pane's 1px edge.
|
|
362
|
-
l: `calc(-1 * ${gap} - ${pane.borderWidth}px)`, width: gap }),
|
|
397
|
+
l: `calc(-1 * ${gap} - ${pane.borderWidth}px)`, width: gap }), desk_(false)] })) : null, _jsx(Dialog, { open: naming, onOpenChange: setNaming, children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: "Rename this run" }), _jsx(Input, { autoFocus: true, value: name, onChangeText: setName, "aria-label": "The run\u2019s name", maxLength: 512, onKeyDown: (e) => {
|
|
363
398
|
if (e.key === 'Enter')
|
|
364
399
|
save();
|
|
365
400
|
} }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setNaming(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: busy || !name.trim(), onPress: save, children: "Save" })] })] }) })] }));
|
package/lib/section.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare const DOTS: Record<string, SessionStatus>;
|
|
|
8
8
|
/** The org's coding runs, newest first, kept live by the org's feed. */
|
|
9
9
|
export declare function useSessions(host: Host): Read<Session[]>;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* The builder's places, grouped as every rail groups them (nav.tsx), then the
|
|
12
12
|
* runs. `children` sit between the places and the runs — where a host lists its
|
|
13
13
|
* own. `onPick` fires after a move, so a host that draws its rail as a drawer
|
|
14
14
|
* on a phone can close it.
|
package/lib/section.js
CHANGED
|
@@ -11,12 +11,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
11
11
|
* host's own `go`.
|
|
12
12
|
*/
|
|
13
13
|
import { SizableText } from '@hanzo/gui';
|
|
14
|
-
import { Blocks, CircleDot, FolderGit2, Kanban, LayoutTemplate, SlidersHorizontal, SquarePen, Workflow } from '@hanzogui/lucide-icons-2';
|
|
15
14
|
import { SidebarItem, SidebarSection, StatusDot } from '@hanzo/ui/chat';
|
|
16
15
|
import { useMemo } from 'react';
|
|
17
|
-
import { pinBoard } from './choice.js';
|
|
18
16
|
import { useRecents } from './data.js';
|
|
19
|
-
import {
|
|
17
|
+
import { nav } from './nav.js';
|
|
18
|
+
import { route } from './route.js';
|
|
20
19
|
/** A run's status, as the rail's dot draws it. Anything else is idle. */
|
|
21
20
|
export const DOTS = {
|
|
22
21
|
running: 'running',
|
|
@@ -32,7 +31,7 @@ export function useSessions(host) {
|
|
|
32
31
|
}
|
|
33
32
|
const Note = ({ children }) => (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", py: "$1", children: children }));
|
|
34
33
|
/**
|
|
35
|
-
*
|
|
34
|
+
* The builder's places, grouped as every rail groups them (nav.tsx), then the
|
|
36
35
|
* runs. `children` sit between the places and the runs — where a host lists its
|
|
37
36
|
* own. `onPick` fires after a move, so a host that draws its rail as a drawer
|
|
38
37
|
* on a phone can close it.
|
|
@@ -40,13 +39,10 @@ const Note = ({ children }) => (_jsx(SizableText, { size: "$1", color: "$soft",
|
|
|
40
39
|
export function DevSection({ host, onPick, label = 'Runs', children, }) {
|
|
41
40
|
const sessions = useSessions(host);
|
|
42
41
|
const r = route(host.path);
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// Every row, a place or a run, closes a drawer behind it.
|
|
43
|
+
const then = (press) => () => {
|
|
44
|
+
press?.();
|
|
45
45
|
onPick?.();
|
|
46
46
|
};
|
|
47
|
-
|
|
48
|
-
return (_jsxs(_Fragment, { children: [_jsx(SidebarItem, { icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), active: r.kind === 'new', onPress: () => go(''), children: "New run" }), _jsx(SidebarItem, { icon: _jsx(Workflow, { size: 16, "aria-hidden": true }), active: screen === 'automations' || r.kind === 'automation', onPress: () => go(path({ kind: 'screen', screen: 'automations' })), children: "Automations" }), _jsx(SidebarItem, { icon: _jsx(FolderGit2, { size: 16, "aria-hidden": true }), active: screen === 'codebases', onPress: () => go(path({ kind: 'screen', screen: 'codebases' })), children: "Codebase" }), _jsx(SidebarItem, { icon: _jsx(Kanban, { size: 16, "aria-hidden": true }), active: screen === 'projects', onPress: () => go(path({ kind: 'screen', screen: 'projects' })), children: "Projects" }), _jsx(SidebarItem, { icon: _jsx(CircleDot, { size: 16, "aria-hidden": true }), active: screen === 'issues', onPress: () => {
|
|
49
|
-
pinBoard(host.org, '');
|
|
50
|
-
go(path({ kind: 'screen', screen: 'issues' }));
|
|
51
|
-
}, children: "Issues" }), _jsx(SidebarItem, { icon: _jsx(SlidersHorizontal, { size: 16, "aria-hidden": true }), active: r.kind === 'customize', onPress: () => go(path({ kind: 'customize', tab: 'connectors' })), children: "Customize" }), _jsx(SidebarItem, { icon: _jsx(Blocks, { size: 16, "aria-hidden": true }), active: screen === 'artifacts', onPress: () => go(path({ kind: 'screen', screen: 'artifacts' })), children: "Artifacts" }), _jsx(SidebarItem, { icon: _jsx(LayoutTemplate, { size: 16, "aria-hidden": true }), active: screen === 'templates', onPress: () => go(path({ kind: 'screen', screen: 'templates' })), children: "Templates" }), children, _jsx(SidebarSection, { label: label, children: !host.person ? (_jsx(Note, { children: "Sign in to see your runs." })) : sessions.value.length ? (sessions.value.map((s) => (_jsx(SidebarItem, { icon: _jsx(StatusDot, { status: DOTS[s.status] ?? 'idle' }), active: r.kind === 'run' && r.id === s.id, onPress: () => go(s.id), children: s.title || 'Untitled run' }, s.id)))) : (_jsx(Note, { children: sessions.error ? sessions.error.message : sessions.loading ? 'Reading…' : 'No runs yet. Say what to build and it lands here.' })) })] }));
|
|
47
|
+
return (_jsxs(_Fragment, { children: [nav(host, host.go).map((g) => (_jsx(SidebarSection, { label: g.label, children: g.links.map((l) => (_jsx(SidebarItem, { icon: l.icon, active: l.active, onPress: then(l.onPress), children: l.label }, l.id))) }, g.id))), children, _jsx(SidebarSection, { label: label, children: !host.person ? (_jsx(Note, { children: "Sign in to see your runs." })) : sessions.value.length ? (sessions.value.map((s) => (_jsx(SidebarItem, { icon: _jsx(StatusDot, { status: DOTS[s.status] ?? 'idle' }), active: r.kind === 'run' && r.id === s.id, onPress: then(() => host.go(s.id)), children: s.title || 'Untitled run' }, s.id)))) : (_jsx(Note, { children: sessions.error ? sessions.error.message : sessions.loading ? 'Reading…' : 'No runs yet. Say what to build and it lands here.' })) })] }));
|
|
52
48
|
}
|
package/lib/settings/billing.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* Billing: the plan this organization is on and the way to change it,
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Billing: the plan this organization is on and the way to change it, its
|
|
4
|
+
* credits (`Credits`: the balance, grants, top-ups, auto-reload, this month's
|
|
5
|
+
* spend and its limit), the cards it pays with, its invoices, and ending the
|
|
6
|
+
* plan. Changing plan is the Plans screen; this page says what is true now.
|
|
6
7
|
*/
|
|
7
8
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
8
9
|
import { CreditCard, Download, Plus, X } from '@hanzogui/lucide-icons-2';
|
|
9
10
|
import { Button, Dialog, DialogContent, DialogTitle, Input, toast } from '@hanzo/ui';
|
|
10
11
|
import { useEffect, useState } from 'react';
|
|
11
|
-
import { cancel, chosen, current, detach, formatAddress, invoices, label, makeDefault, methods, money, pdf, reactivate, subscriptions, updateMethod, } from '../api/billing.js';
|
|
12
|
+
import { cancel, chosen, current, detach, formatAddress, invoices, label, makeDefault, methods, money, pdf, reactivate, subscriptions, tier, updateMethod, } from '../api/billing.js';
|
|
12
13
|
import { useRead } from '../data.js';
|
|
13
14
|
import { useHost, useTarget } from '../host.js';
|
|
15
|
+
import { Title } from '../meter.js';
|
|
16
|
+
import { label as named } from '../plan.js';
|
|
14
17
|
import { path } from '../route.js';
|
|
15
18
|
import { AddCard } from './card.js';
|
|
19
|
+
import { Credits } from './credits.js';
|
|
16
20
|
import { Card, day, Field, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
17
21
|
export function Billing() {
|
|
18
22
|
const host = useHost();
|
|
@@ -21,6 +25,7 @@ export function Billing() {
|
|
|
21
25
|
const subs = useRead(signed ? () => subscriptions(t) : null, [], [t, signed]);
|
|
22
26
|
const cards = useRead(signed ? () => methods(t) : null, [], [t, signed]);
|
|
23
27
|
const bills = useRead(signed ? () => invoices(t) : null, [], [t, signed]);
|
|
28
|
+
const billed = useRead(signed ? () => tier(t) : null, null, [t, signed]);
|
|
24
29
|
const [adding, setAdding] = useState(false);
|
|
25
30
|
const [ending, setEnding] = useState(false);
|
|
26
31
|
const [editingBilling, setEditingBilling] = useState(null);
|
|
@@ -31,6 +36,12 @@ export function Billing() {
|
|
|
31
36
|
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Billing" }), _jsx(Soft, { children: "Sign in to see this organization\u2019s billing." })] }));
|
|
32
37
|
}
|
|
33
38
|
const plan = current(subs.value);
|
|
39
|
+
// The plan by its family, `Max` with `20x` beside it; the subscription names
|
|
40
|
+
// it, else the rung billing serves.
|
|
41
|
+
const title = named(plan?.plan || billed.value?.plan, billed.value?.tier) ?? (plan ? { name: plan.name, tag: '' } : null);
|
|
42
|
+
// Nothing is said about the plan until both reads have answered: a page that
|
|
43
|
+
// has not asked yet is not a page on Free.
|
|
44
|
+
const reading = (subs.loading && !subs.value.length) || (billed.value === null && !billed.error && !subs.error);
|
|
34
45
|
const empty = !cards.error && !cards.loading && cards.value.length === 0;
|
|
35
46
|
const defaultCard = chosen(cards.value);
|
|
36
47
|
// A done act is a toast; a refused one stays on the page, beside what was tried.
|
|
@@ -49,11 +60,7 @@ export function Billing() {
|
|
|
49
60
|
setWorking(false);
|
|
50
61
|
}
|
|
51
62
|
};
|
|
52
|
-
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Billing", detail: `The plan ${host.org ?? 'this organization'} is on, the cards it pays with, and its invoices.` }), _jsxs(XStack, { items: "center", gap: "$3", px: "$4", py: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", flexWrap: "wrap", children: [_jsxs(YStack, { flex: 1, minW: 200, gap: "$1", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: subs.
|
|
53
|
-
? subs.error.message
|
|
54
|
-
: plan
|
|
55
|
-
? terms(plan)
|
|
56
|
-
: 'No paid plan. Usage is paid from the balance.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'plans' })), children: "Adjust plan" })] }), _jsxs(Group, { title: "Payment", detail: "The card a plan renews on and a top-up charges.", action: host.admin && (cards.error || cards.value.length > 0) ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " Add card"] })) : undefined, children: [cards.error ? (_jsx(Soft, { children: cards.error.message })) : cards.loading && !cards.value.length ? (_jsx(Soft, { children: "Reading cards\u2026" })) : empty ? (_jsxs(XStack, { items: "center", gap: "$3", px: "$4", py: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", flexWrap: "wrap", children: [_jsx(CreditCard, { size: 20 }), _jsxs(YStack, { flex: 1, minW: 180, gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: "No card on file." }), _jsx(SizableText, { size: "$2", color: "$soft", children: host.admin ? 'Add one to pay for a plan or a top-up. Adding it charges nothing.' : 'An org admin adds cards.' })] }), host.admin ? (_jsxs(Button, { variant: "primary", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 16 }), " Add card"] })) : null] })) : (_jsx(Card, { children: cards.value.map((m, i) => (_jsx(Row, { first: i === 0, leading: _jsx(CreditCard, { size: 16 }), title: label(m), detail: [
|
|
63
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Billing", detail: `The plan ${host.org ?? 'this organization'} is on, the cards it pays with, and its invoices.` }), _jsxs(XStack, { items: "center", gap: "$3", px: "$4", py: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", flexWrap: "wrap", children: [_jsxs(YStack, { flex: 1, minW: 200, gap: "$1", "data-slot": "billing-plan", children: [reading ? (_jsx(SizableText, { size: "$5", color: "$ink", children: "Reading your plan\u2026" })) : (_jsx(Title, { label: title ?? { name: 'Free', tag: '' }, size: "$5" })), _jsx(SizableText, { size: "$2", color: "$soft", children: subs.error ? subs.error.message : plan ? terms(plan) : title ? 'Your plan.' : 'No paid plan. Usage is paid from credits.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'plans' })), children: "Adjust plan" })] }), _jsx(Credits, { target: t, admin: host.admin, onCard: () => setAdding(true) }), _jsxs(Group, { title: "Payment", detail: "The card a plan renews on and a top-up charges.", action: host.admin && (cards.error || cards.value.length > 0) ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " Add card"] })) : undefined, children: [cards.error ? (_jsx(Soft, { children: cards.error.message })) : cards.loading && !cards.value.length ? (_jsx(Soft, { children: "Reading cards\u2026" })) : empty ? (_jsxs(XStack, { items: "center", gap: "$3", px: "$4", py: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", flexWrap: "wrap", children: [_jsx(CreditCard, { size: 20 }), _jsxs(YStack, { flex: 1, minW: 180, gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: "No card on file." }), _jsx(SizableText, { size: "$2", color: "$soft", children: host.admin ? 'Add one to pay for a plan or a top-up. Adding it charges nothing.' : 'An org admin adds cards.' })] }), host.admin ? (_jsxs(Button, { variant: "primary", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 16 }), " Add card"] })) : null] })) : (_jsx(Card, { children: cards.value.map((m, i) => (_jsx(Row, { first: i === 0, leading: _jsx(CreditCard, { size: 16 }), title: label(m), detail: [
|
|
57
64
|
m.expires ? `Expires ${m.expires}` : '',
|
|
58
65
|
m.default ? 'Default' : '',
|
|
59
66
|
m.billingAddress ? formatAddress(m.billingAddress) : '',
|
package/lib/settings/code.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* Code: where and how a new run starts — the
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Code: where and how a new run starts — the mode and the place, saved to the
|
|
4
|
+
* person's own settings for anyone whose New has no choice of its own kept yet
|
|
5
|
+
* — and the model and effort, which are the one choice Chat and Dev share
|
|
6
|
+
* (mind.tsx): changed here, changed in both composers. Beside them, the
|
|
7
|
+
* codebases' environments and the organization's machines, each on its own page.
|
|
7
8
|
*/
|
|
8
9
|
import { YStack } from '@hanzo/gui';
|
|
9
10
|
import { ChevronRight } from '@hanzogui/lucide-icons-2';
|
|
@@ -11,11 +12,12 @@ import { ChipSelect } from '@hanzo/ui/product';
|
|
|
11
12
|
import { ModelPicker } from '@hanzo/ui/models';
|
|
12
13
|
import { useState } from 'react';
|
|
13
14
|
import { unhonoured } from '../api/coding.js';
|
|
14
|
-
import { ENSO
|
|
15
|
+
import { ENSO } from '../api/models.js';
|
|
15
16
|
import { SANDBOX } from '../api/places.js';
|
|
16
|
-
import { usePlaces
|
|
17
|
+
import { usePlaces } from '../data.js';
|
|
17
18
|
import { useHost, useTarget } from '../host.js';
|
|
18
|
-
import {
|
|
19
|
+
import { MODES } from '../landing.js';
|
|
20
|
+
import { EFFORTS, useCatalog, useChoice } from '../mind.js';
|
|
19
21
|
import { usePrefs } from '../prefs.js';
|
|
20
22
|
import { path } from '../route.js';
|
|
21
23
|
import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
@@ -24,13 +26,14 @@ export function Code() {
|
|
|
24
26
|
const t = useTarget();
|
|
25
27
|
const signed = Boolean(host.person);
|
|
26
28
|
const { prefs, save } = usePrefs();
|
|
27
|
-
const catalog =
|
|
29
|
+
const catalog = useCatalog(t);
|
|
30
|
+
const [choice, choose] = useChoice();
|
|
28
31
|
const places = usePlaces(t, signed);
|
|
29
32
|
const [note, setNote] = useState('');
|
|
30
33
|
if (!signed) {
|
|
31
34
|
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Code" }), _jsx(Soft, { children: "Sign in to set how your runs start." })] }));
|
|
32
35
|
}
|
|
33
|
-
const now = {
|
|
36
|
+
const now = { mode: 'build', place: '', ...prefs.code };
|
|
34
37
|
const keep = (change) => {
|
|
35
38
|
setNote('');
|
|
36
39
|
save({ code: { ...prefs.code, ...change } }).catch((e) => setNote(e.message));
|
|
@@ -45,11 +48,11 @@ export function Code() {
|
|
|
45
48
|
// The sandbox is always listed, so a place not found is a machine that is not: it reads as its id.
|
|
46
49
|
const place = where.find((w) => w.place === now.place) ?? { id: now.place, label: now.place, place: now.place };
|
|
47
50
|
// A default is never premium: a premium model is picked for one run, in New.
|
|
48
|
-
const offered = catalog.
|
|
51
|
+
const offered = catalog.models.filter((m) => m.class !== 'premium');
|
|
49
52
|
const go = (section) => host.go(path({ kind: 'settings', section }));
|
|
50
|
-
const effort = EFFORTS.find((e) => e.id ===
|
|
53
|
+
const effort = EFFORTS.find((e) => e.id === choice.effort);
|
|
51
54
|
const mode = MODES.find((m) => m.id === now.mode);
|
|
52
|
-
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Code", detail: "How a new run starts.
|
|
55
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Code", detail: "How a new run starts. The model and effort are the ones Chat uses too." }), _jsxs(Group, { title: "Defaults", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: "Model", detail: "Enso picks one for each step; any other model runs them all.", trailing: _jsx(ModelPicker, { size: "sm", name: "Default model", models: offered, scope: "chat", value: choice.model || ENSO, onChange: (id) => choose({ model: id }), loading: catalog.loading, error: catalog.error }) }), _jsx(Row, { title: "Effort", detail: "How long the agent thinks before it acts.", trailing: _jsx(ChipSelect, { name: "Default effort", label: effort.label, chosen: effort, items: EFFORTS, onChange: (e) => choose({ effort: e.id }), placement: "bottom-end", width: 180 }) }), _jsx(Row, { title: "Mode", detail: mode.hint, trailing: _jsx(ChipSelect, { name: "Default mode", label: mode.label, chosen: mode, items: MODES, onChange: (m) => keep({ mode: m.id }), placement: "bottom-end", width: 260 }) }), _jsx(Row, { title: "Where it runs", detail: "The Hanzo sandbox, or one of your organization\u2019s machines.", trailing: _jsx(ChipSelect, { name: "Default place", label: place.label, chosen: place, items: where, onChange: (w) => keep({ place: w.place }), placeholder: "Search machines\u2026", placement: "bottom-end", loading: places.loading, error: places.error?.message ?? null }) })] }), _jsx(Note, { children: unhonoured(now.mode, now.place) })] }), _jsx(Group, { title: "Workspace", children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: "Environments", detail: "What a run installs and starts in each codebase, and the secrets it exports.", trailing: _jsx(Link, { label: "Environments", onPress: () => go('environments') }) }), _jsx(Row, { title: "Machines", detail: "Your organization\u2019s own computers that take runs.", trailing: _jsx(Link, { label: "Machines", onPress: () => go('machines') }) })] }) }), _jsx(Note, { children: note })] }));
|
|
53
56
|
}
|
|
54
57
|
function Link({ label, onPress }) {
|
|
55
58
|
return (_jsx(YStack, { render: "button", "aria-label": `Open ${label}`, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: onPress, children: _jsx(ChevronRight, { size: 16 }) }));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Target } from '../api/call.ts';
|
|
2
|
+
/** What the credits section says first, on every surface. */
|
|
3
|
+
export declare const SEPARATE = "Credits are separate from your plan\u2019s usage allowance. They pay for usage past it when you choose to continue with credits, and for all usage when there is no plan.";
|
|
4
|
+
export declare function Credits({ target: t, admin, add, onCard }: {
|
|
5
|
+
target: Target;
|
|
6
|
+
admin: boolean;
|
|
7
|
+
add?: string;
|
|
8
|
+
onCard?: () => void;
|
|
9
|
+
}): import("react").JSX.Element;
|