@hanzo/build 0.2.42 → 0.2.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/auto.d.ts +11 -0
- package/lib/api/auto.js +10 -0
- package/lib/automations.js +74 -30
- package/package.json +2 -2
package/lib/api/auto.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* DELETE /v1/auto/automations/{id} delete it, its schedule and its runs
|
|
9
9
|
* POST /v1/auto/automations/{id}/run {run: {id, status}} one run, now
|
|
10
10
|
* GET /v1/auto/automations/{id}/runs {data: [run]} newest first
|
|
11
|
+
* GET /v1/auto/templates {data: [starter]} to start a new one from
|
|
11
12
|
*
|
|
12
13
|
* Each run is one Dev run in this organization, started as the person who last
|
|
13
14
|
* saved what it does. A draft is a flow named and not yet told what to do: it
|
|
@@ -84,3 +85,13 @@ export declare function start(t: Target, id: string): Promise<{
|
|
|
84
85
|
export declare function runs(t: Target, id: string): Promise<Run[]>;
|
|
85
86
|
/** A schedule the way a person says it. */
|
|
86
87
|
export declare function words(s: Schedule): string;
|
|
88
|
+
/** A ready-made automation to start a new one from. Its schedule names no zone: the page fills in the person's. */
|
|
89
|
+
export interface Starter {
|
|
90
|
+
key: string;
|
|
91
|
+
name: string;
|
|
92
|
+
description: string;
|
|
93
|
+
instructions: string;
|
|
94
|
+
schedule: Schedule;
|
|
95
|
+
}
|
|
96
|
+
/** The starters, from GET /v1/auto/templates. */
|
|
97
|
+
export declare function starters(t: Target): Promise<Starter[]>;
|
package/lib/api/auto.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* DELETE /v1/auto/automations/{id} delete it, its schedule and its runs
|
|
9
9
|
* POST /v1/auto/automations/{id}/run {run: {id, status}} one run, now
|
|
10
10
|
* GET /v1/auto/automations/{id}/runs {data: [run]} newest first
|
|
11
|
+
* GET /v1/auto/templates {data: [starter]} to start a new one from
|
|
11
12
|
*
|
|
12
13
|
* Each run is one Dev run in this organization, started as the person who last
|
|
13
14
|
* saved what it does. A draft is a flow named and not yet told what to do: it
|
|
@@ -125,3 +126,12 @@ export function words(s) {
|
|
|
125
126
|
return `Cron ${s.cron}${zone}`;
|
|
126
127
|
}
|
|
127
128
|
}
|
|
129
|
+
/** The starters, from GET /v1/auto/templates. */
|
|
130
|
+
export async function starters(t) {
|
|
131
|
+
return list(await call(t, 'GET', '/v1/auto/templates'))
|
|
132
|
+
.map((raw) => {
|
|
133
|
+
const o = obj(raw);
|
|
134
|
+
return { key: str(o.key), name: str(o.name), description: str(o.description), instructions: str(o.instructions), schedule: schedule(o.schedule) };
|
|
135
|
+
})
|
|
136
|
+
.filter((x) => x.key && x.name && x.instructions);
|
|
137
|
+
}
|
package/lib/automations.js
CHANGED
|
@@ -20,10 +20,10 @@ import { ChipSelect } from '@hanzo/ui/product';
|
|
|
20
20
|
import { useLimits } from '@hanzo/ui/product/useLimits';
|
|
21
21
|
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
22
22
|
import { ago } from './ago.js';
|
|
23
|
-
import { automation, automations, changes, create, remove, runs, save, start, words } from './api/auto.js';
|
|
23
|
+
import { automation, automations, changes, create, remove, runs, save, start, starters, words } from './api/auto.js';
|
|
24
24
|
import { Refusal } from './api/call.js';
|
|
25
25
|
import { ENSO, limits as readLimits, models } from './api/models.js';
|
|
26
|
-
import { Choice, Confirm, Field, Line } from './customize/ui.js';
|
|
26
|
+
import { Choice, Confirm, Field, Line, Sheet } from './customize/ui.js';
|
|
27
27
|
import { useRead } from './data.js';
|
|
28
28
|
import { useHost, useTarget } from './host.js';
|
|
29
29
|
import { path } from './route.js';
|
|
@@ -118,12 +118,9 @@ function rebase(cur, was, next) {
|
|
|
118
118
|
}
|
|
119
119
|
return out;
|
|
120
120
|
}
|
|
121
|
-
/** A
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return `${e.message || 'This organization is out of credit.'} Add credit in Settings → Billing.`;
|
|
125
|
-
return e.message;
|
|
126
|
-
}
|
|
121
|
+
/** A run refused for want of credit. */
|
|
122
|
+
const broke = (e) => e instanceof Refusal && e.status === 402;
|
|
123
|
+
const BROKE = 'This organization has no credit for a run. Add credit, then Run now again.';
|
|
127
124
|
export function Automations() {
|
|
128
125
|
const host = useHost();
|
|
129
126
|
const t = useTarget();
|
|
@@ -141,7 +138,7 @@ export function Automations() {
|
|
|
141
138
|
list.reload();
|
|
142
139
|
}
|
|
143
140
|
catch (e) {
|
|
144
|
-
setProblem(`${a.name}: ${
|
|
141
|
+
setProblem(`${a.name}: ${e.message}`);
|
|
145
142
|
}
|
|
146
143
|
};
|
|
147
144
|
return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", children: _jsxs(YStack, { width: "100%", maxW: 1040, mx: "auto", gap: "$4", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$6", fontWeight: "500", color: "$ink", children: "Automations" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Work that runs itself in this organization, on a schedule or when you run it. Each run is a Dev run." })] }), _jsxs(Button, { size: "sm", shrink: 0, disabled: !signed, onPress: () => open('new'), children: [_jsx(Plus, { size: 14 }), "New automation"] })] }), _jsx(XStack, { justify: "flex-end", children: _jsx(YStack, { width: 240, maxW: "100%", children: _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Search\u2026", "aria-label": "Search automations", disabled: !signed }) }) }), _jsx(Line, { children: problem }), list.error ? (_jsx(Empty, { says: list.error.message })) : !signed ? (_jsx(Empty, { says: "Sign in to see this organization's automations." })) : list.loading && list.value.length === 0 ? (_jsx(Empty, { says: "Reading automations\u2026" })) : shown.length === 0 ? (_jsx(Empty, { says: list.value.length === 0 ? 'No automations yet.' : 'Nothing matches.', children: list.value.length === 0 ? (_jsx(Button, { size: "sm", onPress: () => open('new'), children: "New automation" })) : null })) : (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", children: [_jsx(SizableText, { flex: 1, size: "$1", color: "$soft", children: "Name" }), _jsx(SizableText, { size: "$1", color: "$soft", width: 220, children: "When" }), _jsx(SizableText, { size: "$1", color: "$soft", width: 150, children: "Last run" }), _jsx(SizableText, { size: "$1", color: "$soft", width: 56, style: { textAlign: 'right' }, children: "On" })] }), shown.map((a) => (_jsx(Item, { a: a, onOpen: () => open(a.id), onSwitch: (on) => void flip(a, on) }, a.id)))] }))] }) }));
|
|
@@ -175,6 +172,11 @@ export function Editor({ id }) {
|
|
|
175
172
|
const [busy, setBusy] = useState('');
|
|
176
173
|
const [note, setNote] = useState('');
|
|
177
174
|
const [asking, setAsking] = useState(false);
|
|
175
|
+
const [leaving, setLeaving] = useState(false);
|
|
176
|
+
const [credit, setCredit] = useState(false);
|
|
177
|
+
const nameRef = useRef(null);
|
|
178
|
+
const asksRef = useRef(null);
|
|
179
|
+
const offered = useRead(signed && fresh ? () => starters(t) : null, [], [t, signed, fresh]);
|
|
178
180
|
const here = useRef(true);
|
|
179
181
|
useEffect(() => {
|
|
180
182
|
here.current = true;
|
|
@@ -188,7 +190,7 @@ export function Editor({ id }) {
|
|
|
188
190
|
const change = changes(base, d);
|
|
189
191
|
if (switchesAlone)
|
|
190
192
|
delete change.enabled;
|
|
191
|
-
const dirty =
|
|
193
|
+
const dirty = Object.keys(change).length > 0;
|
|
192
194
|
// What is saved now becomes the form's start, unless the person is mid-edit: then
|
|
193
195
|
// their edit stands, and what they did not touch is not sent back over it.
|
|
194
196
|
const clean = useRef(true);
|
|
@@ -211,8 +213,13 @@ export function Editor({ id }) {
|
|
|
211
213
|
const timer = setInterval(() => setBeat((n) => n + 1), every);
|
|
212
214
|
return () => clearInterval(timer);
|
|
213
215
|
}, [every]);
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
+
// An edit answers what the last line asked for, so the line goes with it.
|
|
217
|
+
const set = (p) => {
|
|
218
|
+
setNote('');
|
|
219
|
+
setCredit(false);
|
|
220
|
+
setD((x) => ({ ...x, ...p }));
|
|
221
|
+
};
|
|
222
|
+
const setWhen = (p) => set({ schedule: { ...d.schedule, ...p } });
|
|
216
223
|
const wrong = unready(d.schedule);
|
|
217
224
|
const zones = useMemo(() => {
|
|
218
225
|
try {
|
|
@@ -222,19 +229,43 @@ export function Editor({ id }) {
|
|
|
222
229
|
return [{ id: 'UTC', label: 'UTC' }];
|
|
223
230
|
}
|
|
224
231
|
}, []);
|
|
232
|
+
// A draft waits on its instructions: they have the focus when it opens.
|
|
233
|
+
const drafty = Boolean(a?.draft);
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (ready && drafty)
|
|
236
|
+
asksRef.current?.focus();
|
|
237
|
+
}, [ready, drafty]);
|
|
225
238
|
const act = async (what, work) => {
|
|
226
239
|
setBusy(what);
|
|
227
240
|
setNote('');
|
|
241
|
+
setCredit(false);
|
|
228
242
|
try {
|
|
229
243
|
await work();
|
|
230
244
|
}
|
|
231
245
|
catch (e) {
|
|
232
|
-
|
|
246
|
+
// Wanting credit is said in these words only where a run was asked for.
|
|
247
|
+
const run = what === 'run' && broke(e);
|
|
248
|
+
setNote(run ? BROKE : e.message);
|
|
249
|
+
setCredit(run);
|
|
233
250
|
}
|
|
234
251
|
finally {
|
|
235
252
|
setBusy('');
|
|
236
253
|
}
|
|
237
254
|
};
|
|
255
|
+
/** What a save still needs, said, with the focus on where to give it; true when nothing. */
|
|
256
|
+
const complete = () => {
|
|
257
|
+
const ask = !d.name.trim() ? ['Name it first.', nameRef] : !d.instructions.trim() ? ['Add instructions first.', asksRef] : null;
|
|
258
|
+
if (ask) {
|
|
259
|
+
setNote(ask[0]);
|
|
260
|
+
ask[1].current?.focus();
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (wrong) {
|
|
264
|
+
setNote(wrong);
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
};
|
|
238
269
|
const keep = () => act('save', async () => {
|
|
239
270
|
if (fresh) {
|
|
240
271
|
const made = await create(t, tidy(d));
|
|
@@ -243,14 +274,14 @@ export function Editor({ id }) {
|
|
|
243
274
|
host.go(path({ kind: 'automation', id: made.id }), { replace: true });
|
|
244
275
|
return;
|
|
245
276
|
}
|
|
246
|
-
const
|
|
247
|
-
const send = changes(base,
|
|
277
|
+
const snap = d;
|
|
278
|
+
const send = changes(base, tidy(snap));
|
|
248
279
|
if (switchesAlone)
|
|
249
280
|
delete send.enabled;
|
|
250
281
|
const now = Object.keys(send).length ? await save(t, id, send) : a;
|
|
251
282
|
// What was typed while the save was in flight stands.
|
|
252
283
|
setBase(drafted(now));
|
|
253
|
-
setD((cur) => rebase(cur,
|
|
284
|
+
setD((cur) => rebase(cur, snap, drafted(now)));
|
|
254
285
|
setTick((n) => n + 1);
|
|
255
286
|
setNote('Saved.');
|
|
256
287
|
});
|
|
@@ -268,26 +299,39 @@ export function Editor({ id }) {
|
|
|
268
299
|
if (run.status === 'skipped')
|
|
269
300
|
setNote('Not started: the previous run is still going.');
|
|
270
301
|
});
|
|
271
|
-
const
|
|
302
|
+
const leave = (how) => host.go(path({ kind: 'screen', screen: 'automations' }), how);
|
|
303
|
+
// Unsaved work is not left without a word.
|
|
304
|
+
const back = () => (dirty ? setLeaving(true) : leave());
|
|
305
|
+
const begin = (x) => {
|
|
306
|
+
const tz = x.schedule.kind === 'manual' ? undefined : zone();
|
|
307
|
+
set({ name: x.name, instructions: x.instructions, schedule: tz ? { ...x.schedule, tz } : { kind: 'manual' } });
|
|
308
|
+
asksRef.current?.focus();
|
|
309
|
+
};
|
|
272
310
|
if (!signed)
|
|
273
|
-
return _jsx(Page, { title: "Automation", onBack: () =>
|
|
311
|
+
return _jsx(Page, { title: "Automation", onBack: () => leave(), says: "Sign in to see this organization's automations." });
|
|
274
312
|
if (!fresh && saved.error && !a)
|
|
275
|
-
return _jsx(Page, { title: "Automation", onBack: () =>
|
|
313
|
+
return _jsx(Page, { title: "Automation", onBack: () => leave(), says: saved.error.message });
|
|
276
314
|
if (!ready)
|
|
277
|
-
return _jsx(Page, { title: "Automation", onBack: () =>
|
|
315
|
+
return _jsx(Page, { title: "Automation", onBack: () => leave(), says: "Reading the automation\u2026" });
|
|
278
316
|
const s = d.schedule;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
317
|
+
// A new automation or a draft has nothing to switch on until it says what to do.
|
|
318
|
+
const can = switchesAlone || Boolean(d.instructions.trim());
|
|
319
|
+
const on = switchesAlone ? Boolean(a?.enabled) : can && d.enabled;
|
|
320
|
+
const says = switchesAlone ? (on ? 'On' : 'Off') : !can ? 'Add instructions first' : on ? 'On when saved' : 'Off when saved';
|
|
321
|
+
const state = fresh ? 'Not created yet' : dirty ? 'Unsaved changes' : '';
|
|
322
|
+
const sayId = `${id}-switch`;
|
|
323
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(YStack, { shrink: 0, px: "$6", pt: "$4", pb: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsxs(XStack, { width: "100%", maxW: 760, mx: "auto", items: "center", gap: "$3", flexWrap: "wrap", rowGap: "$2", children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: back, "aria-label": "All automations", children: [_jsx(ChevronLeft, { size: 14 }), "Automations"] }), _jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: state }), _jsx(XStack, { flex: 1 }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { id: sayId, size: "$2", color: "$soft", children: says }), _jsx(Switch, { checked: on, disabled: busy === 'switch' || !can, onCheckedChange: flip, "aria-label": switchesAlone ? `${a.name} on` : 'On when saved', "aria-describedby": sayId })] }), a && !a.draft ? (_jsx(Button, { size: "sm", variant: "outline", disabled: Boolean(busy) || dirty, onPress: () => void go(), children: busy === 'run' ? 'Starting…' : 'Run now' })) : null, a && dirty ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: Boolean(busy), onPress: () => {
|
|
285
324
|
setBase(drafted(a));
|
|
286
325
|
setD(drafted(a));
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
326
|
+
setNote('');
|
|
327
|
+
}, children: "Discard changes" })) : null, _jsx(Button, { size: "sm", disabled: Boolean(busy) || !(fresh || drafty || dirty), onPress: () => complete() && void keep(), children: busy === 'save' ? 'Saving…' : fresh ? 'Create' : 'Save' })] }), note ? (_jsxs(XStack, { width: "100%", maxW: 760, mx: "auto", items: "center", gap: "$3", pt: "$2", children: [_jsx(Line, { children: note }), credit ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(path({ kind: 'settings', section: 'billing' })), children: "Add credit" })) : null] })) : null] }), _jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$5", children: [_jsxs(YStack, { width: "100%", maxW: 760, mx: "auto", gap: "$5", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$6", color: "$ink", numberOfLines: 1, children: fresh ? 'New automation' : a?.name }), _jsx(SizableText, { size: "$2", color: "$soft", children: fresh
|
|
328
|
+
? 'Name it, say what it should do, and when. It runs as you, in this organization.'
|
|
329
|
+
: a?.draft
|
|
330
|
+
? 'A draft: say what it should do and save it, and it runs.'
|
|
331
|
+
: [words(a.schedule), a.next && a.enabled ? `next ${local(a.next)}` : a.schedule.kind === 'manual' ? '' : 'off'].filter(Boolean).join(' · ') })] }), fresh && offered.value.length && !d.instructions.trim() ? (_jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", items: "center", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "Start from" }), offered.value.map((x) => (_jsx(Button, { size: "sm", variant: "outline", onPress: () => begin(x), "aria-label": `Start from ${x.name}`, children: x.name }, x.key)))] })) : null, _jsx(Field, { label: "Name", children: _jsx(Input, { ref: nameRef, value: d.name, onChangeText: (v) => set({ name: v }), onSubmitEditing: () => asksRef.current?.focus(), placeholder: "Morning briefing", "aria-label": "Automation name", autoFocus: fresh }) }), _jsx(Field, { label: "Instructions (required)", hint: "What the agent does each run. It works in a Dev sandbox with this organization's connectors, MCP servers and skills.", children: _jsx(Textarea, { ref: asksRef, value: d.instructions, onChangeText: (v) => set({ instructions: v }), placeholder: "What should it do each run? For example: summarize what needs my attention today across calendar, email and messages.", "aria-label": "Instructions", rows: 7 }) }), _jsx(Field, { label: "When it runs", hint: s.kind === 'cron' ? 'Five fields: minute, hour, day of month, month, day of week.' : undefined, children: _jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", items: "center", children: [_jsx(ChipSelect, { name: "When it runs", label: KINDS.find((k) => k.id === s.kind).label, chosen: KINDS.find((k) => k.id === s.kind), items: KINDS, onChange: (k) => set({ schedule: shaped(k.id, s) }), placement: "bottom-start", width: 200 }), s.kind === 'weekly' ? _jsx(ChipSelect, { name: "Day", label: DAYS.find((x) => x.id === s.day)?.label ?? 'Monday', chosen: DAYS.find((x) => x.id === s.day), items: DAYS, onChange: (x) => setWhen({ day: x.id }), placement: "bottom-start", width: 180 }) : null, s.kind === 'daily' || s.kind === 'weekdays' || s.kind === 'weekly' ? (_jsx(YStack, { width: 110, children: _jsx(Input, { value: s.at ?? '', onChangeText: (v) => setWhen({ at: v.trim() }), placeholder: "09:00", "aria-label": "Time of day", inputMode: "text" }) })) : null, s.kind === 'hourly' ? (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "at minute" }), _jsx(YStack, { width: 70, children: _jsx(Input, { value: (s.at ?? '00:00').slice(3), onChangeText: (v) => setWhen({ at: `00:${v.replace(/\D/g, '').slice(0, 2)}` }), placeholder: "00", "aria-label": "Minute", inputMode: "numeric" }) })] })) : null, s.kind === 'cron' ? (_jsx(YStack, { width: 200, children: _jsx(Input, { value: s.cron ?? '', onChangeText: (v) => setWhen({ cron: v }), placeholder: "0 9 * * 1-5", "aria-label": "Cron", autoCapitalize: "none" }) })) : null, s.kind !== 'manual' ? _jsx(ChipSelect, { name: "Time zone", label: s.tz || 'UTC', chosen: { id: s.tz || 'UTC', label: s.tz || 'UTC' }, items: zones, onChange: (z) => setWhen({ tz: z.id }), placeholder: "Search zones\u2026", placement: "bottom-start", width: 260 }) : null] }) }), _jsx(Field, { label: "Model", children: _jsx(XStack, { children: _jsx(ModelPicker, { size: "sm", name: "Model", models: catalog.value, scope: "chat", limits: limits, value: d.model ?? ENSO, onChange: (id) => set({ model: id === ENSO ? null : id }), loading: catalog.loading, error: catalog.error?.message ?? null }) }) }), _jsx(Field, { label: "Permissions", hint: d.permissions === 'auto' ? 'Works and uses connectors without stopping.' : 'Reads and researches, changes nothing, and ends with the actions it would take for you to approve.', children: _jsx(Choice, { label: "Permissions", value: d.permissions, options: PERMISSIONS, onChange: (p) => set({ permissions: p }) }) }), _jsxs(XStack, { items: "center", gap: "$3", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: "Email me when a run ends" }), _jsx(SizableText, { size: "$1", color: "$soft", children: "One line on how it went, with a link to the run." })] }), _jsx(Switch, { checked: d.notify, onCheckedChange: (v) => set({ notify: v }), "aria-label": "Email me when a run ends" })] }), dirty && wrong && note !== wrong ? _jsx(Line, { children: wrong }) : null, a ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", disabled: Boolean(busy), onPress: () => setAsking(true), children: "Delete" }) })) : null, a ? (_jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: "Runs" }), history.error && !history.value.length ? (_jsx(Line, { children: history.error.message })) : !history.value.length ? (_jsx(SizableText, { size: "$2", color: "$soft", children: history.loading ? 'Reading its runs…' : a.draft ? 'It runs once its instructions are saved.' : 'No runs yet. Run now starts one.' })) : (_jsx(YStack, { role: "list", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", "aria-label": "Run history", children: history.value.map((r, i) => (_jsxs(XStack, { role: "listitem", items: "flex-start", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsx(YStack, { pt: "$1", children: _jsx(StatusDot, { status: DOT[r.status] ?? 'idle' }) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: `${WORD[r.status] ?? r.status} · ${local(r.at)}` }), _jsx(SizableText, { size: "$1", color: "$soft", style: { whiteSpace: 'pre-wrap' }, children: r.summary || (live(r.status) ? 'Working…' : '') })] }), r.session ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => host.go(r.session), "aria-label": `Open the Dev run of ${local(r.at)}`, children: "Open run" })) : null] }, r.id))) }))] })) : null] }), a ? (_jsx(Confirm, { what: a.name, says: "It stops running, and its schedule and its runs go with it. A run going now is stopped.", open: asking, onOpenChange: setAsking, onYes: async () => {
|
|
332
|
+
await remove(t, a.id);
|
|
333
|
+
leave({ replace: true });
|
|
334
|
+
} })) : null, _jsxs(Sheet, { title: "Leave without saving?", open: leaving, onOpenChange: setLeaving, width: 420, children: [_jsx(SizableText, { size: "$2", color: "$soft", children: fresh ? 'This automation is not created yet.' : 'Your changes to this automation are not saved.' }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setLeaving(false), children: "Keep editing" }), _jsx(Button, { size: "sm", variant: "destructive", onPress: () => leave(), children: "Leave" })] })] })] })] }));
|
|
291
335
|
}
|
|
292
336
|
function Page({ title, says, onBack }) {
|
|
293
337
|
return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", children: _jsxs(YStack, { width: "100%", maxW: 760, mx: "auto", gap: "$4", children: [_jsx(XStack, { children: _jsxs(Button, { size: "sm", variant: "ghost", onPress: onBack, "aria-label": "All automations", children: [_jsx(ChevronLeft, { size: 14 }), "Automations"] }) }), _jsx(SizableText, { render: "h1", size: "$6", color: "$ink", children: title }), _jsx(SizableText, { size: "$2", color: "$soft", children: says })] }) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.43",
|
|
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.",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"@hanzo/font": "1.9.3",
|
|
12
12
|
"@hanzo/gui": "8.3.5",
|
|
13
13
|
"@hanzo/iam": "0.21.28",
|
|
14
|
-
"@hanzo/ui": "8.27.
|
|
14
|
+
"@hanzo/ui": "8.27.77",
|
|
15
15
|
"@hanzo/vite": "0.1.4",
|
|
16
16
|
"@hanzo/voice": "0.1.8",
|
|
17
17
|
"@hanzogui/lucide-icons-2": "8.3.5",
|