@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/door.js
CHANGED
|
@@ -8,22 +8,47 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
* past the deadline, is a failed connection, and Retry mints a new ticket: a
|
|
9
9
|
* ticket is spent the moment its socket opens, so reloading the old address can
|
|
10
10
|
* only fail again.
|
|
11
|
+
*
|
|
12
|
+
* What a door says is the reader's language (@hanzo/i18n `t`), one sentence
|
|
13
|
+
* per door; the page's own reason for a failed connection is drawn as given.
|
|
11
14
|
*/
|
|
12
15
|
import { SizableText, YStack } from '@hanzo/gui';
|
|
16
|
+
import { phrase } from '@hanzo/i18n';
|
|
17
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
13
18
|
import { Button } from '@hanzo/ui';
|
|
14
19
|
import { useEffect, useRef, useState } from 'react';
|
|
15
20
|
import { door } from './api/sandbox.js';
|
|
16
21
|
import { useTarget } from './host.js';
|
|
17
22
|
/** What each page signs its messages to the window that frames it with. */
|
|
18
23
|
const SOURCE = { screen: 'hanzo-screen', terminal: 'hanzo-term' };
|
|
19
|
-
const WHAT = { screen: 'desktop', terminal: 'terminal' };
|
|
20
24
|
const DEADLINE = 20_000;
|
|
25
|
+
/** What each door says of itself, one sentence per door, marked (`phrase`) and drawn through `t`. */
|
|
26
|
+
const SAYS = {
|
|
27
|
+
screen: {
|
|
28
|
+
starting: phrase('The desktop opens once the run’s sandbox is up.'),
|
|
29
|
+
parked: phrase('This run’s sandbox is suspended with its files kept. Resume it from the bar above to open its desktop.'),
|
|
30
|
+
error: phrase('This run’s sandbox could not start, so it has no desktop.'),
|
|
31
|
+
unreachable: phrase('Unable to reach the desktop. The run may have stopped.'),
|
|
32
|
+
title: phrase('The run’s desktop'),
|
|
33
|
+
connecting: phrase('Connecting to the desktop…'),
|
|
34
|
+
},
|
|
35
|
+
terminal: {
|
|
36
|
+
starting: phrase('The terminal opens once the run’s sandbox is up.'),
|
|
37
|
+
parked: phrase('This run’s sandbox is suspended with its files kept. Resume it from the bar above to open its terminal.'),
|
|
38
|
+
error: phrase('This run’s sandbox could not start, so it has no terminal.'),
|
|
39
|
+
unreachable: phrase('Unable to reach the terminal. The run may have stopped.'),
|
|
40
|
+
title: phrase('The run’s terminal'),
|
|
41
|
+
connecting: phrase('Connecting to the terminal…'),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
21
44
|
/**
|
|
22
45
|
* `held` is where the run's sandbox is in its life, '' until it has been read. Only
|
|
23
46
|
* a running sandbox has an open door.
|
|
24
47
|
*/
|
|
25
48
|
export function Door({ which, sandbox, held, session }) {
|
|
26
|
-
const
|
|
49
|
+
const target = useTarget();
|
|
50
|
+
const { t } = useI18n();
|
|
51
|
+
const says = SAYS[which];
|
|
27
52
|
const frame = useRef(null);
|
|
28
53
|
const [src, setSrc] = useState('');
|
|
29
54
|
const [state, setState] = useState('opening');
|
|
@@ -37,7 +62,7 @@ export function Door({ which, sandbox, held, session }) {
|
|
|
37
62
|
setSrc('');
|
|
38
63
|
setState('opening');
|
|
39
64
|
setWhy('');
|
|
40
|
-
door(
|
|
65
|
+
door(target, sandbox, which, session).then((u) => {
|
|
41
66
|
if (!gone)
|
|
42
67
|
setSrc(u);
|
|
43
68
|
}, (e) => {
|
|
@@ -49,7 +74,7 @@ export function Door({ which, sandbox, held, session }) {
|
|
|
49
74
|
return () => {
|
|
50
75
|
gone = true;
|
|
51
76
|
};
|
|
52
|
-
}, [
|
|
77
|
+
}, [target, sandbox, which, session, up, attempt]);
|
|
53
78
|
useEffect(() => {
|
|
54
79
|
if (!src)
|
|
55
80
|
return;
|
|
@@ -79,21 +104,21 @@ export function Door({ which, sandbox, held, session }) {
|
|
|
79
104
|
};
|
|
80
105
|
}, [src, which]);
|
|
81
106
|
if (!sandbox || held === '' || held === 'pending') {
|
|
82
|
-
return _jsx(Shut, { title:
|
|
107
|
+
return _jsx(Shut, { title: t('Starting'), body: t(says.starting) });
|
|
83
108
|
}
|
|
84
109
|
if (held === 'parked') {
|
|
85
|
-
return _jsx(Shut, { title:
|
|
110
|
+
return _jsx(Shut, { title: t('Suspended'), body: t(says.parked) });
|
|
86
111
|
}
|
|
87
112
|
if (held === 'gone') {
|
|
88
|
-
return _jsx(Shut, { title:
|
|
113
|
+
return _jsx(Shut, { title: t('Ended'), body: t('This run’s sandbox has ended. Its work is on its branch and in its artifacts.') });
|
|
89
114
|
}
|
|
90
115
|
if (held === 'error') {
|
|
91
|
-
return _jsx(Shut, { title:
|
|
116
|
+
return _jsx(Shut, { title: t('Unavailable'), body: t(says.error) });
|
|
92
117
|
}
|
|
93
118
|
if (state === 'failed') {
|
|
94
|
-
return (_jsx(Shut, { title:
|
|
119
|
+
return (_jsx(Shut, { title: t('Failed to connect'), body: why || t(says.unreachable), children: _jsx(Button, { size: "sm", variant: "outline", onPress: () => setAttempt((a) => a + 1), children: t('Retry') }) }));
|
|
95
120
|
}
|
|
96
|
-
return (_jsxs(YStack, { flex: 1, minH: 0, position: "relative", bg: "#000", children: [src ? (_jsx("iframe", { ref: frame, src: src, title:
|
|
121
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, position: "relative", bg: "#000", children: [src ? (_jsx("iframe", { ref: frame, src: src, title: t(says.title), allow: "clipboard-read; clipboard-write", style: { border: 0, width: '100%', height: '100%', display: 'block', background: '#000' } })) : null, state === 'opening' ? (_jsx(YStack, { position: "absolute", t: 0, l: 0, r: 0, b: 0, items: "center", justify: "center", pointerEvents: "none", children: _jsx(SizableText, { size: "$2", color: "$soft", children: t(says.connecting) }) })) : null] }));
|
|
97
122
|
}
|
|
98
123
|
function Shut({ title, body, children }) {
|
|
99
124
|
return (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$2", px: "$6", py: "$8", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: title }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center', maxWidth: 360 }, children: body }), children] }));
|
package/lib/environment.js
CHANGED
|
@@ -7,9 +7,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
* the review. While a setup run on the codebase is still working the scripts are
|
|
8
8
|
* its to find, so they cannot be edited. A member reads; an org admin saves and
|
|
9
9
|
* sets secrets, which is the platform's rule and not a choice made here.
|
|
10
|
+
*
|
|
11
|
+
* Its words are the reader's language (@hanzo/i18n `t`); a codebase's name, a
|
|
12
|
+
* secret's, the scripts and what the setup agent wrote are drawn as given.
|
|
10
13
|
*/
|
|
11
14
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
12
15
|
import { Plus, X } from '@hanzogui/lucide-icons-2';
|
|
16
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
13
17
|
import { Button, Dialog, DialogContent, DialogTitle, Input, Textarea } from '@hanzo/ui';
|
|
14
18
|
import { useEffect, useState } from 'react';
|
|
15
19
|
import { start } from './api/coding.js';
|
|
@@ -19,11 +23,12 @@ import { matches } from './merge.js';
|
|
|
19
23
|
import { useHost, useTarget } from './host.js';
|
|
20
24
|
const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
|
|
21
25
|
export function Environment({ repo, busy }) {
|
|
26
|
+
const { t } = useI18n();
|
|
22
27
|
const host = useHost();
|
|
23
|
-
const
|
|
28
|
+
const target = useTarget();
|
|
24
29
|
const signed = Boolean(host.person);
|
|
25
30
|
// Read again when a setup run on it stops, which is when its proposal lands.
|
|
26
|
-
const env = useRead(signed && repo ? () => read(
|
|
31
|
+
const env = useRead(signed && repo ? () => read(target, repo) : null, null, [target, repo, signed, busy]);
|
|
27
32
|
const [install, setInstall] = useState('');
|
|
28
33
|
const [startCmd, setStart] = useState('');
|
|
29
34
|
const [adding, setAdding] = useState(false);
|
|
@@ -39,20 +44,20 @@ export function Environment({ repo, busy }) {
|
|
|
39
44
|
setStart(shown.proposal?.start ?? shown.start);
|
|
40
45
|
}, [shown]);
|
|
41
46
|
if (!signed) {
|
|
42
|
-
return _jsx(Soft, { children:
|
|
47
|
+
return _jsx(Soft, { children: t('Sign in to see this codebase’s environment.') });
|
|
43
48
|
}
|
|
44
49
|
if (!repo) {
|
|
45
|
-
return _jsx(Soft, { children:
|
|
50
|
+
return _jsx(Soft, { children: t('This run has no codebase, so it has no environment.') });
|
|
46
51
|
}
|
|
47
52
|
if (env.error) {
|
|
48
|
-
return _jsx(Soft, { children:
|
|
53
|
+
return _jsx(Soft, { children: t('{repo}’s environment could not be read right now.', { repo }) });
|
|
49
54
|
}
|
|
50
55
|
if (!shown) {
|
|
51
|
-
return _jsx(Soft, { children:
|
|
56
|
+
return _jsx(Soft, { children: t('Reading the environment…') });
|
|
52
57
|
}
|
|
53
58
|
const changed = install !== shown.install || startCmd !== shown.start || shown.proposal !== null;
|
|
54
59
|
const needed = (shown.proposal?.secrets ?? []).filter((n) => !shown.secrets.includes(n));
|
|
55
|
-
const status = busy ? 'Setting up' : shown.state === 'proposed' ? 'Proposed' : shown.state === 'ready' ? 'Ready' : 'Not set up';
|
|
60
|
+
const status = busy ? t('Setting up') : shown.state === 'proposed' ? t('Proposed') : shown.state === 'ready' ? t('Ready') : t('Not set up');
|
|
56
61
|
/** One write, then the record read again. Answers whether it took. */
|
|
57
62
|
const act = async (what, done = '') => {
|
|
58
63
|
setWorking(true);
|
|
@@ -64,7 +69,7 @@ export function Environment({ repo, busy }) {
|
|
|
64
69
|
return true;
|
|
65
70
|
}
|
|
66
71
|
catch (e) {
|
|
67
|
-
setNote(plain(e, repo, 'save'));
|
|
72
|
+
setNote(plain(e, repo, 'save', t));
|
|
68
73
|
return false;
|
|
69
74
|
}
|
|
70
75
|
finally {
|
|
@@ -75,22 +80,22 @@ export function Environment({ repo, busy }) {
|
|
|
75
80
|
setWorking(true);
|
|
76
81
|
setNote('');
|
|
77
82
|
try {
|
|
78
|
-
const run = await start(
|
|
83
|
+
const run = await start(target, { prompt: SETUP, repo, mode: 'setup' });
|
|
79
84
|
host.go(run.session);
|
|
80
85
|
}
|
|
81
86
|
catch (e) {
|
|
82
|
-
setNote(plain(e, repo, 'start'));
|
|
87
|
+
setNote(plain(e, repo, 'start', t));
|
|
83
88
|
setWorking(false);
|
|
84
89
|
}
|
|
85
90
|
};
|
|
86
91
|
const add = () => {
|
|
87
92
|
const n = name.trim();
|
|
88
|
-
const why = refuse(n) || (value ? '' : 'A secret needs a value');
|
|
93
|
+
const why = refuse(n, t) || (value ? '' : t('A secret needs a value'));
|
|
89
94
|
if (why) {
|
|
90
95
|
setNote(why);
|
|
91
96
|
return;
|
|
92
97
|
}
|
|
93
|
-
void act(() => setSecret(
|
|
98
|
+
void act(() => setSecret(target, repo, n, value), t('{name} is set', { name: n })).then((took) => {
|
|
94
99
|
if (!took)
|
|
95
100
|
return;
|
|
96
101
|
setAdding(false);
|
|
@@ -98,7 +103,7 @@ export function Environment({ repo, busy }) {
|
|
|
98
103
|
setValue('');
|
|
99
104
|
});
|
|
100
105
|
};
|
|
101
|
-
return (_jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, size: "$4", color: "$ink", numberOfLines: 1, children: repo }), _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(YStack, { width: 6, height: 6, rounded: 999, bg: busy ? '$ink' : shown.state === 'ready' ? '$ink' : '$soft', opacity: busy ? 1 : 0.7 }), _jsx(SizableText, { size: "$1", color: "$soft", children: status })] }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy || working || !changed, onPress: () => void act(() => save(
|
|
106
|
+
return (_jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, size: "$4", color: "$ink", numberOfLines: 1, children: repo }), _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(YStack, { width: 6, height: 6, rounded: 999, bg: busy ? '$ink' : shown.state === 'ready' ? '$ink' : '$soft', opacity: busy ? 1 : 0.7 }), _jsx(SizableText, { size: "$1", color: "$soft", children: status })] }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy || working || !changed, onPress: () => void act(() => save(target, repo, { install, start: startCmd }), t('Saved')), children: t('Save') })) : null] }), shown.proposal?.note ? (_jsxs(YStack, { gap: "$1", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('What the setup agent found') }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 8, children: shown.proposal.note })] })) : null, _jsx(Script, { label: t('Install Script'), value: install, onChange: setInstall, locked: busy || !host.admin, hint: busy ? t('Install script editing will be available when the agent stops running') : 'pnpm install --frozen-lockfile' }), _jsx(Script, { label: t('Start Script'), value: startCmd, onChange: setStart, locked: busy || !host.admin, hint: busy ? t('Start script editing will be available when the agent stops running') : 'pnpm dev' }), _jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, children: t('Secrets') }), host.admin ? (_jsxs(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), t('New Secret')] })) : null] }), _jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [shown.secrets.length === 0 && needed.length === 0 && !adding ? (_jsx(YStack, { py: "$5", items: "center", children: _jsx(SizableText, { size: "$2", color: "$soft", children: t('No secrets yet') }) })) : null, shown.secrets.map((s, i) => (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", style: mono, children: s }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('set') }), host.admin ? (_jsx(XStack, { render: "button", "aria-label": t('Remove {name}', { name: s }), px: "$1", onPress: () => void act(() => removeSecret(target, repo, s), t('{name} is removed', { name: s })), children: _jsx(X, { size: 14 }) })) : null] }, s))), needed.map((s) => (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", style: mono, children: s }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('needed, not set') }), host.admin ? (_jsx(XStack, { render: "button", "aria-label": t('Set {name}', { name: s }), px: "$1", onPress: () => { setAdding(true); setName(s); }, children: _jsx(Plus, { size: 14 }) })) : null] }, s))), adding ? (_jsxs(YStack, { gap: "$2", px: "$3", py: "$3", borderTopWidth: shown.secrets.length || needed.length ? 1 : 0, borderColor: "$borderColor", children: [_jsx(Input, { value: name, onChangeText: setName, placeholder: t('NAME'), "aria-label": t('Secret name'), autoCapitalize: "characters" }), _jsx(Input, { value: value, onChangeText: setValue, placeholder: t('Value'), "aria-label": t('Secret value'), secureTextEntry: true }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => { setAdding(false); setName(''); setValue(''); }, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working, onPress: add, children: t('Add secret') })] })] })) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Values are sealed in KMS and exported to every run on this codebase. They are never shown again.') })] }), shown.state === 'none' && !busy ? (_jsxs(YStack, { gap: "$2", items: "flex-start", children: [_jsx(Button, { size: "sm", disabled: working, onPress: () => void setup(), children: t('Set up with an agent') }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('An agent explores the codebase, installs and checks it, and proposes the scripts for you to review and save.') })] })) : null, !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin saves the environment and sets its secrets.') })) : null, note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null] }));
|
|
102
107
|
}
|
|
103
108
|
/**
|
|
104
109
|
* New's offer to set a codebase up. An agent onboards it, or an org admin saves
|
|
@@ -109,8 +114,9 @@ export function Environment({ repo, busy }) {
|
|
|
109
114
|
* board's key handed over as a codebase — is said so and cannot be set up.
|
|
110
115
|
*/
|
|
111
116
|
export function SetupDialog({ repo, homes, open, onOpenChange, onStart, onSaved, onRepo, }) {
|
|
117
|
+
const { t } = useI18n();
|
|
112
118
|
const host = useHost();
|
|
113
|
-
const
|
|
119
|
+
const target = useTarget();
|
|
114
120
|
const [chosen, setChosen] = useState(repo);
|
|
115
121
|
const [q, setQ] = useState('');
|
|
116
122
|
const [working, setWorking] = useState('');
|
|
@@ -140,7 +146,7 @@ export function SetupDialog({ repo, homes, open, onOpenChange, onStart, onSaved,
|
|
|
140
146
|
setNote('');
|
|
141
147
|
try {
|
|
142
148
|
if (what === 'save') {
|
|
143
|
-
await save(
|
|
149
|
+
await save(target, repo, { install: '', start: '' });
|
|
144
150
|
onSaved(repo);
|
|
145
151
|
}
|
|
146
152
|
else {
|
|
@@ -149,20 +155,20 @@ export function SetupDialog({ repo, homes, open, onOpenChange, onStart, onSaved,
|
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
catch (e) {
|
|
152
|
-
setNote(plain(e, repo, what));
|
|
158
|
+
setNote(plain(e, repo, what, t));
|
|
153
159
|
}
|
|
154
160
|
finally {
|
|
155
161
|
setWorking('');
|
|
156
162
|
}
|
|
157
163
|
};
|
|
158
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children:
|
|
164
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: t('Set up an environment') }), _jsx(XStack, { render: "button", "aria-label": t('Close'), p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsxs(YStack, { gap: "$3", children: [_jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: t('Repository') }), _jsx(XStack, { px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: stranger ? '$ink' : '$borderColor', items: "center", gap: "$2", "aria-label": t('Chosen repository'), children: _jsx(SizableText, { size: "$2", color: home || (chosen && !stranger) ? '$ink' : '$soft', numberOfLines: 1, flex: 1, minW: 0, children: home ? home.label : chosen || t('Choose a repository') }) }), stranger ? (_jsx(SizableText, { size: "$1", color: "$ink", role: "alert", children: t('{repo} is not one of this organization’s repositories. Choose one below.', { repo: chosen }) })) : null, _jsx(Input, { value: q, onChangeText: setQ, placeholder: t('Find a repository…'), "aria-label": t('Find a repository') }), _jsx(YStack, { gap: 2, "aria-label": t('Repositories'), children: homes.loading && !list.length ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('Reading the repositories…') })) : homes.error && !list.length ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('The repository list could not be read right now.') }), _jsx(Button, { size: "sm", variant: "outline", onPress: homes.reload, children: t('Read again') })] })) : shown.length === 0 ? (_jsx(SizableText, { size: "$1", color: "$soft", children: list.length ? t('Nothing matches.') : t('This organization has no repositories on the forge yet.') })) : (shown.map((h) => {
|
|
159
165
|
const on = h.name.toLowerCase() === chosen.toLowerCase();
|
|
160
|
-
return (_jsx(XStack, { render: "button", "aria-pressed": on, "aria-label":
|
|
166
|
+
return (_jsx(XStack, { render: "button", "aria-pressed": on, "aria-label": t('Choose {repo}', { repo: h.label }), onPress: () => {
|
|
161
167
|
setChosen(h.name);
|
|
162
168
|
setNote('');
|
|
163
169
|
onRepo(h);
|
|
164
170
|
}, px: "$2.5", py: "$1.5", rounded: "$2", bg: on ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: on ? '$ink' : '$soft', numberOfLines: 1, children: h.label }) }, h.name));
|
|
165
|
-
})) })] }), _jsx(SizableText, { size: "$2", color: "$soft", children:
|
|
171
|
+
})) })] }), _jsx(SizableText, { size: "$2", color: "$soft", children: t('An agent onboards the codebase: it explores it, writes the install and start scripts, and names the secrets it needs. It takes several minutes. Interrupt it anytime, or take over in the terminal.') }), note ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$1", color: "$ink", role: "status", flex: 1, minW: 200, children: note }), last ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working !== '', onPress: () => void run(last), children: t('Try again') })) : null] })) : null, _jsxs(XStack, { gap: "$2", justify: "space-between", items: "center", children: [host.admin ? (_jsx(Button, { size: "sm", variant: "secondary", disabled: working !== '' || !ready, onPress: () => void run('save'), children: working === 'save' ? t('Saving…') : t('Skip & save') })) : (_jsx(YStack, {})), _jsx(Button, { size: "sm", variant: "primary", disabled: working !== '' || !ready, onPress: () => void run('start'), children: working === 'start' ? t('Starting the agent…') : t('Start agent') })] })] })] }) }));
|
|
166
172
|
}
|
|
167
173
|
function Script({ label, value, onChange, locked, hint }) {
|
|
168
174
|
return (_jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: label }), _jsx(Textarea, { value: value, onChangeText: onChange, disabled: locked, placeholder: hint, "aria-label": label, rows: 3, style: mono })] }));
|
package/lib/files.js
CHANGED
|
@@ -6,8 +6,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* or changed and the whole change as a patch, kept after its sandbox is gone; its
|
|
7
7
|
* previews, pull request and deployments — then its published site, its branch,
|
|
8
8
|
* its environment proposal and its project.
|
|
9
|
+
*
|
|
10
|
+
* Its words are the reader's language (@hanzo/i18n `t`); names, paths and a
|
|
11
|
+
* file's contents are drawn as given.
|
|
9
12
|
*/
|
|
10
13
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
14
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
11
15
|
import { Download, ExternalLink, File, FileDiff, Folder, Globe, GitBranch, GitPullRequest, Layers, MonitorPlay, Rocket, Settings2 } from '@hanzogui/lucide-icons-2';
|
|
12
16
|
import { useState } from 'react';
|
|
13
17
|
import { blob, tree } from './api/changes.js';
|
|
@@ -18,25 +22,27 @@ import { useHost, useTarget } from './host.js';
|
|
|
18
22
|
import { away, Out } from './out.js';
|
|
19
23
|
const monoWrap = { whiteSpace: 'pre-wrap', fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
|
|
20
24
|
export function Files({ session, repo, branch, sandbox, live, }) {
|
|
25
|
+
const { t } = useI18n();
|
|
21
26
|
const reachable = live && Boolean(sandbox);
|
|
22
27
|
// Live once the sandbox is reachable, unless the branch was picked.
|
|
23
28
|
const [source, setSource] = useState(null);
|
|
24
29
|
const from = reachable ? (source ?? 'live') : 'branch';
|
|
25
|
-
return (_jsxs(YStack, { gap: "$3", children: [reachable ? (_jsxs(XStack, { gap: "$1", items: "center", flexWrap: "wrap", children: [_jsx(Pick, { on: from === 'live', onPress: () => setSource('live'), children:
|
|
30
|
+
return (_jsxs(YStack, { gap: "$3", children: [reachable ? (_jsxs(XStack, { gap: "$1", items: "center", flexWrap: "wrap", children: [_jsx(Pick, { on: from === 'live', onPress: () => setSource('live'), children: t('Live') }), _jsx(Pick, { on: from === 'branch', onPress: () => setSource('branch'), children: t('Branch') })] })) : null, from === 'live' ? _jsx(Live, { sandbox: sandbox }) : _jsx(Branch, { session: session, label: `${repo}${branch ? `@${branch}` : ''}` })] }));
|
|
26
31
|
}
|
|
27
32
|
/** The sandbox's working tree, as it is right now. */
|
|
28
33
|
function Live({ sandbox }) {
|
|
29
|
-
const
|
|
34
|
+
const target = useTarget();
|
|
35
|
+
const { t } = useI18n();
|
|
30
36
|
const [dir, setDir] = useState('');
|
|
31
37
|
const [file, setFile] = useState('');
|
|
32
|
-
const list = useRead(() => look(
|
|
33
|
-
const body = useRead(file ? () => look(
|
|
38
|
+
const list = useRead(() => look(target, sandbox, dir), null, [target, sandbox, dir]);
|
|
39
|
+
const body = useRead(file ? () => look(target, sandbox, file) : null, null, [target, sandbox, file]);
|
|
34
40
|
const at = (name) => (dir ? `${dir}/${name}` : name);
|
|
35
41
|
// A name is only a name until it is read: the answer says whether it was a directory.
|
|
36
42
|
const open = async (name) => {
|
|
37
43
|
const path = at(name);
|
|
38
44
|
try {
|
|
39
|
-
const n = await look(
|
|
45
|
+
const n = await look(target, sandbox, path);
|
|
40
46
|
if (n.dir) {
|
|
41
47
|
setDir(path);
|
|
42
48
|
setFile('');
|
|
@@ -53,23 +59,24 @@ function Live({ sandbox }) {
|
|
|
53
59
|
? body.error.message
|
|
54
60
|
: body.value
|
|
55
61
|
? body.value.binary
|
|
56
|
-
? 'Binary file'
|
|
62
|
+
? t('Binary file')
|
|
57
63
|
: body.value.truncated
|
|
58
|
-
? 'Too large to show'
|
|
59
|
-
: body.value.text || 'Empty file'
|
|
64
|
+
? t('Too large to show')
|
|
65
|
+
: body.value.text || t('Empty file')
|
|
60
66
|
: '' }));
|
|
61
67
|
}
|
|
62
68
|
/** The run's branch on the forge; its base until it has pushed. */
|
|
63
69
|
function Branch({ session, label }) {
|
|
64
70
|
const host = useHost();
|
|
65
|
-
const
|
|
71
|
+
const target = useTarget();
|
|
72
|
+
const { t } = useI18n();
|
|
66
73
|
const signed = Boolean(host.person);
|
|
67
74
|
const [dir, setDir] = useState('');
|
|
68
75
|
const [file, setFile] = useState('');
|
|
69
|
-
const list = useRead(signed ? () => tree(
|
|
70
|
-
const body = useRead(file ? () => blob(
|
|
76
|
+
const list = useRead(signed ? () => tree(target, session, dir) : null, [], [target, session, dir, signed]);
|
|
77
|
+
const body = useRead(file ? () => blob(target, session, file) : null, null, [target, session, file]);
|
|
71
78
|
if (!signed)
|
|
72
|
-
return _jsx(Soft, { children:
|
|
79
|
+
return _jsx(Soft, { children: t('Sign in to read this run’s files.') });
|
|
73
80
|
return (_jsx(Listing, { where: `${label}${dir ? `/${dir}` : ''}`, up: dir ? () => (setDir(dir.includes('/') ? dir.slice(0, dir.lastIndexOf('/')) : ''), setFile('')) : undefined, error: list.error?.message ?? '', loading: list.loading && list.value.length === 0, rows: list.value.map((e) => ({
|
|
74
81
|
key: e.path,
|
|
75
82
|
name: e.name,
|
|
@@ -79,34 +86,36 @@ function Branch({ session, label }) {
|
|
|
79
86
|
? body.error.message
|
|
80
87
|
: body.value
|
|
81
88
|
? body.value.binary
|
|
82
|
-
? 'Binary file'
|
|
89
|
+
? t('Binary file')
|
|
83
90
|
: body.value.truncated
|
|
84
|
-
? 'Too large to show'
|
|
85
|
-
: body.value.text || 'Empty file'
|
|
91
|
+
? t('Too large to show')
|
|
92
|
+
: body.value.text || t('Empty file')
|
|
86
93
|
: '' }));
|
|
87
94
|
}
|
|
88
95
|
function Listing({ where, up, error, loading, rows, file, body, }) {
|
|
89
|
-
|
|
96
|
+
const { t } = useI18n();
|
|
97
|
+
return (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { gap: "$2", items: "center", children: [up ? (_jsx(XStack, { render: "button", "aria-label": t('Up one directory'), onPress: up, px: "$1", py: "$1", children: _jsx(SizableText, { size: "$1", color: "$soft", children: t('Up') }) })) : null, _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: where })] }), error ? (_jsx(SizableText, { size: "$2", color: "$soft", children: error })) : loading ? (_jsx(SizableText, { size: "$2", color: "$soft", children: t('Reading…') })) : rows.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: t('This directory is empty.') })) : (rows.map((r) => (_jsxs(XStack, { render: "button", "aria-label": r.dir === false ? t('Read {name}', { name: r.name }) : t('Open {name}', { name: r.name }), onPress: r.onPress, items: "center", gap: "$2", py: "$1", hoverStyle: { bg: '$hover' }, children: [r.dir === false ? _jsx(File, { size: 14 }) : r.dir ? _jsx(Folder, { size: 14 }) : _jsx(File, { size: 14, opacity: 0.6 }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, text: "start", children: r.name })] }, r.key)))), file && body ? (_jsxs(YStack, { gap: "$1", pt: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: file }), _jsx(SizableText, { size: "$1", color: "$ink", style: monoWrap, children: body })] })) : null] }));
|
|
90
98
|
}
|
|
91
99
|
/** A stored artifact's size, as a person reads it. */
|
|
92
100
|
const size = (n) => (n < 1024 ? `${n} B` : n < 1 << 20 ? `${(n / 1024).toFixed(1)} KB` : `${(n / (1 << 20)).toFixed(1)} MB`);
|
|
93
|
-
/** What each kind of artifact is drawn with, and says it is. */
|
|
101
|
+
/** What each kind of artifact is drawn with, and says it is, in the reader's language. */
|
|
94
102
|
const KIND = {
|
|
95
|
-
file: { icon: _jsx(File, { size: 16 }), note: (a) =>
|
|
96
|
-
patch: { icon: _jsx(FileDiff, { size: 16 }), note: (a) =>
|
|
97
|
-
preview: { icon: _jsx(MonitorPlay, { size: 16 }), note: (a) =>
|
|
98
|
-
pull: { icon: _jsx(GitPullRequest, { size: 16 }), note: () => 'Pull request' },
|
|
99
|
-
deploy: { icon: _jsx(Rocket, { size: 16 }), note: () => 'Deployment' },
|
|
103
|
+
file: { icon: _jsx(File, { size: 16 }), note: (a, t) => t('Changed file · {size}', { size: size(a.size) }) },
|
|
104
|
+
patch: { icon: _jsx(FileDiff, { size: 16 }), note: (a, t) => t('The whole change as a patch · {size}', { size: size(a.size) }) },
|
|
105
|
+
preview: { icon: _jsx(MonitorPlay, { size: 16 }), note: (a, t) => t('What the run served on port {port}', { port: a.port }) },
|
|
106
|
+
pull: { icon: _jsx(GitPullRequest, { size: 16 }), note: (_, t) => t('Pull request') },
|
|
107
|
+
deploy: { icon: _jsx(Rocket, { size: 16 }), note: (_, t) => t('Deployment') },
|
|
100
108
|
};
|
|
101
109
|
export function Artifacts({ session, sandbox, repo, branch, mode, project, site, pr, onEnvironment, }) {
|
|
102
110
|
const host = useHost();
|
|
103
|
-
const
|
|
104
|
-
const
|
|
111
|
+
const target = useTarget();
|
|
112
|
+
const { t } = useI18n();
|
|
113
|
+
const left = useRead(host.person ? () => artifacts(target, session) : null, { saved: '', artifacts: [] }, [target, session, host.person]);
|
|
105
114
|
const [note, setNote] = useState('');
|
|
106
115
|
// Bytes come with the person's bearer, so a stored artifact is fetched, then saved.
|
|
107
116
|
const save = async (a) => {
|
|
108
117
|
try {
|
|
109
|
-
const url = URL.createObjectURL(await artifact(
|
|
118
|
+
const url = URL.createObjectURL(await artifact(target, session, a.name));
|
|
110
119
|
const link = document.createElement('a');
|
|
111
120
|
link.href = url;
|
|
112
121
|
link.download = a.name.split('/').pop() || a.name;
|
|
@@ -118,7 +127,7 @@ export function Artifacts({ session, sandbox, repo, branch, mode, project, site,
|
|
|
118
127
|
}
|
|
119
128
|
};
|
|
120
129
|
// A preview is opened with a fresh ticket, in a tab of its own.
|
|
121
|
-
const open = (a) => away(preview(
|
|
130
|
+
const open = (a) => away(preview(target, sandbox, a.port)).catch((e) => setNote(e.message));
|
|
122
131
|
const items = [];
|
|
123
132
|
for (const a of left.value.artifacts) {
|
|
124
133
|
const stored = a.kind === 'file' || a.kind === 'patch';
|
|
@@ -129,26 +138,26 @@ export function Artifacts({ session, sandbox, repo, branch, mode, project, site,
|
|
|
129
138
|
key: `${a.kind}:${a.name}`,
|
|
130
139
|
icon: KIND[a.kind].icon,
|
|
131
140
|
title: link ? a.url.replace(/^https:\/\//, '') : a.name,
|
|
132
|
-
note: KIND[a.kind].note(a),
|
|
141
|
+
note: KIND[a.kind].note(a, t),
|
|
133
142
|
href: link ? a.url : undefined,
|
|
134
143
|
onPress: stored ? () => void save(a) : a.kind === 'preview' ? () => void open(a) : undefined,
|
|
135
144
|
action: stored ? _jsx(Download, { size: 14 }) : a.kind === 'preview' ? _jsx(ExternalLink, { size: 14 }) : undefined,
|
|
136
145
|
});
|
|
137
146
|
}
|
|
138
147
|
if (site)
|
|
139
|
-
items.push({ key: 'site', icon: _jsx(Globe, { size: 16 }), title: site.replace(/^https:\/\//, ''), note: 'Where this run is published', href: site });
|
|
148
|
+
items.push({ key: 'site', icon: _jsx(Globe, { size: 16 }), title: site.replace(/^https:\/\//, ''), note: t('Where this run is published'), href: site });
|
|
140
149
|
if (pr.href)
|
|
141
|
-
items.push({ key: 'pr', icon: _jsx(GitPullRequest, { size: 16 }), title:
|
|
150
|
+
items.push({ key: 'pr', icon: _jsx(GitPullRequest, { size: 16 }), title: t('Pull request {pr}', { pr: pr.label }), note: t('Opened on the forge'), href: pr.href });
|
|
142
151
|
if (branch)
|
|
143
|
-
items.push({ key: 'branch', icon: _jsx(GitBranch, { size: 16 }), title: branch, note: repo ?
|
|
152
|
+
items.push({ key: 'branch', icon: _jsx(GitBranch, { size: 16 }), title: branch, note: repo ? t('The branch this run pushes to in {repo}', { repo }) : t('The branch this run pushes to') });
|
|
144
153
|
if (mode === 'setup')
|
|
145
|
-
items.push({ key: 'environment', icon: _jsx(Settings2, { size: 16 }), title: 'Environment proposal', note: 'The install and start scripts this run found', onPress: onEnvironment });
|
|
154
|
+
items.push({ key: 'environment', icon: _jsx(Settings2, { size: 16 }), title: t('Environment proposal'), note: t('The install and start scripts this run found'), onPress: onEnvironment });
|
|
146
155
|
if (project)
|
|
147
|
-
items.push({ key: 'project', icon: _jsx(Layers, { size: 16 }), title: project, note: 'The project this run builds', onPress: () => host.go(project) });
|
|
156
|
+
items.push({ key: 'project', icon: _jsx(Layers, { size: 16 }), title: project, note: t('The project this run builds'), onPress: () => host.go(project) });
|
|
148
157
|
if (!items.length)
|
|
149
|
-
return _jsx(Soft, { children: left.error?.message || (left.loading ? 'Reading what this run left…' : 'This run has produced no artifacts yet.') });
|
|
158
|
+
return _jsx(Soft, { children: left.error?.message || (left.loading ? t('Reading what this run left…') : t('This run has produced no artifacts yet.')) });
|
|
150
159
|
return (_jsxs(YStack, { gap: "$2", children: [note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: items.map((a, i) => {
|
|
151
|
-
const row = (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", hoverStyle: a.href || a.onPress ? { bg: '$hover' } : undefined, children: [a.icon, _jsxs(YStack, { flex: 1, minW: 0,
|
|
160
|
+
const row = (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", hoverStyle: a.href || a.onPress ? { bg: '$hover' } : undefined, children: [a.icon, _jsxs(YStack, { flex: 1, minW: 0, "$platform-web": { textAlign: 'start' }, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: a.title }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: a.note })] }), a.action ?? (a.href ? _jsx(ExternalLink, { size: 14 }) : null)] }));
|
|
152
161
|
return a.href ? (_jsx(Out, { href: a.href, label: a.title, children: _jsx(YStack, { width: "100%", children: row }) }, a.key)) : a.onPress ? (_jsx(YStack, { render: "button", "aria-label": a.title, onPress: a.onPress, children: row }, a.key)) : (_jsx(YStack, { children: row }, a.key));
|
|
153
162
|
}) })] }));
|
|
154
163
|
}
|
package/lib/find.js
CHANGED
|
@@ -6,20 +6,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* the first page lands it searches the rail's own recents.
|
|
7
7
|
*/
|
|
8
8
|
import { ScrollView, SizableText, XStack, YStack } from '@hanzo/gui';
|
|
9
|
+
import { phrase } from '@hanzo/i18n';
|
|
10
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
9
11
|
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
10
12
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
11
13
|
import { page } from './api/sessions.js';
|
|
12
14
|
import { useHost, useTarget } from './host.js';
|
|
13
15
|
/** The statuses the platform filters on, and every run. */
|
|
14
16
|
const FILTERS = [
|
|
15
|
-
{ id: '', label: 'All' },
|
|
16
|
-
{ id: 'running', label: 'Running' },
|
|
17
|
-
{ id: 'paused', label: 'Paused' },
|
|
18
|
-
{ id: 'done', label: 'Done' },
|
|
19
|
-
{ id: 'error', label: 'Error' },
|
|
17
|
+
{ id: '', label: phrase('All') },
|
|
18
|
+
{ id: 'running', label: phrase('Running') },
|
|
19
|
+
{ id: 'paused', label: phrase('Paused') },
|
|
20
|
+
{ id: 'done', label: phrase('Done') },
|
|
21
|
+
{ id: 'error', label: phrase('Error') },
|
|
20
22
|
];
|
|
21
23
|
const PAGE = 50;
|
|
22
24
|
export function Find({ open, onOpenChange, recents, onOpen, }) {
|
|
25
|
+
const { t: say } = useI18n();
|
|
23
26
|
const host = useHost();
|
|
24
27
|
const t = useTarget();
|
|
25
28
|
const signed = Boolean(host.person);
|
|
@@ -64,14 +67,14 @@ export function Find({ open, onOpenChange, recents, onOpen, }) {
|
|
|
64
67
|
const hits = useMemo(() => {
|
|
65
68
|
const n = q.trim().toLowerCase();
|
|
66
69
|
const all = rows
|
|
67
|
-
? rows.map((s) => ({ id: s.id, title: s.title || 'Untitled run', line: [s.repo, s.status].filter(Boolean).join(' · ') }))
|
|
70
|
+
? rows.map((s) => ({ id: s.id, title: s.title || say('Untitled run'), line: [s.repo, s.status].filter(Boolean).join(' · ') }))
|
|
68
71
|
: status
|
|
69
72
|
? []
|
|
70
73
|
: recents.map((r) => ({ id: r.id, title: r.title, line: '' }));
|
|
71
74
|
return n ? all.filter((h) => `${h.title} ${h.line}`.toLowerCase().includes(n)) : all;
|
|
72
|
-
}, [q, rows, recents, status]);
|
|
73
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 520, children: [_jsx(DialogTitle, { children:
|
|
75
|
+
}, [q, rows, recents, status, say]);
|
|
76
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 520, children: [_jsx(DialogTitle, { children: say('Find a run') }), _jsx(Input, { autoFocus: true, value: q, onChangeText: setQ, placeholder: say('Search runs…'), "aria-label": say('Search runs'), onKeyDown: (e) => {
|
|
74
77
|
if (e.key === 'Enter' && hits[0])
|
|
75
78
|
onOpen(hits[0].id);
|
|
76
|
-
} }), _jsx(XStack, { gap: "$1", flexWrap: "wrap", role: "group", "aria-label":
|
|
79
|
+
} }), _jsx(XStack, { gap: "$1", flexWrap: "wrap", role: "group", "aria-label": say('Status'), children: FILTERS.map((f) => (_jsx(XStack, { render: "button", "aria-label": say(f.label), "aria-pressed": status === f.id, onPress: () => setStatus(f.id), px: "$2.5", py: "$1", rounded: "$10", borderWidth: 1, borderColor: status === f.id ? '$ink' : '$borderColor', bg: status === f.id ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$1", color: status === f.id ? '$ink' : '$soft', children: say(f.label) }) }, f.id || 'all'))) }), _jsx(ScrollView, { maxH: 360, children: _jsxs(YStack, { gap: "$1", role: "list", children: [hits.map((h) => (_jsxs(XStack, { role: "listitem", render: "button", "aria-label": h.title, onPress: () => onOpen(h.id), px: "$2", py: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, items: "center", gap: "$3", children: [_jsx(SizableText, { flex: 1, minW: 0, size: "$2", color: "$ink", numberOfLines: 1, style: { textAlign: 'left' }, children: h.title }), h.line ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, shrink: 0, maxW: "45%", children: h.line })) : null] }, h.id))), !loading && !error && hits.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", children: rows && next ? say('No run read so far matches.') : say('No run matches.') })) : null] }) }), error ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: error })) : null, loading ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: say('Reading runs…') })) : next ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", onPress: () => void read(next), children: say('Older runs') }) })) : null] }) }));
|
|
77
80
|
}
|
package/lib/foot.js
CHANGED
|
@@ -9,6 +9,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
9
9
|
*/
|
|
10
10
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
11
|
import { Popover, SizableText, XStack, YStack } from '@hanzo/gui';
|
|
12
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
12
13
|
import { CreditCard, Gauge, LifeBuoy, LogOut, Settings, Slack as Mark, Sparkles, X } from '@hanzogui/lucide-icons-2';
|
|
13
14
|
import { MenuLabel, MenuRow, MenuRule } from '@hanzo/ui/product';
|
|
14
15
|
import { useHost, useTarget } from './host.js';
|
|
@@ -18,7 +19,8 @@ import { path } from './route.js';
|
|
|
18
19
|
export const DOCS = 'https://docs.hanzo.ai/docs/dev';
|
|
19
20
|
/** Hanzo in Slack is set up under Settings; the card goes there, and once dismissed stays gone. */
|
|
20
21
|
export function Slack({ onOpen, onDismiss }) {
|
|
21
|
-
|
|
22
|
+
const { t } = useI18n();
|
|
23
|
+
return (_jsxs(XStack, { items: "center", gap: "$2.5", px: "$3", py: "$2.5", rounded: "var(--radius-lg)", borderWidth: 1, borderColor: "$borderColor", bg: "var(--glass)", children: [_jsx(Mark, { size: 16 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: t('Try Hanzo in Slack') }), _jsx(XStack, { render: "button", "aria-label": t('Set up Hanzo in Slack'), onPress: onOpen, cursor: "pointer", children: _jsx(SizableText, { size: "$1", color: "$soft", textDecorationLine: "underline", children: t('Set up') }) })] }), _jsx(XStack, { render: "button", "aria-label": t('Dismiss'), onPress: onDismiss, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 14 }) })] }));
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
26
|
* The account menu's open state, for whatever row opens it.
|
|
@@ -54,6 +56,7 @@ export function useWho() {
|
|
|
54
56
|
* open rail, beside it on the collapsed one.
|
|
55
57
|
*/
|
|
56
58
|
export function Who({ open, onOpenChange, narrow }) {
|
|
59
|
+
const { t } = useI18n();
|
|
57
60
|
const host = useHost();
|
|
58
61
|
const standing = useStanding(useTarget(), open);
|
|
59
62
|
// The organizations this person acts in; choosing one reloads the page scoped to it.
|
|
@@ -62,14 +65,14 @@ export function Who({ open, onOpenChange, narrow }) {
|
|
|
62
65
|
onOpenChange(false);
|
|
63
66
|
host.go(p);
|
|
64
67
|
};
|
|
65
|
-
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 6, children: [_jsx(Popover.Anchor, { position: "absolute", l: 8, b: 8, width: narrow ? 48 : 256, height: 36, pointerEvents: "none" }), _jsx(Popover.Content, { role: "menu", "aria-label":
|
|
68
|
+
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 6, children: [_jsx(Popover.Anchor, { position: "absolute", l: 8, b: 8, width: narrow ? 48 : 256, height: 36, pointerEvents: "none" }), _jsx(Popover.Content, { role: "menu", "aria-label": t('Account'), items: "stretch", borderWidth: 1, elevation: "$2", px: "$1", py: "$1", width: 260, bg: "$panel", borderColor: "$borderColor", children: _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person?.email || host.person?.name || '' }), _jsx(MenuRule, {}), _jsx(Meter, { read: standing, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), orgs.length ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuLabel, { children: t('Organization') }), _jsx(YStack, { role: "radiogroup", "aria-label": t('Organizations'), gap: "$1", children: orgs.map((o) => (_jsx(MenuRow, { label: o, active: o === host.org, onPress: () => {
|
|
66
69
|
onOpenChange(false);
|
|
67
70
|
if (o !== host.org)
|
|
68
71
|
host.chooseOrg?.(o);
|
|
69
|
-
} }, o))) })] })) : null, _jsx(MenuRule, {}), _jsx(MenuRow, { label:
|
|
72
|
+
} }, o))) })] })) : null, _jsx(MenuRule, {}), _jsx(MenuRow, { label: t('Settings'), icon: _jsx(Settings, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(MenuRow, { label: t('Usage'), icon: _jsx(Gauge, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(MenuRow, { label: t('Billing'), icon: _jsx(CreditCard, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(MenuRow, { label: t('View all plans'), icon: _jsx(Sparkles, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(MenuRow, { label: t('Get help'), icon: _jsx(LifeBuoy, { size: 16 }), onPress: () => {
|
|
70
73
|
onOpenChange(false);
|
|
71
74
|
window.open(DOCS, '_blank', 'noopener,noreferrer');
|
|
72
|
-
} }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuRow, { label:
|
|
75
|
+
} }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuRow, { label: t('Log out'), icon: _jsx(LogOut, { size: 16 }), onPress: () => {
|
|
73
76
|
onOpenChange(false);
|
|
74
77
|
host.signOut?.();
|
|
75
78
|
} })] })) : null] }) })] }));
|