@hanzo/build 0.2.56 → 0.2.58
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 +37 -11
- package/lib/activity.d.ts +20 -0
- package/lib/activity.js +38 -0
- package/lib/api/auto.d.ts +77 -2
- package/lib/api/auto.js +137 -4
- package/lib/api/library.d.ts +121 -0
- package/lib/api/library.js +157 -0
- package/lib/automations.d.ts +28 -1
- package/lib/automations.js +49 -61
- package/lib/builder.js +5 -4
- package/lib/forge.js +2 -2
- package/lib/index.d.ts +5 -0
- package/lib/index.js +5 -0
- package/lib/library.d.ts +21 -0
- package/lib/library.js +407 -0
- package/lib/nav.js +3 -3
- package/lib/panel.d.ts +3 -1
- package/lib/panel.js +11 -10
- package/lib/run.js +17 -1
- package/lib/scheduled.d.ts +3 -0
- package/lib/scheduled.js +281 -0
- package/lib/shelf.d.ts +6 -5
- package/lib/shelf.js +9 -8
- package/lib/summary.d.ts +50 -0
- package/lib/summary.js +60 -0
- package/lib/tabs.d.ts +12 -1
- package/lib/tabs.js +37 -4
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Library: everything a person made, in Chat, Dev, Studio and workflows, in
|
|
3
|
+
* one index.
|
|
4
|
+
*
|
|
5
|
+
* GET /v1/library {data: [item], next} newest first, filtered
|
|
6
|
+
* GET /v1/library/{id} one item, with fresh signed URLs
|
|
7
|
+
* POST /v1/library keep one: its bytes as base64, at most 10 MiB
|
|
8
|
+
* PATCH /v1/library/{id} rename, share with the org, move to a project
|
|
9
|
+
* DELETE /v1/library/{id} its row and its bytes
|
|
10
|
+
*
|
|
11
|
+
* An item is its owner's; another member of the org reads it only once its owner
|
|
12
|
+
* shares it, and changes it never. Its `url`, `download` and `preview` are
|
|
13
|
+
* signed and stop working at `expires`: read the item again for fresh ones.
|
|
14
|
+
*/
|
|
15
|
+
import { call, query, seg } from './call.js';
|
|
16
|
+
export const KINDS = ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
|
|
17
|
+
export const SOURCES = ['chat', 'dev', 'studio', 'workflow'];
|
|
18
|
+
/** A product, as a person names it. */
|
|
19
|
+
export const SOURCE = { chat: 'Chat', dev: 'Dev', studio: 'Studio', workflow: 'Workflow' };
|
|
20
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
21
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : 0);
|
|
22
|
+
const obj = (v) => (v && typeof v === 'object' ? v : {});
|
|
23
|
+
/** A signed address: https, and nothing a link could be made to run. */
|
|
24
|
+
const signed = (v) => (/^https:\/\//.test(str(v)) ? str(v) : '');
|
|
25
|
+
/** An app path back to what made an item: under this origin, never another's. */
|
|
26
|
+
export const inApp = (v) => {
|
|
27
|
+
const s = str(v);
|
|
28
|
+
return s.startsWith('/') && !s.startsWith('//') && !s.startsWith('/\\') ? s : '';
|
|
29
|
+
};
|
|
30
|
+
/** One item as the platform answers it, or null for a row with no id. */
|
|
31
|
+
export function item(raw) {
|
|
32
|
+
const o = obj(raw);
|
|
33
|
+
const id = str(o.id);
|
|
34
|
+
if (!id)
|
|
35
|
+
return null;
|
|
36
|
+
const kind = KINDS.includes(str(o.kind)) ? o.kind : 'file';
|
|
37
|
+
const source = SOURCES.includes(str(o.source)) ? o.source : 'dev';
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
kind,
|
|
41
|
+
source,
|
|
42
|
+
name: str(o.name) || id,
|
|
43
|
+
mime: str(o.mime),
|
|
44
|
+
bytes: num(o.bytes),
|
|
45
|
+
width: num(o.width),
|
|
46
|
+
height: num(o.height),
|
|
47
|
+
seconds: num(o.seconds),
|
|
48
|
+
pages: num(o.pages),
|
|
49
|
+
owner: str(o.owner),
|
|
50
|
+
mine: o.mine === true,
|
|
51
|
+
shared: o.shared === true,
|
|
52
|
+
project: str(o.project),
|
|
53
|
+
ref: str(o.ref),
|
|
54
|
+
origin: inApp(o.origin),
|
|
55
|
+
url: signed(o.url),
|
|
56
|
+
download: signed(o.download),
|
|
57
|
+
preview: signed(o.preview),
|
|
58
|
+
expires: str(o.expires),
|
|
59
|
+
created: str(o.created),
|
|
60
|
+
updated: str(o.updated),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export async function items(t, f = {}, init) {
|
|
64
|
+
const raw = obj(await call(t, 'GET', `/v1/library${query({
|
|
65
|
+
kind: f.kind,
|
|
66
|
+
source: f.source,
|
|
67
|
+
ref: f.ref,
|
|
68
|
+
project: f.project,
|
|
69
|
+
q: f.q?.trim(),
|
|
70
|
+
since: f.since,
|
|
71
|
+
until: f.until,
|
|
72
|
+
scope: f.scope === 'all' ? undefined : f.scope,
|
|
73
|
+
limit: f.limit,
|
|
74
|
+
cursor: f.cursor,
|
|
75
|
+
})}`, undefined, init));
|
|
76
|
+
const data = (Array.isArray(raw.data) ? raw.data : []).map(item).filter((x) => x !== null);
|
|
77
|
+
return { data, next: str(raw.next) };
|
|
78
|
+
}
|
|
79
|
+
export async function get(t, id) {
|
|
80
|
+
const x = item(await call(t, 'GET', `/v1/library/${seg(id)}`));
|
|
81
|
+
if (!x)
|
|
82
|
+
throw new Error('The item came back without an id');
|
|
83
|
+
return x;
|
|
84
|
+
}
|
|
85
|
+
/** The largest thing one upload carries. */
|
|
86
|
+
export const INLINE = 10 << 20;
|
|
87
|
+
/** Bytes as base64, in chunks a call stack holds. */
|
|
88
|
+
export function base64(bytes) {
|
|
89
|
+
let s = '';
|
|
90
|
+
for (let i = 0; i < bytes.length; i += 0x8000)
|
|
91
|
+
s += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
|
92
|
+
return btoa(s);
|
|
93
|
+
}
|
|
94
|
+
/** Keep a file in the Library. */
|
|
95
|
+
export async function upload(t, file, made) {
|
|
96
|
+
if (!file.bytes.length)
|
|
97
|
+
throw new Error(`${file.name} is empty`);
|
|
98
|
+
if (file.bytes.length > INLINE)
|
|
99
|
+
throw new Error(`${file.name} is larger than 10 MB`);
|
|
100
|
+
const x = item(await call(t, 'POST', '/v1/library', {
|
|
101
|
+
name: file.name,
|
|
102
|
+
mime: file.type || 'application/octet-stream',
|
|
103
|
+
data: base64(file.bytes),
|
|
104
|
+
...made,
|
|
105
|
+
}));
|
|
106
|
+
if (!x)
|
|
107
|
+
throw new Error('The item was kept and came back without an id');
|
|
108
|
+
return x;
|
|
109
|
+
}
|
|
110
|
+
export async function change(t, id, c) {
|
|
111
|
+
const x = item(await call(t, 'PATCH', `/v1/library/${seg(id)}`, c));
|
|
112
|
+
if (!x)
|
|
113
|
+
throw new Error('The item was changed and came back without an id');
|
|
114
|
+
return x;
|
|
115
|
+
}
|
|
116
|
+
export async function remove(t, id) {
|
|
117
|
+
await call(t, 'DELETE', `/v1/library/${seg(id)}`);
|
|
118
|
+
}
|
|
119
|
+
/** A size the way a person reads it. */
|
|
120
|
+
export function size(bytes) {
|
|
121
|
+
if (bytes < 1024)
|
|
122
|
+
return `${bytes} B`;
|
|
123
|
+
const units = ['KB', 'MB', 'GB'];
|
|
124
|
+
let n = bytes / 1024;
|
|
125
|
+
let u = 0;
|
|
126
|
+
while (n >= 1024 && u < units.length - 1) {
|
|
127
|
+
n /= 1024;
|
|
128
|
+
u++;
|
|
129
|
+
}
|
|
130
|
+
return `${n < 10 ? n.toFixed(1) : Math.round(n)} ${units[u]}`;
|
|
131
|
+
}
|
|
132
|
+
/** The day an instant falls on, as a section of the Library: Today, Yesterday, then the month. */
|
|
133
|
+
export function day(iso, now = new Date()) {
|
|
134
|
+
const d = new Date(iso);
|
|
135
|
+
if (Number.isNaN(d.getTime()))
|
|
136
|
+
return 'Earlier';
|
|
137
|
+
const start = (x) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();
|
|
138
|
+
const days = Math.round((start(now) - start(d)) / 86_400_000);
|
|
139
|
+
if (days <= 0)
|
|
140
|
+
return 'Today';
|
|
141
|
+
if (days === 1)
|
|
142
|
+
return 'Yesterday';
|
|
143
|
+
return d.toLocaleDateString(undefined, d.getFullYear() === now.getFullYear() ? { month: 'long' } : { month: 'long', year: 'numeric' });
|
|
144
|
+
}
|
|
145
|
+
/** Items under the day each was made, in the order given (newest first). */
|
|
146
|
+
export function days(list, now = new Date()) {
|
|
147
|
+
const out = [];
|
|
148
|
+
for (const x of list) {
|
|
149
|
+
const label = day(x.created, now);
|
|
150
|
+
const last = out[out.length - 1];
|
|
151
|
+
if (last && last.label === label)
|
|
152
|
+
last.rows.push(x);
|
|
153
|
+
else
|
|
154
|
+
out.push({ label, rows: [x] });
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
package/lib/automations.d.ts
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import { type SessionStatus } from '@hanzo/ui/chat';
|
|
2
|
+
import { type Draft, type Kind, type Schedule } from './api/auto.ts';
|
|
3
|
+
export declare const zone: () => string;
|
|
4
|
+
export declare const KINDS: readonly {
|
|
5
|
+
id: Kind;
|
|
6
|
+
label: string;
|
|
7
|
+
}[];
|
|
8
|
+
/** A run's status as the rail's dot draws it. */
|
|
9
|
+
export declare const DOT: Record<string, SessionStatus>;
|
|
10
|
+
export declare const WORD: Record<string, string>;
|
|
11
|
+
export declare const live: (s: string) => boolean;
|
|
12
|
+
/** The schedule a kind starts from, keeping whatever of `was` still applies. */
|
|
13
|
+
export declare function shaped(kind: Kind, was: Schedule): Schedule;
|
|
14
|
+
/** What is wrong with a schedule before it is sent, or ''. */
|
|
15
|
+
export declare function unready(s: Schedule): string;
|
|
16
|
+
/** A schedule as the platform reads it: an hourly minute in two digits. */
|
|
17
|
+
export declare function tidy(d: Draft): Draft;
|
|
18
|
+
/** A local time and date for an RFC 3339 instant. */
|
|
19
|
+
export declare const local: (iso: string) => string;
|
|
20
|
+
/** A run the platform refused for want of a plan: a free org with no credit for the run fee. */
|
|
21
|
+
export declare const unplanned: (e: unknown) => boolean;
|
|
22
|
+
export declare const PLAN = "Automations run on a paid plan; on Free each run needs $1 of credit, and this organization has none. Upgrade to run it.";
|
|
23
|
+
/** When a task runs, chosen: how often, the day and time, and the zone it is read in. */
|
|
24
|
+
export declare function When({ s, onChange, disabled }: {
|
|
25
|
+
s: Schedule;
|
|
26
|
+
onChange: (s: Schedule) => void;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
}): import("react").JSX.Element;
|
|
2
29
|
/** One automation: what it does and when, its switch, Run now, and its runs. `new` writes one. */
|
|
3
30
|
export declare function Editor({ id }: {
|
|
4
31
|
id: string;
|
package/lib/automations.js
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* One scheduled task: what it does and when, its switch, Run now and its runs.
|
|
4
|
+
* The list of them all is Scheduled (scheduled.tsx).
|
|
5
5
|
*
|
|
6
|
-
* -/automations the org's automations; a row opens its editor
|
|
7
6
|
* -/automations/new a new one: its name, what it does, when it runs
|
|
8
7
|
* -/automations/<id> its editor, its switch, Run now and its runs
|
|
9
8
|
*
|
|
10
|
-
* Every read and write is /v1/auto/automations (api/auto.ts),
|
|
11
|
-
* org the builder is in. A save sends only
|
|
12
|
-
* `enabled` alone, so turning one on or off never
|
|
9
|
+
* Every read and write is /v1/auto/automations (api/auto.ts), the caller's
|
|
10
|
+
* own: a task is its person's, in the org the builder is in. A save sends only
|
|
11
|
+
* what changed; the switch sends `enabled` alone, so turning one on or off never
|
|
12
|
+
* changes who it runs as.
|
|
13
13
|
*/
|
|
14
14
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
15
|
-
import { ChevronLeft
|
|
15
|
+
import { ChevronLeft } from '@hanzogui/lucide-icons-2';
|
|
16
16
|
import { Button, Input, Switch, Textarea } from '@hanzo/ui';
|
|
17
17
|
import { StatusDot } from '@hanzo/ui/chat';
|
|
18
18
|
import { ModelPicker } from '@hanzo/ui/models';
|
|
19
19
|
import { ChipSelect } from '@hanzo/ui/product';
|
|
20
20
|
import { useLimits } from '@hanzo/ui/product/useLimits';
|
|
21
|
-
import { useEffect,
|
|
22
|
-
import {
|
|
23
|
-
import { automation, automations, changes, create, remove, review, runs, save, start, starters, words } from './api/auto.js';
|
|
21
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
22
|
+
import { automation, changes, create, remove, review, runs, save, start, starters, words } from './api/auto.js';
|
|
24
23
|
import { current, subscriptions } from './api/billing.js';
|
|
25
24
|
import { SOCIAL } from './api/provider.js';
|
|
26
25
|
import { Out } from './out.js';
|
|
@@ -30,8 +29,10 @@ import { ENSO, limits as readLimits, models } from './api/models.js';
|
|
|
30
29
|
import { Choice, Confirm, Field, Line, Sheet } from './customize/ui.js';
|
|
31
30
|
import { useRead } from './data.js';
|
|
32
31
|
import { useHost, useTarget } from './host.js';
|
|
32
|
+
import { SOURCE } from './api/library.js';
|
|
33
|
+
import { Outputs } from './library.js';
|
|
33
34
|
import { path } from './route.js';
|
|
34
|
-
const zone = () => {
|
|
35
|
+
export const zone = () => {
|
|
35
36
|
try {
|
|
36
37
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
37
38
|
}
|
|
@@ -39,8 +40,9 @@ const zone = () => {
|
|
|
39
40
|
return 'UTC';
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
|
-
const KINDS = [
|
|
43
|
+
export const KINDS = [
|
|
43
44
|
{ id: 'manual', label: 'When I run it' },
|
|
45
|
+
{ id: 'once', label: 'Once' },
|
|
44
46
|
{ id: 'hourly', label: 'Every hour' },
|
|
45
47
|
{ id: 'daily', label: 'Every day' },
|
|
46
48
|
{ id: 'weekdays', label: 'Weekdays' },
|
|
@@ -61,8 +63,8 @@ const PERMISSIONS = [
|
|
|
61
63
|
{ id: 'auto', label: 'Act on its own' },
|
|
62
64
|
];
|
|
63
65
|
/** A run's status as the rail's dot draws it. */
|
|
64
|
-
const DOT = { succeeded: 'done', failed: 'error', running: 'running', queued: 'running', review: 'paused', skipped: 'stopped', refused: 'stopped' };
|
|
65
|
-
const WORD = {
|
|
66
|
+
export const DOT = { succeeded: 'done', failed: 'error', running: 'running', queued: 'running', review: 'paused', skipped: 'stopped', refused: 'stopped' };
|
|
67
|
+
export const WORD = {
|
|
66
68
|
succeeded: 'Succeeded',
|
|
67
69
|
failed: 'Failed',
|
|
68
70
|
running: 'Running',
|
|
@@ -72,14 +74,22 @@ const WORD = {
|
|
|
72
74
|
refused: 'Refused',
|
|
73
75
|
};
|
|
74
76
|
const NAME = Object.fromEntries(SOCIAL.map((p) => [p.id, p.name]));
|
|
75
|
-
const live = (s) => s === 'running' || s === 'queued';
|
|
77
|
+
export const live = (s) => s === 'running' || s === 'queued';
|
|
78
|
+
/** A local date and time an hour from now, as a once schedule's `at`. */
|
|
79
|
+
const soon = () => {
|
|
80
|
+
const d = new Date(Date.now() + 3_600_000);
|
|
81
|
+
const two = (n) => String(n).padStart(2, '0');
|
|
82
|
+
return `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())}T${two(d.getHours())}:00`;
|
|
83
|
+
};
|
|
76
84
|
/** The schedule a kind starts from, keeping whatever of `was` still applies. */
|
|
77
|
-
function shaped(kind, was) {
|
|
85
|
+
export function shaped(kind, was) {
|
|
78
86
|
// A schedule that runs only when asked keeps no zone worth keeping: leaving it takes the person's own.
|
|
79
87
|
const tz = was.kind === 'manual' || !was.tz ? zone() : was.tz;
|
|
80
88
|
switch (kind) {
|
|
81
89
|
case 'manual':
|
|
82
90
|
return { kind };
|
|
91
|
+
case 'once':
|
|
92
|
+
return { kind, at: soon(), tz };
|
|
83
93
|
case 'hourly':
|
|
84
94
|
return { kind, at: `00:${(was.at ?? '00:00').slice(3) || '00'}`, tz };
|
|
85
95
|
case 'daily':
|
|
@@ -92,15 +102,17 @@ function shaped(kind, was) {
|
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
/** What is wrong with a schedule before it is sent, or ''. */
|
|
95
|
-
function unready(s) {
|
|
105
|
+
export function unready(s) {
|
|
96
106
|
if ((s.kind === 'daily' || s.kind === 'weekdays' || s.kind === 'weekly') && !/^([01]?\d|2[0-3]):[0-5]\d$/.test(s.at ?? ''))
|
|
97
107
|
return 'Time of day is HH:MM on a 24-hour clock.';
|
|
108
|
+
if (s.kind === 'once' && s.at && !/^\d{4}-\d{2}-\d{2}T([01]\d|2[0-3]):[0-5]\d$/.test(s.at))
|
|
109
|
+
return 'Once is a date and a time: YYYY-MM-DDTHH:MM.';
|
|
98
110
|
if (s.kind === 'cron' && (s.cron ?? '').trim().split(/\s+/).length !== 5)
|
|
99
111
|
return 'Cron is five fields: minute, hour, day of month, month, day of week.';
|
|
100
112
|
return '';
|
|
101
113
|
}
|
|
102
114
|
/** A schedule as the platform reads it: an hourly minute in two digits. */
|
|
103
|
-
function tidy(d) {
|
|
115
|
+
export function tidy(d) {
|
|
104
116
|
const s = d.schedule;
|
|
105
117
|
if (s.kind !== 'hourly')
|
|
106
118
|
return d;
|
|
@@ -108,7 +120,7 @@ function tidy(d) {
|
|
|
108
120
|
return { ...d, schedule: { ...s, at: `00:${String(m).padStart(2, '0')}` } };
|
|
109
121
|
}
|
|
110
122
|
/** A local time and date for an RFC 3339 instant. */
|
|
111
|
-
const local = (iso) => {
|
|
123
|
+
export const local = (iso) => {
|
|
112
124
|
const d = new Date(iso);
|
|
113
125
|
return Number.isNaN(d.getTime()) ? '' : d.toLocaleString(undefined, { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
114
126
|
};
|
|
@@ -122,6 +134,7 @@ const drafted = (a) => ({
|
|
|
122
134
|
notify: a.notify,
|
|
123
135
|
postTo: a.postTo,
|
|
124
136
|
enabled: a.draft ? true : a.enabled,
|
|
137
|
+
source: a.source,
|
|
125
138
|
});
|
|
126
139
|
/** `next`, keeping each field of `cur` the person changed from `was` since. */
|
|
127
140
|
function rebase(cur, was, next) {
|
|
@@ -133,39 +146,23 @@ function rebase(cur, was, next) {
|
|
|
133
146
|
return out;
|
|
134
147
|
}
|
|
135
148
|
/** A run the platform refused for want of a plan: a free org with no credit for the run fee. */
|
|
136
|
-
const unplanned = (e) => e instanceof Refusal && e.status === 402;
|
|
137
|
-
const PLAN = 'Automations run on a paid plan; on Free each run needs $1 of credit, and this organization has none. Upgrade to run it.';
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const [problem, setProblem] = useState('');
|
|
144
|
-
const list = useRead(signed ? () => automations(t) : null, [], [t, signed]);
|
|
145
|
-
const needle = q.trim().toLowerCase();
|
|
146
|
-
const shown = list.value.filter((a) => !needle || a.name.toLowerCase().includes(needle) || a.instructions.toLowerCase().includes(needle));
|
|
147
|
-
const open = (id) => host.go(path({ kind: 'automation', id }));
|
|
148
|
-
const flip = async (a, on) => {
|
|
149
|
-
setProblem('');
|
|
149
|
+
export const unplanned = (e) => e instanceof Refusal && e.status === 402;
|
|
150
|
+
export const PLAN = 'Automations run on a paid plan; on Free each run needs $1 of credit, and this organization has none. Upgrade to run it.';
|
|
151
|
+
const BLANK = () => ({ name: '', instructions: '', model: null, schedule: { kind: 'manual' }, permissions: 'ask', notify: false, postTo: [], enabled: true, source: 'dev' });
|
|
152
|
+
/** When a task runs, chosen: how often, the day and time, and the zone it is read in. */
|
|
153
|
+
export function When({ s, onChange, disabled }) {
|
|
154
|
+
const setWhen = (p) => onChange({ ...s, ...p });
|
|
155
|
+
const zones = useMemo(() => {
|
|
150
156
|
try {
|
|
151
|
-
|
|
152
|
-
list.reload();
|
|
157
|
+
return ['UTC', ...Intl.supportedValuesOf('timeZone').filter((z) => z !== 'UTC')].map((z) => ({ id: z, label: z }));
|
|
153
158
|
}
|
|
154
|
-
catch
|
|
155
|
-
|
|
159
|
+
catch {
|
|
160
|
+
return [{ id: 'UTC', label: 'UTC' }];
|
|
156
161
|
}
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
/** One automation in the list: its name opens it, and its switch arms it. */
|
|
161
|
-
function Item({ a, onOpen, onSwitch }) {
|
|
162
|
-
const about = useId();
|
|
163
|
-
return (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsxs(XStack, { render: "button", "aria-label": `Open ${a.name}`, "aria-describedby": about, onPress: onOpen, flex: 1, minW: 0, items: "center", gap: "$3", flexWrap: "wrap", rowGap: "$1", children: [_jsx(Workflow, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, flex: 1, minW: 120, style: { textAlign: 'left' }, children: a.name }), _jsxs(XStack, { id: about, items: "center", gap: "$3", flexWrap: "wrap", rowGap: "$1", maxW: "100%", children: [a.draft ? (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", children: "Draft" })) : null, _jsx(SizableText, { size: "$1", color: "$soft", width: 220, numberOfLines: 1, style: { textAlign: 'left' }, children: a.draft ? 'Needs instructions' : words(a.schedule) }), _jsxs(XStack, { width: 150, items: "center", gap: "$2", children: [a.last ? _jsx(StatusDot, { status: DOT[a.last.status] ?? 'idle' }) : null, _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: a.last ? `${WORD[a.last.status] ?? a.last.status} · ${ago(a.last.at)}` : 'Never run' })] })] })] }), _jsx(XStack, { width: 56, justify: "flex-end", children: _jsx(Switch, { checked: a.enabled, disabled: a.draft, onCheckedChange: onSwitch, "aria-label": `${a.name} on` }) })] }));
|
|
164
|
-
}
|
|
165
|
-
function Empty({ says, children }) {
|
|
166
|
-
return (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$4", px: "$4", py: "$8", gap: "$3", items: "center", children: [_jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: says }), children] }));
|
|
162
|
+
}, []);
|
|
163
|
+
const kind = KINDS.find((k) => k.id === s.kind) ?? KINDS[0];
|
|
164
|
+
return (_jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", items: "center", opacity: disabled ? 0.6 : 1, pointerEvents: disabled ? 'none' : 'auto', children: [_jsx(ChipSelect, { name: "When it runs", label: kind.label, chosen: kind, items: KINDS, onChange: (k) => onChange(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 === 'once' ? (_jsx(YStack, { width: 190, children: _jsx(Input, { value: s.at ?? '', onChangeText: (v) => setWhen({ at: v.trim() }), placeholder: "2026-10-20T09:00", "aria-label": "Date and time", autoCapitalize: "none" }) })) : 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] }));
|
|
167
165
|
}
|
|
168
|
-
const BLANK = () => ({ name: '', instructions: '', model: null, schedule: { kind: 'manual' }, permissions: 'ask', notify: false, postTo: [], enabled: true });
|
|
169
166
|
/** One automation: what it does and when, its switch, Run now, and its runs. `new` writes one. */
|
|
170
167
|
export function Editor({ id }) {
|
|
171
168
|
const host = useHost();
|
|
@@ -238,16 +235,7 @@ export function Editor({ id }) {
|
|
|
238
235
|
setUpgrade(false);
|
|
239
236
|
setD((x) => ({ ...x, ...p }));
|
|
240
237
|
};
|
|
241
|
-
const setWhen = (p) => set({ schedule: { ...d.schedule, ...p } });
|
|
242
238
|
const wrong = unready(d.schedule);
|
|
243
|
-
const zones = useMemo(() => {
|
|
244
|
-
try {
|
|
245
|
-
return ['UTC', ...Intl.supportedValuesOf('timeZone').filter((z) => z !== 'UTC')].map((z) => ({ id: z, label: z }));
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
248
|
-
return [{ id: 'UTC', label: 'UTC' }];
|
|
249
|
-
}
|
|
250
|
-
}, []);
|
|
251
239
|
// A draft waits on its instructions: they have the focus when it opens.
|
|
252
240
|
const drafty = Boolean(a?.draft);
|
|
253
241
|
useEffect(() => {
|
|
@@ -350,7 +338,7 @@ export function Editor({ id }) {
|
|
|
350
338
|
const says = switchesAlone ? (on ? 'On' : 'Off') : !can ? 'Add instructions first' : on ? 'On when saved' : 'Off when saved';
|
|
351
339
|
const state = fresh ? 'Not created yet' : dirty ? 'Unsaved changes' : '';
|
|
352
340
|
const sayId = `${id}-switch`;
|
|
353
|
-
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
|
|
341
|
+
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 scheduled tasks", children: [_jsx(ChevronLeft, { size: 14 }), "Scheduled tasks"] }), _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: () => {
|
|
354
342
|
setBase(drafted(a));
|
|
355
343
|
setD(drafted(a));
|
|
356
344
|
setNote('');
|
|
@@ -358,7 +346,7 @@ export function Editor({ id }) {
|
|
|
358
346
|
? 'Name it, say what it should do, and when. It runs as you, in this organization.'
|
|
359
347
|
: a?.draft
|
|
360
348
|
? 'A draft: say what it should do and save it, and it runs.'
|
|
361
|
-
: [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, disabled: locked }) }), _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, disabled: locked, 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.' :
|
|
349
|
+
: [`From ${SOURCE[a.source]}`, 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, disabled: locked }) }), _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, disabled: locked, 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.' : s.kind === 'once' ? 'It runs at that time and then switches itself off.' : undefined, children: _jsx(When, { s: s, onChange: (next) => set({ schedule: next }), disabled: locked }) }), _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(Field, { label: "Post to", hint: !host.admin
|
|
362
350
|
? 'An org admin chooses where automations post: the accounts are the organization’s.'
|
|
363
351
|
: !d.postTo.length
|
|
364
352
|
? 'Turn an account on and each run’s answer is a post for it.'
|
|
@@ -376,11 +364,11 @@ export function Editor({ id }) {
|
|
|
376
364
|
: `${c.available ? 'Not connected: an org admin connects it in Settings → Integrations.' : p.needs} ${on || d.permissions === 'ask'
|
|
377
365
|
? 'Runs hold the post for you to read; posting it fails until it is connected.'
|
|
378
366
|
: 'Acting on its own, it posts only to a connected account.'}` })] }), _jsx(Switch, { checked: on, disabled: !host.admin || (!on && !c?.connected && d.permissions === 'auto'), onCheckedChange: (v) => set({ postTo: v ? [...d.postTo, p.id].sort() : d.postTo.filter((x) => x !== p.id) }), "aria-label": `Post to ${p.name}` })] }, p.id));
|
|
379
|
-
}) }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => away(path({ kind: 'settings', section: 'integrations' })), children: "Connect accounts" }) })] }), _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.draft ? (_jsx(YStack, { mt: "$1.5", px: "$3", py: "$2", rounded: "$2", bg: "$raised", role: "group", "aria-label": "The post", children: _jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: r.draft }) })) : null, r.posts.map((x) => (_jsxs(XStack, { items: "center", gap: "$2", pt: "$1", children: [_jsx(SizableText, { size: "$1", color: "$ink", children: NAME[x.to] ?? x.to }), x.url ? (_jsx(Out, { href: x.url, label: `Open the ${NAME[x.to] ?? x.to} post`, children: _jsx(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: "Posted \u00B7 open" }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: x.error }))] }, x.to))), r.status === 'review' && host.admin && !answered.has(r.id) ? (_jsxs(XStack, { gap: "$2", pt: "$2", children: [_jsx(Button, { size: "sm", disabled: Boolean(busy), onPress: () => void answer(r, true), "aria-label": `Post the post of ${local(r.at)}`, children: "Post it" }), _jsx(Button, { size: "sm", variant: "ghost", disabled: Boolean(busy), onPress: () => void answer(r, false), "aria-label": `Discard the post of ${local(r.at)}`, children: "Discard" })] })) : r.status === 'review' && !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", pt: "$1", children: "An org admin posts or discards it." })) : null] }), 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 () => {
|
|
367
|
+
}) }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => away(path({ kind: 'settings', section: 'integrations' })), children: "Connect accounts" }) })] }), _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.draft ? (_jsx(YStack, { mt: "$1.5", px: "$3", py: "$2", rounded: "$2", bg: "$raised", role: "group", "aria-label": "The post", children: _jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: r.draft }) })) : null, r.outputs.length ? _jsx(Outputs, { ids: r.outputs }) : null, r.posts.map((x) => (_jsxs(XStack, { items: "center", gap: "$2", pt: "$1", children: [_jsx(SizableText, { size: "$1", color: "$ink", children: NAME[x.to] ?? x.to }), x.url ? (_jsx(Out, { href: x.url, label: `Open the ${NAME[x.to] ?? x.to} post`, children: _jsx(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: "Posted \u00B7 open" }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: x.error }))] }, x.to))), r.status === 'review' && host.admin && !answered.has(r.id) ? (_jsxs(XStack, { gap: "$2", pt: "$2", children: [_jsx(Button, { size: "sm", disabled: Boolean(busy), onPress: () => void answer(r, true), "aria-label": `Post the post of ${local(r.at)}`, children: "Post it" }), _jsx(Button, { size: "sm", variant: "ghost", disabled: Boolean(busy), onPress: () => void answer(r, false), "aria-label": `Discard the post of ${local(r.at)}`, children: "Discard" })] })) : r.status === 'review' && !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", pt: "$1", children: "An org admin posts or discards it." })) : null] }), 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 () => {
|
|
380
368
|
await remove(t, a.id);
|
|
381
369
|
leave({ replace: true });
|
|
382
370
|
} })) : null, _jsxs(Sheet, { title: "Leave without saving?", open: Boolean(leaving), onOpenChange: (o) => !o && 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(''), children: "Keep editing" }), _jsx(Button, { size: "sm", variant: "destructive", onPress: () => host.go(leaving), children: "Leave" })] })] })] })] }));
|
|
383
371
|
}
|
|
384
372
|
function Page({ title, says, onBack }) {
|
|
385
|
-
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
|
|
373
|
+
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 scheduled tasks", children: [_jsx(ChevronLeft, { size: 14 }), "Scheduled tasks"] }) }), _jsx(SizableText, { render: "h1", size: "$6", color: "$ink", children: title }), _jsx(SizableText, { size: "$2", color: "$soft", children: says })] }) }));
|
|
386
374
|
}
|
package/lib/builder.js
CHANGED
|
@@ -4,11 +4,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
*
|
|
5
5
|
* / New — the empty state, a composer over the codebase it works on
|
|
6
6
|
* /sess_… one run, live
|
|
7
|
-
* /-/automations
|
|
7
|
+
* /-/automations scheduled tasks and what ran; /-/automations/<id> one, to edit and run
|
|
8
8
|
* /-/projects the org's repositories on the forge: a project is a repository
|
|
9
9
|
* /-/sync link repositories from GitHub onto the forge
|
|
10
10
|
* /-/issues the forge's issues
|
|
11
|
-
* /-/artifacts
|
|
11
|
+
* /-/artifacts the Library: everything you made, in Chat, Dev and Studio
|
|
12
12
|
* /-/templates the public starters
|
|
13
13
|
* /-/customize skills, connectors, plugins and agents
|
|
14
14
|
* /<org>/<repo> a repository's workspace, in this same window
|
|
@@ -47,7 +47,8 @@ import { Plans } from './plans.js';
|
|
|
47
47
|
import { path, route } from './route.js';
|
|
48
48
|
import { Run } from './run.js';
|
|
49
49
|
import { DOTS } from './section.js';
|
|
50
|
-
import {
|
|
50
|
+
import { Library } from './library.js';
|
|
51
|
+
import { Templates } from './shelf.js';
|
|
51
52
|
import { Find } from './find.js';
|
|
52
53
|
import { Slack, useWho, Who } from './foot.js';
|
|
53
54
|
import { Settings } from './settings/index.js';
|
|
@@ -111,7 +112,7 @@ function Pane({ onStarted }) {
|
|
|
111
112
|
return _jsx(Editor, { id: r.id }, r.id);
|
|
112
113
|
if (r.kind === 'screen') {
|
|
113
114
|
if (r.screen === 'artifacts')
|
|
114
|
-
return _jsx(
|
|
115
|
+
return _jsx(Library, {});
|
|
115
116
|
if (r.screen === 'templates')
|
|
116
117
|
return _jsx(Templates, {});
|
|
117
118
|
if (r.screen === 'plans')
|
package/lib/forge.js
CHANGED
|
@@ -16,7 +16,7 @@ import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from
|
|
|
16
16
|
import { useEffect, useMemo, useState } from 'react';
|
|
17
17
|
import { ago } from './ago.js';
|
|
18
18
|
import { codebases } from './api/codebases.js';
|
|
19
|
-
import {
|
|
19
|
+
import { Scheduled } from './scheduled.js';
|
|
20
20
|
import { boards, everything, sources } from './api/work.js';
|
|
21
21
|
import { boardKey, pinRepo, readPending, writePending } from './choice.js';
|
|
22
22
|
import { useKept, useRead } from './data.js';
|
|
@@ -40,7 +40,7 @@ const STATUS = {
|
|
|
40
40
|
};
|
|
41
41
|
export function Forge({ screen }) {
|
|
42
42
|
if (screen === 'automations')
|
|
43
|
-
return _jsx(
|
|
43
|
+
return _jsx(Scheduled, {});
|
|
44
44
|
if (screen === 'sync')
|
|
45
45
|
return _jsx(Sync, {});
|
|
46
46
|
// The fleet's native servers are Connectors → Discover now; the old address lands there.
|
package/lib/index.d.ts
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
* which loads in Node without the components.
|
|
19
19
|
*/
|
|
20
20
|
export { Builder } from './builder.tsx';
|
|
21
|
+
export { Library, Outputs, Preview, Thumb, want } from './library.tsx';
|
|
22
|
+
export { SOURCE, type Item, type Kind as ItemKind, type Source } from './api/library.ts';
|
|
23
|
+
export { Scheduled, schedule } from './scheduled.tsx';
|
|
24
|
+
export { Editor } from './automations.tsx';
|
|
21
25
|
export { nav, NEW, type Group } from './nav.tsx';
|
|
22
26
|
export { DevSection, DOTS, useSessions } from './section.tsx';
|
|
23
27
|
export { HostProvider } from './host.tsx';
|
|
@@ -45,4 +49,5 @@ export { catalog, EFFORTS, FIRST, keep, kept, nameOf, reasons, useCatalog, useCh
|
|
|
45
49
|
export { cut, GAP, MEASURE, measure, Prompt, prompt, ROUND, Tune, useEar, type PromptOptions } from './prompt.tsx';
|
|
46
50
|
export { artifacts, blob, lead, renders, Reply, Served, speakable, speaker, type Artifact, type Listen } from './reply.tsx';
|
|
47
51
|
export { frames, Page, Panel, PanelToggle, type Kind, type PageProps, type PanelProps, type Suggestion } from './panel.tsx';
|
|
52
|
+
export { kinds, Summary, useKinds, type Entry, type Section } from './summary.tsx';
|
|
48
53
|
export { address, destination, located, name, PAGE, SEARCH, useDeck, useKey, usePanel, useShown, type Deck, type Tab, type Tabs } from './tabs.ts';
|
package/lib/index.js
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
* which loads in Node without the components.
|
|
19
19
|
*/
|
|
20
20
|
export { Builder } from './builder.js';
|
|
21
|
+
export { Library, Outputs, Preview, Thumb, want } from './library.js';
|
|
22
|
+
export { SOURCE } from './api/library.js';
|
|
23
|
+
export { Scheduled, schedule } from './scheduled.js';
|
|
24
|
+
export { Editor } from './automations.js';
|
|
21
25
|
export { nav, NEW } from './nav.js';
|
|
22
26
|
export { DevSection, DOTS, useSessions } from './section.js';
|
|
23
27
|
export { HostProvider } from './host.js';
|
|
@@ -40,4 +44,5 @@ export { catalog, EFFORTS, FIRST, keep, kept, nameOf, reasons, useCatalog, useCh
|
|
|
40
44
|
export { cut, GAP, MEASURE, measure, Prompt, prompt, ROUND, Tune, useEar } from './prompt.js';
|
|
41
45
|
export { artifacts, blob, lead, renders, Reply, Served, speakable, speaker } from './reply.js';
|
|
42
46
|
export { frames, Page, Panel, PanelToggle } from './panel.js';
|
|
47
|
+
export { kinds, Summary, useKinds } from './summary.js';
|
|
43
48
|
export { address, destination, located, name, PAGE, SEARCH, useDeck, useKey, usePanel, useShown } from './tabs.js';
|
package/lib/library.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DropdownMenuProps } from '@hanzo/ui';
|
|
2
|
+
import { type Item } from './api/library.ts';
|
|
3
|
+
type MenuItems = NonNullable<DropdownMenuProps['items']>;
|
|
4
|
+
/** One square: the still, or the kind's mark on a quiet ground. */
|
|
5
|
+
export declare function Thumb({ x, size: n }: {
|
|
6
|
+
x: Item;
|
|
7
|
+
size?: number;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
/** Open the Library at one item: the move leaves its id for the screen to take. */
|
|
10
|
+
export declare function want(id: string): void;
|
|
11
|
+
/** One item, read fresh so its signed URLs are live: the thing itself, then what it is. */
|
|
12
|
+
export declare function Preview({ id, rows }: {
|
|
13
|
+
id: string;
|
|
14
|
+
rows?: (x: Item) => MenuItems;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
16
|
+
/** A run's outputs as small tiles: each opens in the Library. */
|
|
17
|
+
export declare function Outputs({ ids }: {
|
|
18
|
+
ids: string[];
|
|
19
|
+
}): import("react").JSX.Element | null;
|
|
20
|
+
export declare function Library(): import("react").JSX.Element;
|
|
21
|
+
export {};
|