@hanzo/build 0.2.79 → 0.2.81
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 +22 -4
- package/lib/activity.d.ts +3 -7
- package/lib/activity.js +18 -5
- package/lib/api/agents.d.ts +36 -35
- package/lib/api/agents.js +66 -29
- package/lib/api/auto.d.ts +3 -23
- package/lib/api/auto.js +39 -22
- package/lib/api/billing.d.ts +0 -35
- package/lib/api/billing.js +6 -5
- package/lib/api/call.d.ts +16 -8
- package/lib/api/call.js +32 -1
- package/lib/api/capabilities.d.ts +0 -11
- package/lib/api/capabilities.js +2 -1
- package/lib/api/codebases.d.ts +0 -10
- package/lib/api/codebases.js +3 -2
- package/lib/api/coding.d.ts +3 -32
- package/lib/api/coding.js +13 -7
- package/lib/api/connectors.d.ts +1 -17
- package/lib/api/connectors.js +5 -4
- package/lib/api/environment.d.ts +6 -20
- package/lib/api/environment.js +22 -19
- package/lib/api/github.d.ts +0 -22
- package/lib/api/github.js +3 -2
- package/lib/api/keys.d.ts +0 -14
- package/lib/api/keys.js +4 -3
- package/lib/api/library.d.ts +3 -17
- package/lib/api/library.js +23 -22
- package/lib/api/machines.d.ts +0 -15
- package/lib/api/machines.js +6 -5
- package/lib/api/mcp.d.ts +0 -9
- package/lib/api/mcp.js +3 -2
- package/lib/api/members.d.ts +0 -15
- package/lib/api/members.js +4 -3
- package/lib/api/memory.d.ts +0 -11
- package/lib/api/memory.js +2 -1
- package/lib/api/places.d.ts +1 -9
- package/lib/api/places.js +12 -1
- package/lib/api/plugins.d.ts +1 -16
- package/lib/api/plugins.js +4 -3
- package/lib/api/profile.d.ts +0 -10
- package/lib/api/profile.js +5 -4
- package/lib/api/projects.d.ts +0 -14
- package/lib/api/projects.js +2 -1
- package/lib/api/provider.d.ts +2 -14
- package/lib/api/provider.js +12 -10
- package/lib/api/sandbox.d.ts +0 -17
- package/lib/api/sandbox.js +5 -4
- package/lib/api/sessions.d.ts +0 -17
- package/lib/api/sessions.js +5 -4
- package/lib/api/skills.d.ts +1 -15
- package/lib/api/skills.js +5 -4
- package/lib/api/studio.d.ts +0 -14
- package/lib/api/studio.js +6 -5
- package/lib/api/turn.d.ts +6 -28
- package/lib/api/turn.js +105 -49
- package/lib/api/verdict.d.ts +0 -9
- package/lib/api/verdict.js +2 -1
- package/lib/api/webhooks.d.ts +1 -14
- package/lib/api/webhooks.js +5 -4
- package/lib/ask.js +7 -4
- package/lib/automations.d.ts +5 -4
- package/lib/automations.js +95 -74
- package/lib/back.js +10 -4
- package/lib/builder.js +7 -5
- package/lib/carry.js +20 -16
- package/lib/customize/agents.d.ts +3 -0
- package/lib/customize/agents.js +152 -55
- package/lib/customize/connectors.js +62 -48
- package/lib/customize/index.js +45 -14
- package/lib/customize/plugins.js +34 -22
- package/lib/customize/say.d.ts +13 -1
- package/lib/customize/say.js +15 -21
- package/lib/customize/skills.js +31 -22
- package/lib/customize/ui.d.ts +5 -9
- package/lib/customize/ui.js +26 -16
- package/lib/data.d.ts +1 -1
- package/lib/data.js +3 -2
- package/lib/desk.js +45 -33
- package/lib/door.js +35 -10
- package/lib/environment.js +25 -19
- package/lib/files.js +43 -34
- package/lib/find.js +12 -9
- package/lib/foot.js +7 -4
- package/lib/forge.js +61 -40
- package/lib/gear.d.ts +2 -1
- package/lib/gear.js +6 -4
- package/lib/git.js +29 -19
- package/lib/host.d.ts +3 -1
- package/lib/landing.d.ts +5 -5
- package/lib/landing.js +52 -44
- package/lib/library.js +73 -51
- package/lib/meter.d.ts +1 -1
- package/lib/meter.js +14 -5
- package/lib/mic.js +12 -5
- package/lib/mind.d.ts +1 -0
- package/lib/mind.js +5 -3
- package/lib/nav.d.ts +6 -5
- package/lib/nav.js +37 -30
- package/lib/panel.d.ts +2 -1
- package/lib/panel.js +52 -36
- package/lib/plan.d.ts +4 -20
- package/lib/plan.js +26 -10
- package/lib/plans.js +49 -30
- package/lib/plugins/design.js +3 -2
- package/lib/plugins/index.d.ts +23 -4
- package/lib/plugins/index.js +11 -26
- package/lib/plugins/pdf.js +2 -1
- package/lib/plugins/project.js +6 -3
- package/lib/plugins/research.js +3 -2
- package/lib/plugins/sketch.js +6 -3
- package/lib/plugins/slides.js +3 -2
- package/lib/plugins/template.js +6 -3
- package/lib/plus.js +25 -13
- package/lib/prefs.d.ts +0 -14
- package/lib/prefs.js +2 -1
- package/lib/project.js +69 -62
- package/lib/prompt.d.ts +12 -11
- package/lib/prompt.js +38 -20
- package/lib/publish.js +17 -12
- package/lib/reply.js +6 -3
- package/lib/run.js +72 -54
- package/lib/say.d.ts +2 -0
- package/lib/say.js +7 -0
- package/lib/scheduled.js +54 -43
- package/lib/section.d.ts +1 -0
- package/lib/section.js +8 -2
- package/lib/settings/account.d.ts +1 -0
- package/lib/settings/account.js +31 -21
- package/lib/settings/billing.js +55 -28
- package/lib/settings/capabilities.js +26 -15
- package/lib/settings/card.js +19 -12
- package/lib/settings/code.js +22 -11
- package/lib/settings/credits.d.ts +4 -4
- package/lib/settings/credits.js +34 -21
- package/lib/settings/environments.js +21 -14
- package/lib/settings/general.js +26 -14
- package/lib/settings/index.js +6 -3
- package/lib/settings/integrations.js +42 -28
- package/lib/settings/keys.js +37 -19
- package/lib/settings/list.d.ts +0 -5
- package/lib/settings/list.js +24 -15
- package/lib/settings/machines.d.ts +3 -0
- package/lib/settings/machines.js +63 -38
- package/lib/settings/members.js +23 -14
- package/lib/settings/memory.js +12 -6
- package/lib/settings/notifications.js +32 -19
- package/lib/settings/plan.d.ts +1 -1
- package/lib/settings/plan.js +40 -17
- package/lib/settings/privacy.js +26 -13
- package/lib/settings/ui.d.ts +3 -2
- package/lib/settings/ui.js +9 -3
- package/lib/settings/usage.js +7 -3
- package/lib/shelf.js +18 -15
- package/lib/side.d.ts +1 -26
- package/lib/side.js +11 -5
- package/lib/standing.d.ts +2 -10
- package/lib/standing.js +5 -3
- package/lib/suggest.d.ts +8 -28
- package/lib/suggest.js +39 -4
- package/lib/suggestions.js +14 -5
- package/lib/summary.d.ts +1 -1
- package/lib/summary.js +4 -1
- package/lib/sync.js +22 -12
- package/lib/tools.d.ts +4 -3
- package/lib/tools.js +10 -7
- package/lib/transcript.js +10 -8
- package/package.json +4 -2
package/lib/customize/skills.js
CHANGED
|
@@ -7,8 +7,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
7
7
|
* all adding is: the platform keeps no copy. Yours is the org's own skills —
|
|
8
8
|
* written here, each with its switch — and the catalogue's skills it has added.
|
|
9
9
|
* A skill written here is switched on when it is saved.
|
|
10
|
+
*
|
|
11
|
+
* Its words are the reader's language (@hanzo/i18n `t`); a skill's name,
|
|
12
|
+
* description and SKILL.md are the platform's or the org's, drawn as given.
|
|
10
13
|
*/
|
|
11
14
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
15
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
12
16
|
import { BookOpen } from '@hanzogui/lucide-icons-2';
|
|
13
17
|
import { Button, Input, Switch, Textarea } from '@hanzo/ui';
|
|
14
18
|
import { Code } from '@hanzo/ui/chat';
|
|
@@ -21,14 +25,15 @@ import { say } from './say.js';
|
|
|
21
25
|
import { Add, Confirm, day, Empty, Failed, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
|
|
22
26
|
const PAGE = 60;
|
|
23
27
|
export function Skills({ view, q, adding, onAdding, onView, onCount }) {
|
|
28
|
+
const { t } = useI18n();
|
|
24
29
|
const host = useHost();
|
|
25
|
-
const
|
|
30
|
+
const target = useTarget();
|
|
26
31
|
const signed = Boolean(host.person);
|
|
27
32
|
// What every run in the org loads is an org admin's to change; a member reads it.
|
|
28
33
|
const may = signed && host.admin;
|
|
29
|
-
const own = useLoad(signed ? () => authored(
|
|
30
|
-
const on = useLoad(signed ? () => active(
|
|
31
|
-
const shelf = useLoad(view === 'discover' ? () => brand(
|
|
34
|
+
const own = useLoad(signed ? () => authored(target) : null, [], [target, signed]);
|
|
35
|
+
const on = useLoad(signed ? () => active(target) : null, [], [target, signed]);
|
|
36
|
+
const shelf = useLoad(view === 'discover' ? () => brand(target) : null, null, [target.api, view]);
|
|
32
37
|
const [editing, setEditing] = useState(null);
|
|
33
38
|
const [reading, setReading] = useState(null);
|
|
34
39
|
const [busy, setBusy] = useState('');
|
|
@@ -43,12 +48,12 @@ export function Skills({ view, q, adding, onAdding, onView, onCount }) {
|
|
|
43
48
|
setBusy(name);
|
|
44
49
|
setNote('');
|
|
45
50
|
try {
|
|
46
|
-
await toggle(
|
|
51
|
+
await toggle(target, next ? [tool(name)] : [], next ? [] : [tool(name)]);
|
|
47
52
|
on.reload();
|
|
48
|
-
setNote(next ?
|
|
53
|
+
setNote(next ? t('{name} is on', { name }) : t('{name} is off', { name }));
|
|
49
54
|
}
|
|
50
55
|
catch (e) {
|
|
51
|
-
setNote(say(e));
|
|
56
|
+
setNote(say(e, t));
|
|
52
57
|
}
|
|
53
58
|
finally {
|
|
54
59
|
setBusy('');
|
|
@@ -64,20 +69,20 @@ export function Skills({ view, q, adding, onAdding, onView, onCount }) {
|
|
|
64
69
|
own.reload();
|
|
65
70
|
on.reload();
|
|
66
71
|
onView('yours');
|
|
67
|
-
setNote(fresh ?
|
|
72
|
+
setNote(fresh ? t('{name} is saved and on', { name: s.name }) : t('{name} is saved', { name: s.name }));
|
|
68
73
|
}, onDeleted: (name) => {
|
|
69
74
|
setEditing(null);
|
|
70
75
|
own.reload();
|
|
71
76
|
on.reload();
|
|
72
|
-
setNote(
|
|
77
|
+
setNote(t('{name} is deleted', { name }));
|
|
73
78
|
} })) : null;
|
|
74
79
|
if (view === 'discover') {
|
|
75
80
|
const all = shelf.value?.skills ?? [];
|
|
76
81
|
const found = all.filter((e) => matches(q, e.name, e.description, e.product));
|
|
77
|
-
return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), _jsx(Part, { title:
|
|
82
|
+
return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), _jsx(Part, { title: t('From Hanzo'), detail: t('Add one and every run in your organization can use it.'), children: shelf.error && !shelf.value ? (_jsx(Failed, { error: shelf.error, onRetry: shelf.reload })) : !shelf.value ? (_jsx(Soft, { children: t('Loading skills…') })) : !found.length ? (_jsx(Soft, { children: all.length ? t('No skill matches “{q}”.', { q: q.trim() }) : t('There are no skills to add yet.') })) : (_jsxs(_Fragment, { children: [_jsx(SizableText, { size: "$1", color: "$soft", children: found.length === all.length ? t('{n} skills', { n: all.length }) : t('{found} of {n} skills', { found: found.length, n: all.length }) }), _jsx(Grid, { label: t('Skills to add'), children: found.slice(0, shown).map((e) => (_jsx(Tile, { title: e.name, detail: e.description, meta: e.product, mark: _jsx(Mark, { name: e.name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading(e), action: may ? _jsx(Add, { name: e.name, added: lit.has(tool(e.name)), busy: busy === e.name, onPress: () => void flip(e.name, true) }) : undefined }, e.name))) }), found.length > shown ? (_jsx(XStack, { justify: "center", children: _jsx(Button, { size: "sm", variant: "outline", onPress: () => setShown(shown + PAGE), children: t('Show more') }) })) : null] })) }), reader, editor] }));
|
|
78
83
|
}
|
|
79
84
|
if (!signed) {
|
|
80
|
-
return _jsx(Visitor, { children:
|
|
85
|
+
return _jsx(Visitor, { children: t('Sign in to see your skills.') });
|
|
81
86
|
}
|
|
82
87
|
const ownShown = own.value.filter((s) => matches(q, s.name, s.description));
|
|
83
88
|
const addedShown = added.filter((x) => matches(q, nameOf(x.name), x.description));
|
|
@@ -85,9 +90,11 @@ export function Skills({ view, q, adding, onAdding, onView, onCount }) {
|
|
|
85
90
|
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Line, { children: note }), error && !own.value.length && !on.value.length ? (_jsx(Failed, { error: error, onRetry: () => {
|
|
86
91
|
own.reload();
|
|
87
92
|
on.reload();
|
|
88
|
-
} })) : (own.loading || on.loading) && !own.value.length && !on.value.length ? (_jsx(Soft, { children:
|
|
93
|
+
} })) : (own.loading || on.loading) && !own.value.length && !on.value.length ? (_jsx(Soft, { children: t('Loading your skills…') })) : !own.value.length && !added.length ? (_jsxs(Empty, { title: t('No skills yet'), detail: may ? t('Add one from Hanzo, or write your own.') : t('An org admin adds skills. Browse what there is.'), children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: t('Browse skills') }), may ? (_jsx(Button, { size: "sm", onPress: () => onAdding(true), children: t('Write a skill') })) : null] })) : !ownShown.length && !addedShown.length ? (_jsx(Soft, { children: t('No skill of yours matches “{q}”.', { q: q.trim() }) })) : (_jsxs(_Fragment, { children: [ownShown.length ? (_jsx(Part, { title: t('Made by your team'), detail: t('Off keeps a skill but leaves it out of every run.'), children: _jsx(Grid, { label: t('Your skills'), children: ownShown.map((s) => (_jsx(Tile, { title: s.name, detail: s.description || firstLine(s.content), meta: [s.source ? t('From {source}', { source: s.source }) : day(s.created) ? t('Saved {date}', { date: day(s.created) }) : '', s.admitted ? '' : t('Not in runs until an admin saves it')]
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
.join(' · ') || undefined, mark: _jsx(Mark, { name: s.name }), onOpen: () => setEditing(s), action: may ? (_jsx(Switch, { checked: lit.has(tool(s.name)), disabled: busy === s.name, onCheckedChange: (v) => void flip(s.name, v), "aria-label": t('{name} on', { name: s.name }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: lit.has(tool(s.name)) ? t('On') : t('Off') })) }, s.id))) }) })) : null, addedShown.length ? (_jsx(Part, { title: t('Added from Hanzo'), detail: t('On for every run in your organization.'), children: _jsx(Grid, { label: t('Added skills'), children: addedShown.map((x) => {
|
|
89
96
|
const name = nameOf(x.name);
|
|
90
|
-
return (_jsx(Tile, { title: name, detail: x.description, mark: _jsx(Mark, { name: name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading({ name, description: x.description, product: '' }), action: may ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: busy === name, onPress: () => void flip(name, false), "aria-label":
|
|
97
|
+
return (_jsx(Tile, { title: name, detail: x.description, mark: _jsx(Mark, { name: name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading({ name, description: x.description, product: '' }), action: may ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: busy === name, onPress: () => void flip(name, false), "aria-label": t('Remove {name}', { name }), children: t('Remove') })) : undefined }, x.name));
|
|
91
98
|
}) }) })) : null] })), reader, editor] }));
|
|
92
99
|
}
|
|
93
100
|
const firstLine = (md) => md
|
|
@@ -96,13 +103,15 @@ const firstLine = (md) => md
|
|
|
96
103
|
.find(Boolean) ?? '';
|
|
97
104
|
/** One catalogue skill: what it is, its SKILL.md, and adding or removing it. */
|
|
98
105
|
function Reader({ skill, added, signed, busy, onFlip, onClose, }) {
|
|
99
|
-
const t =
|
|
100
|
-
const
|
|
101
|
-
|
|
106
|
+
const { t } = useI18n();
|
|
107
|
+
const target = useTarget();
|
|
108
|
+
const doc = useLoad(() => document(target, skill.name), '', [target.api, skill.name]);
|
|
109
|
+
return (_jsxs(Sheet, { title: skill.name, open: true, onOpenChange: (o) => !o && onClose(), width: 720, children: [skill.description ? (_jsx(SizableText, { size: "$2", color: "$soft", children: skill.description })) : null, signed ? (_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(Button, { size: "sm", variant: added ? 'outline' : 'default', disabled: busy, onPress: () => onFlip(!added), children: added ? t('Remove') : t('Add to your skills') }), _jsx(SizableText, { size: "$1", color: "$soft", flex: 1, children: added ? t('On for your organization.') : t('Every run in your organization can use it once added.') })] })) : null, doc.error ? (_jsx(Failed, { error: doc.error, onRetry: doc.reload })) : doc.loading && !doc.value ? (_jsx(Soft, { children: t('Loading SKILL.md…') })) : (_jsx(Code, { language: "SKILL.md", value: doc.value, children: _jsx(SizableText, { size: "$1", color: "$ink", style: { ...mono, whiteSpace: 'pre-wrap' }, children: doc.value }) }))] }));
|
|
102
110
|
}
|
|
103
111
|
/** Write a skill, or revise one of the org's own. Its name is its id, so a saved skill keeps its name. */
|
|
104
112
|
function Editor({ skill, may, onClose, onSaved, onDeleted, }) {
|
|
105
|
-
const t =
|
|
113
|
+
const { t } = useI18n();
|
|
114
|
+
const target = useTarget();
|
|
106
115
|
const [name, setName] = useState(skill?.name ?? '');
|
|
107
116
|
const [description, setDescription] = useState(skill?.description ?? '');
|
|
108
117
|
const [content, setContent] = useState(skill?.content ?? '');
|
|
@@ -113,21 +122,21 @@ function Editor({ skill, may, onClose, onSaved, onDeleted, }) {
|
|
|
113
122
|
setWorking(true);
|
|
114
123
|
setNote('');
|
|
115
124
|
try {
|
|
116
|
-
const saved = await write(
|
|
125
|
+
const saved = await write(target, { name: name.trim(), description: description.trim(), content });
|
|
117
126
|
// A new skill is switched on as it is saved; a revised one keeps its switch.
|
|
118
127
|
if (!skill)
|
|
119
|
-
await toggle(
|
|
128
|
+
await toggle(target, [tool(saved.name)]);
|
|
120
129
|
onSaved(saved, !skill);
|
|
121
130
|
}
|
|
122
131
|
catch (e) {
|
|
123
|
-
setNote(say(e));
|
|
132
|
+
setNote(say(e, t));
|
|
124
133
|
}
|
|
125
134
|
finally {
|
|
126
135
|
setWorking(false);
|
|
127
136
|
}
|
|
128
137
|
};
|
|
129
|
-
return (_jsxs(Sheet, { title: skill ? skill.name : 'New skill', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: [skill?.source ? (
|
|
130
|
-
await remove(
|
|
138
|
+
return (_jsxs(Sheet, { title: skill ? skill.name : t('New skill'), open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: [skill?.source ? (_jsx(SizableText, { size: "$2", color: "$soft", children: t('Read from {source}. The next push of that repository replaces what is saved here.', { source: skill.source }) })) : null, _jsx(Field, { label: t('Name'), hint: skill ? t('A skill keeps its name. Save under another name to make a new one.') : t('One lowercase word: letters, digits, _ or -.'), children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": t('Name'), disabled: Boolean(skill), autoCapitalize: "none" }) }), _jsx(Field, { label: t('Description'), hint: t('One line. An agent reads it to decide whether it needs the skill.'), children: _jsx(Input, { value: description, onChangeText: setDescription, placeholder: t('How we triage an incoming issue'), "aria-label": t('Description') }) }), _jsx(Field, { label: "SKILL.md", hint: t('Markdown: what it is for, when to use it, and the steps.'), children: _jsx(Textarea, { value: content, onChangeText: setContent, placeholder: t('# Triage\n\n1. Read the issue…'), "aria-label": "SKILL.md", rows: 14, fontFamily: "$mono" }) }), _jsx(Line, { children: note || (may ? '' : t('Only an org admin can write or delete skills.')) }), _jsxs(XStack, { gap: "$2", items: "center", children: [skill && may ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: t('Delete') })) : null, _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: may ? t('Cancel') : t('Close') }), may ? (_jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: t('Save') })) : null] }), skill ? (_jsx(Confirm, { what: skill.name, says: t('The skill leaves your organization and every agent that names it.'), open: asking, onOpenChange: setAsking, onYes: async () => {
|
|
139
|
+
await remove(target, skill.id);
|
|
131
140
|
onDeleted(skill.name);
|
|
132
141
|
} })) : null] }));
|
|
133
142
|
}
|
package/lib/customize/ui.d.ts
CHANGED
|
@@ -2,10 +2,6 @@ import { type ReactNode } from 'react';
|
|
|
2
2
|
export declare const mono: {
|
|
3
3
|
fontFamily: string;
|
|
4
4
|
};
|
|
5
|
-
/** A button that holds a card's text reads from the left, not from the middle a button centres it in. */
|
|
6
|
-
export declare const left: {
|
|
7
|
-
readonly textAlign: 'left';
|
|
8
|
-
};
|
|
9
5
|
/** Which half of a tab: what there is to browse and add (first), or what the org already has. */
|
|
10
6
|
export type View = 'discover' | 'yours';
|
|
11
7
|
/** What the shell hands a tab: which half, the search, the Add dialog's state, and where to say how many are yours. */
|
|
@@ -55,13 +51,13 @@ export declare function Featured({ title, detail, meta, mark, action, onOpen }:
|
|
|
55
51
|
action?: ReactNode;
|
|
56
52
|
onOpen: () => void;
|
|
57
53
|
}): import("react").JSX.Element;
|
|
58
|
-
/** A card's add control: Add while it is not yours, Added once it is. */
|
|
59
|
-
export declare function Add({ name, added, busy, onPress,
|
|
54
|
+
/** A card's add control: Add (Use, for a template one is made from) while it is not yours, Added once it is. */
|
|
55
|
+
export declare function Add({ name, added, busy, onPress, use }: {
|
|
60
56
|
name: string;
|
|
61
57
|
added: boolean;
|
|
62
58
|
busy?: boolean;
|
|
63
59
|
onPress: () => void;
|
|
64
|
-
|
|
60
|
+
use?: boolean;
|
|
65
61
|
}): import("react").JSX.Element;
|
|
66
62
|
/** A dialog with a title, a close control and a body that scrolls inside the window. */
|
|
67
63
|
export declare function Sheet({ title, open, onOpenChange, width, children }: {
|
|
@@ -71,7 +67,7 @@ export declare function Sheet({ title, open, onOpenChange, width, children }: {
|
|
|
71
67
|
width?: number;
|
|
72
68
|
children: ReactNode;
|
|
73
69
|
}): import("react").JSX.Element;
|
|
74
|
-
/** Asks before a delete; says what goes with it. */
|
|
70
|
+
/** Asks before a delete; says what goes with it. `what` is the thing's name, `says` comes already said. */
|
|
75
71
|
export declare function Confirm({ what, says, open, onOpenChange, onYes }: {
|
|
76
72
|
what: string;
|
|
77
73
|
says: string;
|
|
@@ -116,7 +112,7 @@ export declare function Field({ label, hint, children }: {
|
|
|
116
112
|
hint?: string;
|
|
117
113
|
children: ReactNode;
|
|
118
114
|
}): import("react").JSX.Element;
|
|
119
|
-
/** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. */
|
|
115
|
+
/** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. Labels come already said. */
|
|
120
116
|
export declare function Choice<T extends string>({ value, options, onChange, label, }: {
|
|
121
117
|
value: T;
|
|
122
118
|
options: readonly {
|
package/lib/customize/ui.js
CHANGED
|
@@ -5,16 +5,19 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
5
5
|
* featured card, a dialog with a title and a scrolling body, a delete that asks
|
|
6
6
|
* first, the empty state that names the next step, a failure with its retry,
|
|
7
7
|
* and the quiet line for loading and signed-out.
|
|
8
|
+
*
|
|
9
|
+
* Their own words are the reader's language (@hanzo/i18n `t`); a title, a
|
|
10
|
+
* line or a label a tab hands them comes already said, since it may be a
|
|
11
|
+
* name or a description that is drawn as given.
|
|
8
12
|
*/
|
|
9
13
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
14
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
10
15
|
import { Check, Plus, X } from '@hanzogui/lucide-icons-2';
|
|
11
16
|
import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
|
|
12
17
|
import { useEffect, useState } from 'react';
|
|
13
18
|
import { useHost } from '../host.js';
|
|
14
19
|
import { say } from './say.js';
|
|
15
20
|
export const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
|
|
16
|
-
/** A button that holds a card's text reads from the left, not from the middle a button centres it in. */
|
|
17
|
-
export const left = { textAlign: 'left' };
|
|
18
21
|
/** Tells the shell how many are yours, whenever that changes. */
|
|
19
22
|
export function useCount(n, onCount) {
|
|
20
23
|
useEffect(() => onCount(n), [n, onCount]);
|
|
@@ -37,25 +40,29 @@ export function Mark({ name, logo, icon, size = 32 }) {
|
|
|
37
40
|
*/
|
|
38
41
|
export function Tile({ title, detail, meta, mark, action, onOpen, }) {
|
|
39
42
|
const body = (_jsxs(_Fragment, { children: [mark, _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", numberOfLines: 1, children: title }), detail ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 3, children: detail })) : null, meta ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, opacity: 0.8, children: meta })) : null] })] }));
|
|
40
|
-
return (_jsxs(XStack, { role: "listitem", items: "flex-start", gap: "$2", p: "$3", minW: 0, rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", hoverStyle: onOpen ? { borderColor: '$edge' } : undefined, children: [onOpen ? (_jsx(XStack, { render: "button", "aria-label": title, onPress: onOpen, flex: 1, minW: 0, gap: "$3", items: "flex-start", cursor: "pointer",
|
|
43
|
+
return (_jsxs(XStack, { role: "listitem", items: "flex-start", gap: "$2", p: "$3", minW: 0, rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", hoverStyle: onOpen ? { borderColor: '$edge' } : undefined, children: [onOpen ? (_jsx(XStack, { render: "button", "aria-label": title, onPress: onOpen, flex: 1, minW: 0, gap: "$3", items: "flex-start", cursor: "pointer", "$platform-web": { textAlign: 'start' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: body })) : (_jsx(XStack, { flex: 1, minW: 0, gap: "$3", items: "flex-start", children: body })), action] }));
|
|
41
44
|
}
|
|
42
45
|
/** The one card the shelf puts first, drawn larger. */
|
|
43
46
|
export function Featured({ title, detail, meta, mark, action, onOpen }) {
|
|
44
|
-
|
|
47
|
+
const { t } = useI18n();
|
|
48
|
+
return (_jsxs(XStack, { items: "flex-start", gap: "$4", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", flexWrap: "wrap", rowGap: "$3", children: [_jsxs(XStack, { render: "button", "aria-label": title, onPress: onOpen, flex: 1, minW: 220, gap: "$4", items: "flex-start", cursor: "pointer", "$platform-web": { textAlign: 'start' }, children: [mark, _jsxs(YStack, { flex: 1, minW: 0, gap: "$1.5", children: [_jsx(SizableText, { size: "$1", color: "$soft", textTransform: "uppercase", letterSpacing: 1, children: t('Featured') }), _jsx(SizableText, { size: "$6", color: "$ink", numberOfLines: 1, children: title }), _jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 3, children: detail }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: meta })] })] }), action] }));
|
|
45
49
|
}
|
|
46
|
-
/** A card's add control: Add while it is not yours, Added once it is. */
|
|
47
|
-
export function Add({ name, added, busy, onPress,
|
|
50
|
+
/** A card's add control: Add (Use, for a template one is made from) while it is not yours, Added once it is. */
|
|
51
|
+
export function Add({ name, added, busy, onPress, use = false }) {
|
|
52
|
+
const { t } = useI18n();
|
|
48
53
|
if (added) {
|
|
49
|
-
return (_jsxs(XStack, { "aria-label":
|
|
54
|
+
return (_jsxs(XStack, { "aria-label": t('{name} is added', { name }), gap: "$1", height: 32, items: "center", shrink: 0, children: [_jsx(Check, { size: 14 }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Added') })] }));
|
|
50
55
|
}
|
|
51
|
-
return (_jsxs(Button, { size: "sm", variant: "outline", "aria-label":
|
|
56
|
+
return (_jsxs(Button, { size: "sm", variant: "outline", "aria-label": use ? t('Use {name}', { name }) : t('Add {name}', { name }), disabled: busy, onPress: onPress, children: [_jsx(Plus, { size: 14 }), " ", use ? t('Use') : t('Add')] }));
|
|
52
57
|
}
|
|
53
58
|
/** A dialog with a title, a close control and a body that scrolls inside the window. */
|
|
54
59
|
export function Sheet({ title, open, onOpenChange, width = 560, children }) {
|
|
55
|
-
|
|
60
|
+
const { t } = useI18n();
|
|
61
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: width, showCloseButton: false, maxH: "88vh", gap: "$3", children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { numberOfLines: 1, flex: 1, children: title }), _jsx(XStack, { render: "button", "aria-label": t('Close'), p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsx(YStack, { gap: "$3", overflow: "scroll", minH: 0, shrink: 1, children: children })] }) }));
|
|
56
62
|
}
|
|
57
|
-
/** Asks before a delete; says what goes with it. */
|
|
63
|
+
/** Asks before a delete; says what goes with it. `what` is the thing's name, `says` comes already said. */
|
|
58
64
|
export function Confirm({ what, says, open, onOpenChange, onYes }) {
|
|
65
|
+
const { t } = useI18n();
|
|
59
66
|
const [busy, setBusy] = useState(false);
|
|
60
67
|
const [note, setNote] = useState('');
|
|
61
68
|
const yes = async () => {
|
|
@@ -66,13 +73,13 @@ export function Confirm({ what, says, open, onOpenChange, onYes }) {
|
|
|
66
73
|
onOpenChange(false);
|
|
67
74
|
}
|
|
68
75
|
catch (e) {
|
|
69
|
-
setNote(say(e));
|
|
76
|
+
setNote(say(e, t));
|
|
70
77
|
}
|
|
71
78
|
finally {
|
|
72
79
|
setBusy(false);
|
|
73
80
|
}
|
|
74
81
|
};
|
|
75
|
-
return (_jsxs(Sheet, { title:
|
|
82
|
+
return (_jsxs(Sheet, { title: t('Delete {name}?', { name: what }), open: open, onOpenChange: onOpenChange, width: 420, children: [_jsx(SizableText, { size: "$2", color: "$soft", children: says }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: t('Cancel') }), _jsx(Button, { size: "sm", variant: "destructive", disabled: busy, onPress: () => void yes(), children: t('Delete') })] })] }));
|
|
76
83
|
}
|
|
77
84
|
/** The quiet line for loading, a search that matched nothing, and signed-out. */
|
|
78
85
|
export function Soft({ children, action }) {
|
|
@@ -80,7 +87,8 @@ export function Soft({ children, action }) {
|
|
|
80
87
|
}
|
|
81
88
|
/** A read that failed: what went wrong in plain words, and the one control that asks again. */
|
|
82
89
|
export function Failed({ error, onRetry }) {
|
|
83
|
-
|
|
90
|
+
const { t } = useI18n();
|
|
91
|
+
return (_jsxs(YStack, { py: "$6", items: "center", gap: "$3", role: "alert", children: [_jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: say(error, t) }), _jsx(Button, { size: "sm", variant: "outline", onPress: onRetry, children: t('Try again') })] }));
|
|
84
92
|
}
|
|
85
93
|
/** Nothing here yet: what this place holds, and the next step, as buttons. */
|
|
86
94
|
export function Empty({ title, detail, children }) {
|
|
@@ -88,8 +96,9 @@ export function Empty({ title, detail, children }) {
|
|
|
88
96
|
}
|
|
89
97
|
/** What a signed-out visitor is told, with the one control that signs them in. */
|
|
90
98
|
export function Visitor({ children }) {
|
|
99
|
+
const { t } = useI18n();
|
|
91
100
|
const host = useHost();
|
|
92
|
-
return (_jsx(Soft, { action: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children:
|
|
101
|
+
return (_jsx(Soft, { action: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: t('Sign in') }), children: children }));
|
|
93
102
|
}
|
|
94
103
|
/** What the last action did, said once, where it was done. */
|
|
95
104
|
export function Line({ children }) {
|
|
@@ -105,9 +114,10 @@ export function Part({ title, detail, action, children }) {
|
|
|
105
114
|
export function Field({ label, hint, children }) {
|
|
106
115
|
return (_jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: label }), children, hint ? (_jsx(SizableText, { size: "$1", color: "$soft", children: hint })) : null] }));
|
|
107
116
|
}
|
|
108
|
-
/** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. */
|
|
117
|
+
/** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. Labels come already said. */
|
|
109
118
|
export function Choice({ value, options, onChange, label, }) {
|
|
110
|
-
|
|
119
|
+
const { t } = useI18n();
|
|
120
|
+
return (_jsx(XStack, { role: "group", "aria-label": label, p: 2, gap: 2, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", self: "flex-start", shrink: 0, children: options.map((o) => (_jsxs(XStack, { render: "button", "aria-pressed": o.id === value, "aria-label": o.count ? t('{label}, {count}', { label: o.label, count: o.count }) : o.label, onPress: () => onChange(o.id), items: "center", gap: "$1.5", px: "$3", py: "$1", rounded: "$2", bg: o.id === value ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: o.id === value ? '$ink' : '$soft', children: o.label }), o.count ? (_jsx(SizableText, { size: "$1", color: "$soft", fontVariant: ['tabular-nums'], children: o.count })) : null] }, o.id))) }));
|
|
111
121
|
}
|
|
112
122
|
/** A date from Unix seconds, or ''. */
|
|
113
123
|
export const day = (s) => (s ? new Date(s * 1000).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }) : '');
|
package/lib/data.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface Read<T> {
|
|
|
12
12
|
export declare function useRead<T>(load: (() => Promise<T>) | null, initial: T, key: unknown[]): Read<T>;
|
|
13
13
|
/** How long a read is waited on before the screen says it is taking too long. */
|
|
14
14
|
export declare const WAIT = 15000;
|
|
15
|
-
/** A read that did not answer in time. */
|
|
15
|
+
/** A read that did not answer in time. Its message is ours, marked, so a screen drawing it through `t` says it in the reader's language. */
|
|
16
16
|
export declare class Slow extends Error {
|
|
17
17
|
constructor();
|
|
18
18
|
}
|
package/lib/data.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* signed in is not attempted — the platform would refuse it and the refusal
|
|
7
7
|
* would read as an outage.
|
|
8
8
|
*/
|
|
9
|
+
import { phrase } from '@hanzo/i18n';
|
|
9
10
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
10
11
|
import { places } from './api/places.js';
|
|
11
12
|
import { projects } from './api/projects.js';
|
|
@@ -45,10 +46,10 @@ export function useRead(load, initial, key) {
|
|
|
45
46
|
}
|
|
46
47
|
/** How long a read is waited on before the screen says it is taking too long. */
|
|
47
48
|
export const WAIT = 15_000;
|
|
48
|
-
/** A read that did not answer in time. */
|
|
49
|
+
/** A read that did not answer in time. Its message is ours, marked, so a screen drawing it through `t` says it in the reader's language. */
|
|
49
50
|
export class Slow extends Error {
|
|
50
51
|
constructor() {
|
|
51
|
-
super('Hanzo is taking too long to answer.');
|
|
52
|
+
super(phrase('Hanzo is taking too long to answer.'));
|
|
52
53
|
this.name = 'Slow';
|
|
53
54
|
}
|
|
54
55
|
}
|
package/lib/desk.js
CHANGED
|
@@ -8,8 +8,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
* The sandbox outlives the run: kept when the run ends, parked while nobody
|
|
9
9
|
* watches, retired a day after it parks. The bar under the tabs says which and
|
|
10
10
|
* suspends or resumes it; a tab that needs it running resumes it (sandbox.ts).
|
|
11
|
+
*
|
|
12
|
+
* Its words are the reader's language (@hanzo/i18n `t`); a repository, branch,
|
|
13
|
+
* port, address, command and the sandbox's own error are drawn as given.
|
|
11
14
|
*/
|
|
12
15
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
16
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
13
17
|
import { ExternalLink, Info, MoreHorizontal, RefreshCcw } from '@hanzogui/lucide-icons-2';
|
|
14
18
|
import { Button, DropdownMenu } from '@hanzo/ui';
|
|
15
19
|
import { PreviewFrame } from '@hanzo/ui/agents';
|
|
@@ -38,38 +42,39 @@ export const SEED = {
|
|
|
38
42
|
const EVERY = 10_000;
|
|
39
43
|
const PORTS = 5_000;
|
|
40
44
|
export function Desk({ id, repo, branch, base, environment, mode, pr, title, project, sandbox, site, published, events, live, menu, refused, retry, onRetry, side, sheet, onHide, }) {
|
|
45
|
+
const { t: say } = useI18n();
|
|
41
46
|
const name = repo.split('/').filter(Boolean).pop() || repo;
|
|
42
47
|
const lines = shell(events);
|
|
43
|
-
const failed = failure(events);
|
|
48
|
+
const failed = failure(events, say);
|
|
44
49
|
const box = useBox(sandbox);
|
|
45
50
|
// Nothing is said of the sandbox until it has been read.
|
|
46
51
|
const held = box.value?.state ?? '';
|
|
47
|
-
const environ = () => side.open({ kind: 'environment', title: 'Environment' });
|
|
52
|
+
const environ = () => side.open({ kind: 'environment', title: say('Environment') });
|
|
48
53
|
const kinds = [
|
|
49
|
-
{ id: 'preview', label: 'Preview', bleed: true, render: () => _jsx(Browser, { sandbox: sandbox, held: held, site: site, live: live }, published) },
|
|
54
|
+
{ id: 'preview', label: say('Preview'), bleed: true, render: () => _jsx(Browser, { sandbox: sandbox, held: held, site: site, live: live }, published) },
|
|
50
55
|
{
|
|
51
56
|
id: 'artifacts',
|
|
52
|
-
label: 'Artifacts',
|
|
57
|
+
label: say('Artifacts'),
|
|
53
58
|
render: () => _jsx(Artifacts, { session: id, sandbox: sandbox, repo: repo, branch: branch, mode: mode, project: project, site: site, pr: pr, onEnvironment: environ }),
|
|
54
59
|
},
|
|
55
|
-
{ id: 'files', label: 'Files', render: () => _jsx(Files, { session: id, repo: repo, branch: branch, sandbox: sandbox, live: held === 'running' }) },
|
|
56
|
-
{ id: 'diff', label: 'Diff', render: () => _jsx(Git, { session: id, title: title, live: live }) },
|
|
60
|
+
{ id: 'files', label: say('Files'), render: () => _jsx(Files, { session: id, repo: repo, branch: branch, sandbox: sandbox, live: held === 'running' }) },
|
|
61
|
+
{ id: 'diff', label: say('Diff'), render: () => _jsx(Git, { session: id, title: title, live: live }) },
|
|
57
62
|
{
|
|
58
63
|
id: 'terminal',
|
|
59
|
-
label: 'Terminal',
|
|
64
|
+
label: say('Terminal'),
|
|
60
65
|
bleed: true,
|
|
61
66
|
render: () => _jsx(Terminal, { id: id, sandbox: sandbox, held: held, lines: lines, live: live, failed: failed, refused: refused, retry: retry, onRetry: onRetry }),
|
|
62
67
|
},
|
|
63
|
-
{ id: 'desktop', label: 'Desktop', bleed: true, render: () => _jsx(Door, { which: "screen", sandbox: sandbox, held: held, session: id }) },
|
|
68
|
+
{ id: 'desktop', label: say('Desktop'), bleed: true, render: () => _jsx(Door, { which: "screen", sandbox: sandbox, held: held, session: id }) },
|
|
64
69
|
{
|
|
65
70
|
id: 'environment',
|
|
66
|
-
label: 'Environment',
|
|
67
|
-
render: () => (_jsxs(YStack, { gap: "$2", children: [_jsx(Environment, { repo: name, busy: live && mode === 'setup' }), _jsx(SizableText, { size: "$2", color: "$ink", pt: "$4", children:
|
|
71
|
+
label: say('Environment'),
|
|
72
|
+
render: () => (_jsxs(YStack, { gap: "$2", children: [_jsx(Environment, { repo: name, busy: live && mode === 'setup' }), _jsx(SizableText, { size: "$2", color: "$ink", pt: "$4", children: say('This run') }), _jsx(Fact, { label: say('Run'), value: id }), _jsx(Fact, { label: say('Repository'), value: name || '—' }), _jsx(Fact, { label: say('Branch'), value: branch || '—' }), _jsx(Fact, { label: say('Base'), value: base || '—' }), _jsx(Fact, { label: say('Runs on'), value: environment || 'sandbox' }), _jsx(Fact, { label: say('Sandbox'), value: sandbox || '—' }), _jsx(Fact, { label: say('Mode'), value: mode || 'build' })] })),
|
|
68
73
|
},
|
|
69
74
|
];
|
|
70
|
-
return (_jsx(Panel, { side: side, kinds: kinds, sheet: sheet, onHide: onHide, label:
|
|
75
|
+
return (_jsx(Panel, { side: side, kinds: kinds, sheet: sheet, onHide: onHide, label: say('Run details'),
|
|
71
76
|
// An empty tab offers what this run made: its site and its pull request.
|
|
72
|
-
suggested: [...(site ? [{ title: name || site, url: site }] : []), ...(pr.href ? [{ title:
|
|
77
|
+
suggested: [...(site ? [{ title: name || site, url: site }] : []), ...(pr.href ? [{ title: say('Pull request {pr}', { pr: pr.label }), url: pr.href }] : [])], bar: sandbox ? _jsx(Held, { box: box, live: live }) : null, menu: _jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": say('Run actions'), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(MoreHorizontal, { size: 16 }) }), items: [...menu, { key: 'details', label: say('Details'), icon: _jsx(Info, { size: 16 }), onSelect: environ }] }) }));
|
|
73
78
|
}
|
|
74
79
|
/**
|
|
75
80
|
* The run's sandbox as it stands, and what moves it. It is read while the pane is
|
|
@@ -118,30 +123,33 @@ const until = (ms) => new Date(ms).toLocaleString(undefined, { weekday: 'short',
|
|
|
118
123
|
* A sandbox a run is working in is not suspended from here.
|
|
119
124
|
*/
|
|
120
125
|
function Held({ box, live }) {
|
|
126
|
+
const { t } = useI18n();
|
|
121
127
|
const b = box.value;
|
|
122
128
|
const state = b?.state ?? 'pending';
|
|
123
129
|
const says = state === 'running'
|
|
124
|
-
? 'Sandbox running'
|
|
130
|
+
? t('Sandbox running')
|
|
125
131
|
: state === 'parked'
|
|
126
132
|
? b?.ends
|
|
127
|
-
?
|
|
128
|
-
: 'Sandbox suspended · its files are kept'
|
|
133
|
+
? t('Sandbox suspended · its files are kept until {time}', { time: until(b.ends) })
|
|
134
|
+
: t('Sandbox suspended · its files are kept')
|
|
129
135
|
: state === 'pending'
|
|
130
|
-
? 'Sandbox starting…'
|
|
136
|
+
? t('Sandbox starting…')
|
|
131
137
|
: state === 'gone'
|
|
132
|
-
? 'Sandbox ended'
|
|
133
|
-
:
|
|
134
|
-
|
|
138
|
+
? t('Sandbox ended')
|
|
139
|
+
: b?.error
|
|
140
|
+
? t('Sandbox unavailable · {error}', { error: b.error })
|
|
141
|
+
: t('Sandbox unavailable');
|
|
142
|
+
return (_jsxs(XStack, { px: "$3", py: "$1.5", gap: "$2", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", "aria-label": t('Sandbox'), children: [_jsx(YStack, { width: 7, height: 7, rounded: 99, bg: state === 'running' ? '$green10' : state === 'parked' ? '$soft' : '$borderColor' }), _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, children: box.note || says }), state === 'running' && !live ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: box.busy, onPress: () => box.act('park'), children: t('Suspend') })) : state === 'parked' ? (_jsx(Button, { size: "sm", variant: "outline", disabled: box.busy, onPress: () => box.act('wake'), children: box.busy ? t('Resuming…') : t('Resume') })) : null] }));
|
|
135
143
|
}
|
|
136
144
|
/** What a preview page says of itself, when it cannot serve. */
|
|
137
145
|
const PREVIEW = 'hanzo-preview';
|
|
138
146
|
/** How often a preview is opened again on its own before Retry is the person's to press. */
|
|
139
147
|
const REMINTS = 2;
|
|
140
148
|
const REMINT = 30_000;
|
|
141
|
-
/** What a preview page's status means to the person looking at it, when it is not reopened
|
|
149
|
+
/** What a preview page's status means to the person looking at it, when it is not reopened, in the language of `say`. */
|
|
142
150
|
const SAYS = {
|
|
143
|
-
401: () => 'This browser keeps the preview’s cookie out of a frame. Open it in a new tab.'
|
|
144
|
-
502: (port) =>
|
|
151
|
+
401: (_, say) => `${say('This browser keeps the preview’s cookie out of a frame.')} ${say('Open it in a new tab.')}`,
|
|
152
|
+
502: (port, say) => `${say('Nothing answered on port {port}.', { port })} ${say('Start the server, then Retry.')}`,
|
|
145
153
|
};
|
|
146
154
|
/**
|
|
147
155
|
* The Browser tab: what the run's sandbox serves, one port at a time, framed at
|
|
@@ -151,6 +159,7 @@ const SAYS = {
|
|
|
151
159
|
* in a while; any other status is said, and Retry is the person's.
|
|
152
160
|
*/
|
|
153
161
|
function Browser({ sandbox, held, site, live }) {
|
|
162
|
+
const { t: say } = useI18n();
|
|
154
163
|
const t = useTarget();
|
|
155
164
|
const [open, setOpen] = useState([]);
|
|
156
165
|
const [port, setPort] = useState(0);
|
|
@@ -211,32 +220,33 @@ function Browser({ sandbox, held, site, live }) {
|
|
|
211
220
|
setAttempt((a) => a + 1);
|
|
212
221
|
return;
|
|
213
222
|
}
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
setWhy(
|
|
223
|
+
const words = SAYS[m.status];
|
|
224
|
+
if (words || m.status === 409)
|
|
225
|
+
setWhy(words ? words(port, say) : `${say('The sandbox stopped serving.')} ${say('Retry to open it again.')}`);
|
|
217
226
|
};
|
|
218
227
|
window.addEventListener('message', hear);
|
|
219
228
|
return () => window.removeEventListener('message', hear);
|
|
220
|
-
}, [src, port]);
|
|
229
|
+
}, [src, port, say]);
|
|
221
230
|
const tab = () => away(preview(t, sandbox, port)).catch((e) => setWhy(e.message));
|
|
222
231
|
if (!running)
|
|
223
232
|
return _jsx(Site, { site: site, live: live, held: held });
|
|
224
233
|
if (!port)
|
|
225
234
|
return _jsx(Site, { site: site, live: live, held: held });
|
|
226
235
|
const host = open.find((p) => p.port === port)?.host ?? '';
|
|
227
|
-
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1.5", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label":
|
|
236
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1.5", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label": say('Reload the page'), onPress: () => setAttempt((a) => a + 1), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 14 }) }), open.length > 1 ? (_jsx(XStack, { gap: "$1", children: open.map((p) => (_jsx(XStack, { render: "button", "aria-label": say('Port {port}', { port: p.port }), onPress: () => setPort(p.port), px: "$2", py: "$1", rounded: "$2", bg: p.port === port ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$1", color: p.port === port ? '$ink' : '$soft', children: String(p.port) }) }, p.port))) })) : null, _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, style: mono, children: host || say('port {port}', { port }) }), _jsx(XStack, { render: "button", "aria-label": say('Open in a new tab'), onPress: () => void tab(), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 14 }) })] }), why ? (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: why }), _jsxs(XStack, { gap: "$2", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setAttempt((a) => a + 1), children: say('Retry') }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => void tab(), children: say('Open in a new tab') })] })] })) : src ? (_jsx("iframe", { ref: frame, src: src, title: say('What the run serves on port {port}', { port }), sandbox: "allow-scripts allow-same-origin allow-forms allow-popups", style: { border: 0, width: '100%', height: '100%', display: 'block', background: '#fff' } })) : (_jsx(YStack, { flex: 1, items: "center", justify: "center", children: _jsx(SizableText, { size: "$2", color: "$soft", children: say('Opening port {port}…', { port }) }) }))] }));
|
|
228
237
|
}
|
|
229
238
|
/**
|
|
230
239
|
* Where the run published its work, framed, with its address, a reload and the
|
|
231
240
|
* page in a tab of its own; or what keeps its sandbox from serving.
|
|
232
241
|
*/
|
|
233
242
|
function Site({ site, live, held }) {
|
|
243
|
+
const { t } = useI18n();
|
|
234
244
|
const frame = useRef(null);
|
|
235
|
-
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1.5", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label":
|
|
236
|
-
? 'The run’s sandbox is suspended. Resume it to see what it serves.'
|
|
245
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1.5", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label": t('Reload the page'), onPress: () => frame.current?.reload(), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 14 }) }), _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, style: mono, children: site || t('Nothing is serving yet') }), site ? (_jsx(Out, { href: site, label: t('Open in a new tab'), children: _jsx(XStack, { p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 14 }) }) })) : null] }), _jsx(PreviewFrame, { ref: frame, src: site, title: t('Where this run is published'), device: "desktop", empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: t('Nothing is serving yet') }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: held === 'parked'
|
|
246
|
+
? `${t('The run’s sandbox is suspended.')} ${t('Resume it to see what it serves.')}`
|
|
237
247
|
: live
|
|
238
|
-
? 'When the run starts a server in its sandbox, the page opens here.'
|
|
239
|
-
: 'Nothing listens in this run’s sandbox, and it published no site. Its work is on its branch.' })] }) })] }));
|
|
248
|
+
? t('When the run starts a server in its sandbox, the page opens here.')
|
|
249
|
+
: `${t('Nothing listens in this run’s sandbox, and it published no site.')} ${t('Its work is on its branch.')}` })] }) })] }));
|
|
240
250
|
}
|
|
241
251
|
function Fact({ label, value }) {
|
|
242
252
|
return (_jsxs(XStack, { gap: "$3", items: "baseline", children: [_jsx(SizableText, { size: "$1", color: "$soft", width: 96, children: label }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 2, children: value })] }));
|
|
@@ -248,14 +258,16 @@ const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)', whiteSpace:
|
|
|
248
258
|
* shell is a tmux session named for the run, so a reopened tab reattaches to it.
|
|
249
259
|
*/
|
|
250
260
|
function Terminal({ id, sandbox, held, lines, live, failed, refused, retry, onRetry, }) {
|
|
261
|
+
const { t } = useI18n();
|
|
251
262
|
const reachable = Boolean(sandbox) && held === 'running';
|
|
252
263
|
// The shell while there is one, unless the log was asked for.
|
|
253
264
|
const [log, setLog] = useState(false);
|
|
254
265
|
const showShell = reachable && !log;
|
|
255
|
-
return (_jsxs(YStack, { flex: 1, minH: 0, bg: "$background", children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1", borderBottomWidth: 1, borderColor: "$borderColor", items: "center", children: [reachable ? (_jsx(XStack, { render: "button", "aria-label":
|
|
266
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, bg: "$background", children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1", borderBottomWidth: 1, borderColor: "$borderColor", items: "center", children: [reachable ? (_jsx(XStack, { render: "button", "aria-label": t('Shell'), onPress: () => setLog(false), px: "$2", py: "$1", rounded: "$2", bg: showShell ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$1", color: showShell ? '$ink' : '$soft', children: t('Shell') }) })) : null, _jsx(XStack, { render: "button", "aria-label": t('Agent log'), onPress: () => setLog(true), px: "$2", py: "$1", rounded: "$2", bg: showShell ? 'transparent' : '$hover', children: _jsx(SizableText, { size: "$1", color: showShell ? '$soft' : '$ink', children: t('Agent log') }) })] }), showShell ? _jsx(Door, { which: "terminal", sandbox: sandbox, held: held, session: `run-${id.replace(/^sess_/, '').slice(0, 12)}` }) : _jsx(Log, { lines: lines, live: live, failed: failed, refused: refused, retry: retry, onRetry: onRetry })] }));
|
|
256
267
|
}
|
|
257
268
|
function Log({ lines, live, failed, refused, retry, onRetry, }) {
|
|
269
|
+
const { t } = useI18n();
|
|
258
270
|
const prompt = 'workspace';
|
|
259
271
|
const idle = lines.length === 0 && !live;
|
|
260
|
-
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", py: "$3", gap: "$1", children: [idle ? (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", py: "$8", children: [_jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: refused || (failed ?
|
|
272
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", py: "$3", gap: "$1", children: [idle ? (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", py: "$8", children: [_jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: refused || (failed ? t('This run stopped before its first command: {reason}', { reason: failed }) : t('This run finished without writing a command.')) }), refused ? (_jsx(Button, { size: "sm", variant: "outline", onPress: onRetry, children: retry })) : null] })) : (lines.map((line, i) => line.role === 'cmd' ? (_jsxs(XStack, { gap: "$2", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: mono, children: `${prompt} $` }), _jsx(SizableText, { flex: 1, size: "$1", color: "$ink", style: mono, children: line.text })] }, i)) : (_jsx(SizableText, { size: "$1", color: "$ink", style: mono, children: line.text }, i)))), live ? (_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: mono, children: `${prompt} $` }), _jsx(YStack, { width: 7, height: 14, bg: "$ink" })] })) : null] }), _jsx(XStack, { px: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: _jsx(SizableText, { size: "$1", color: "$soft", children: t('Commands appear here as the run writes them.') }) })] }));
|
|
261
273
|
}
|