@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.
Files changed (167) hide show
  1. package/README.md +22 -4
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +36 -35
  5. package/lib/api/agents.js +66 -29
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/machines.d.ts +0 -15
  29. package/lib/api/machines.js +6 -5
  30. package/lib/api/mcp.d.ts +0 -9
  31. package/lib/api/mcp.js +3 -2
  32. package/lib/api/members.d.ts +0 -15
  33. package/lib/api/members.js +4 -3
  34. package/lib/api/memory.d.ts +0 -11
  35. package/lib/api/memory.js +2 -1
  36. package/lib/api/places.d.ts +1 -9
  37. package/lib/api/places.js +12 -1
  38. package/lib/api/plugins.d.ts +1 -16
  39. package/lib/api/plugins.js +4 -3
  40. package/lib/api/profile.d.ts +0 -10
  41. package/lib/api/profile.js +5 -4
  42. package/lib/api/projects.d.ts +0 -14
  43. package/lib/api/projects.js +2 -1
  44. package/lib/api/provider.d.ts +2 -14
  45. package/lib/api/provider.js +12 -10
  46. package/lib/api/sandbox.d.ts +0 -17
  47. package/lib/api/sandbox.js +5 -4
  48. package/lib/api/sessions.d.ts +0 -17
  49. package/lib/api/sessions.js +5 -4
  50. package/lib/api/skills.d.ts +1 -15
  51. package/lib/api/skills.js +5 -4
  52. package/lib/api/studio.d.ts +0 -14
  53. package/lib/api/studio.js +6 -5
  54. package/lib/api/turn.d.ts +6 -28
  55. package/lib/api/turn.js +105 -49
  56. package/lib/api/verdict.d.ts +0 -9
  57. package/lib/api/verdict.js +2 -1
  58. package/lib/api/webhooks.d.ts +1 -14
  59. package/lib/api/webhooks.js +5 -4
  60. package/lib/ask.js +7 -4
  61. package/lib/automations.d.ts +5 -4
  62. package/lib/automations.js +95 -74
  63. package/lib/back.js +10 -4
  64. package/lib/builder.js +7 -5
  65. package/lib/carry.js +20 -16
  66. package/lib/customize/agents.d.ts +3 -0
  67. package/lib/customize/agents.js +152 -55
  68. package/lib/customize/connectors.js +62 -48
  69. package/lib/customize/index.js +45 -14
  70. package/lib/customize/plugins.js +34 -22
  71. package/lib/customize/say.d.ts +13 -1
  72. package/lib/customize/say.js +15 -21
  73. package/lib/customize/skills.js +31 -22
  74. package/lib/customize/ui.d.ts +5 -9
  75. package/lib/customize/ui.js +26 -16
  76. package/lib/data.d.ts +1 -1
  77. package/lib/data.js +3 -2
  78. package/lib/desk.js +45 -33
  79. package/lib/door.js +35 -10
  80. package/lib/environment.js +25 -19
  81. package/lib/files.js +43 -34
  82. package/lib/find.js +12 -9
  83. package/lib/foot.js +7 -4
  84. package/lib/forge.js +61 -40
  85. package/lib/gear.d.ts +2 -1
  86. package/lib/gear.js +6 -4
  87. package/lib/git.js +29 -19
  88. package/lib/host.d.ts +3 -1
  89. package/lib/landing.d.ts +5 -5
  90. package/lib/landing.js +52 -44
  91. package/lib/library.js +73 -51
  92. package/lib/meter.d.ts +1 -1
  93. package/lib/meter.js +14 -5
  94. package/lib/mic.js +12 -5
  95. package/lib/mind.d.ts +1 -0
  96. package/lib/mind.js +5 -3
  97. package/lib/nav.d.ts +6 -5
  98. package/lib/nav.js +37 -30
  99. package/lib/panel.d.ts +2 -1
  100. package/lib/panel.js +52 -36
  101. package/lib/plan.d.ts +4 -20
  102. package/lib/plan.js +26 -10
  103. package/lib/plans.js +49 -30
  104. package/lib/plugins/design.js +3 -2
  105. package/lib/plugins/index.d.ts +23 -4
  106. package/lib/plugins/index.js +11 -26
  107. package/lib/plugins/pdf.js +2 -1
  108. package/lib/plugins/project.js +6 -3
  109. package/lib/plugins/research.js +3 -2
  110. package/lib/plugins/sketch.js +6 -3
  111. package/lib/plugins/slides.js +3 -2
  112. package/lib/plugins/template.js +6 -3
  113. package/lib/plus.js +25 -13
  114. package/lib/prefs.d.ts +0 -14
  115. package/lib/prefs.js +2 -1
  116. package/lib/project.js +69 -62
  117. package/lib/prompt.d.ts +12 -11
  118. package/lib/prompt.js +38 -20
  119. package/lib/publish.js +17 -12
  120. package/lib/reply.js +6 -3
  121. package/lib/run.js +72 -54
  122. package/lib/say.d.ts +2 -0
  123. package/lib/say.js +7 -0
  124. package/lib/scheduled.js +54 -43
  125. package/lib/section.d.ts +1 -0
  126. package/lib/section.js +8 -2
  127. package/lib/settings/account.d.ts +1 -0
  128. package/lib/settings/account.js +31 -21
  129. package/lib/settings/billing.js +55 -28
  130. package/lib/settings/capabilities.js +26 -15
  131. package/lib/settings/card.js +19 -12
  132. package/lib/settings/code.js +22 -11
  133. package/lib/settings/credits.d.ts +4 -4
  134. package/lib/settings/credits.js +34 -21
  135. package/lib/settings/environments.js +21 -14
  136. package/lib/settings/general.js +26 -14
  137. package/lib/settings/index.js +6 -3
  138. package/lib/settings/integrations.js +42 -28
  139. package/lib/settings/keys.js +37 -19
  140. package/lib/settings/list.d.ts +0 -5
  141. package/lib/settings/list.js +24 -15
  142. package/lib/settings/machines.d.ts +3 -0
  143. package/lib/settings/machines.js +63 -38
  144. package/lib/settings/members.js +23 -14
  145. package/lib/settings/memory.js +12 -6
  146. package/lib/settings/notifications.js +32 -19
  147. package/lib/settings/plan.d.ts +1 -1
  148. package/lib/settings/plan.js +40 -17
  149. package/lib/settings/privacy.js +26 -13
  150. package/lib/settings/ui.d.ts +3 -2
  151. package/lib/settings/ui.js +9 -3
  152. package/lib/settings/usage.js +7 -3
  153. package/lib/shelf.js +18 -15
  154. package/lib/side.d.ts +1 -26
  155. package/lib/side.js +11 -5
  156. package/lib/standing.d.ts +2 -10
  157. package/lib/standing.js +5 -3
  158. package/lib/suggest.d.ts +8 -28
  159. package/lib/suggest.js +39 -4
  160. package/lib/suggestions.js +14 -5
  161. package/lib/summary.d.ts +1 -1
  162. package/lib/summary.js +4 -1
  163. package/lib/sync.js +22 -12
  164. package/lib/tools.d.ts +4 -3
  165. package/lib/tools.js +10 -7
  166. package/lib/transcript.js +10 -8
  167. package/package.json +4 -2
@@ -4,11 +4,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  * first. Each can be forgotten, and anything said in the box is remembered as
5
5
  * written. The memories are the person's own; nobody else in the organization
6
6
  * reads them.
7
+ *
8
+ * Its words are the reader's language (@hanzo/i18n `t`); a memory is drawn as
9
+ * it was said.
7
10
  */
8
11
  import { SizableText, XStack, YStack } from '@hanzo/gui';
12
+ import { useI18n } from '@hanzo/i18n/react';
9
13
  import { Trash2 } from '@hanzogui/lucide-icons-2';
10
14
  import { Button, Input } from '@hanzo/ui';
11
15
  import { useState } from 'react';
16
+ import { told } from '../api/call.js';
12
17
  import { forget, memories, remember } from '../api/memory.js';
13
18
  import { useRead } from '../data.js';
14
19
  import { useHost, useTarget } from '../host.js';
@@ -19,15 +24,16 @@ const day = (at) => {
19
24
  return Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
20
25
  };
21
26
  export function Memory() {
27
+ const { t } = useI18n();
22
28
  const host = useHost();
23
- const t = useTarget();
29
+ const target = useTarget();
24
30
  const signed = Boolean(host.person);
25
- const list = useRead(signed ? () => memories(t) : null, [], [t, signed]);
31
+ const list = useRead(signed ? () => memories(target) : null, [], [target, signed]);
26
32
  const [said, setSaid] = useState('');
27
33
  const [busy, setBusy] = useState('');
28
34
  const [note, setNote] = useState('');
29
35
  if (!signed) {
30
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Memory" }), _jsx(Soft, { children: "Sign in to see what Hanzo remembers." })] }));
36
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: t('Memory') }), _jsx(Soft, { children: t('Sign in to see what Hanzo remembers.') })] }));
31
37
  }
32
38
  const act = async (what, run, done) => {
33
39
  setBusy(what);
@@ -39,7 +45,7 @@ export function Memory() {
39
45
  return true;
40
46
  }
41
47
  catch (e) {
42
- setNote(e.message);
48
+ setNote(told(e, t));
43
49
  return false;
44
50
  }
45
51
  finally {
@@ -50,7 +56,7 @@ export function Memory() {
50
56
  const words = said.trim();
51
57
  if (!words)
52
58
  return;
53
- void act('remember', () => remember(t, words), 'Remembered').then((took) => took && setSaid(''));
59
+ void act('remember', () => remember(target, words), t('Remembered')).then((took) => took && setSaid(''));
54
60
  };
55
- return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Memory", detail: "What Hanzo remembers about you here, to carry from one run to the next. Only you see it." }), _jsx(Group, { title: "Tell Hanzo what to remember or change", children: _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(YStack, { flex: 1, minW: 0, children: _jsx(Input, { value: said, onChangeText: setSaid, onSubmitEditing: keep, "aria-label": "Tell Hanzo what to remember or change", placeholder: "I prefer small pull requests, one change each." }) }), _jsx(Button, { size: "sm", disabled: !said.trim() || busy !== '', onPress: keep, children: "Remember" })] }) }), _jsx(Group, { title: "What Hanzo remembers", detail: list.value.length ? `${list.value.length} ${list.value.length === 1 ? 'memory' : 'memories'}, newest first.` : undefined, children: list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && !list.value.length ? (_jsx(Soft, { children: "Reading memories\u2026" })) : !list.value.length ? (_jsx(Soft, { children: "Nothing yet. What you tell Hanzo to remember shows here." })) : (_jsx(Card, { children: list.value.map((m, i) => (_jsxs(XStack, { items: "flex-start", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: m.content }), _jsx(SizableText, { size: "$1", color: "$soft", children: [m.kind, day(m.updated)].filter(Boolean).join(' · ') })] }), _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": `Forget ${m.content.slice(0, 60)}`, disabled: busy !== '', onPress: () => void act(m.id, () => forget(t, m.id), 'Forgotten'), children: _jsx(Trash2, { size: 14 }) })] }, m.id))) })) }), _jsx(Note, { children: note })] }));
61
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: t('Memory'), detail: t('What Hanzo remembers about you here, to carry from one run to the next. Only you see it.') }), _jsx(Group, { title: t('Tell Hanzo what to remember or change'), children: _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(YStack, { flex: 1, minW: 0, children: _jsx(Input, { value: said, onChangeText: setSaid, onSubmitEditing: keep, "aria-label": t('Tell Hanzo what to remember or change'), placeholder: t('I prefer small pull requests, one change each.') }) }), _jsx(Button, { size: "sm", disabled: !said.trim() || busy !== '', onPress: keep, children: t('Remember') })] }) }), _jsx(Group, { title: t('What Hanzo remembers'), detail: list.value.length ? (list.value.length === 1 ? t('1 memory, newest first.') : t('{n} memories, newest first.', { n: list.value.length })) : undefined, children: list.error ? (_jsx(Soft, { children: told(list.error, t) })) : list.loading && !list.value.length ? (_jsx(Soft, { children: t('Reading memories…') })) : !list.value.length ? (_jsx(Soft, { children: t('Nothing yet. What you tell Hanzo to remember shows here.') })) : (_jsx(Card, { children: list.value.map((m, i) => (_jsxs(XStack, { items: "flex-start", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: m.content }), _jsx(SizableText, { size: "$1", color: "$soft", children: [m.kind, day(m.updated)].filter(Boolean).join(' · ') })] }), _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": t('Forget {memory}', { memory: m.content.slice(0, 60) }), disabled: busy !== '', onPress: () => void act(m.id, () => forget(target, m.id), t('Forgotten')), children: _jsx(Trash2, { size: 14 }) })] }, m.id))) })) }), _jsx(Note, { children: note })] }));
56
62
  }
@@ -4,21 +4,27 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  * every matching event is POSTed to, signed with a secret shown once, when the
5
5
  * webhook is added. A test sends one signed event now; the log says what each
6
6
  * delivery got back.
7
+ *
8
+ * Its words are the reader's language (@hanzo/i18n `t`); an address, an event
9
+ * pattern, a description and what an endpoint answered are drawn as given.
7
10
  */
8
11
  import { SizableText, XStack, YStack } from '@hanzo/gui';
12
+ import { useI18n } from '@hanzo/i18n/react';
9
13
  import { Copy, Plus, Webhook, X } from '@hanzogui/lucide-icons-2';
10
14
  import { Button, Input } from '@hanzo/ui';
11
15
  import { useState } from 'react';
16
+ import { told } from '../api/call.js';
12
17
  import { add, deliveries, endpoints, patterns, refuse, remove, test } from '../api/webhooks.js';
13
18
  import { useRead } from '../data.js';
14
19
  import { useHost, useTarget } from '../host.js';
15
20
  import { Card, day, Field, Group, Heading, Note, Row, Soft } from './ui.js';
16
21
  const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
17
22
  export function Notifications() {
23
+ const { t } = useI18n();
18
24
  const host = useHost();
19
- const t = useTarget();
25
+ const target = useTarget();
20
26
  const signed = Boolean(host.person);
21
- const list = useRead(signed ? () => endpoints(t) : null, [], [t, signed]);
27
+ const list = useRead(signed ? () => endpoints(target) : null, [], [target, signed]);
22
28
  const [adding, setAdding] = useState(false);
23
29
  const [url, setUrl] = useState('');
24
30
  const [events, setEvents] = useState('');
@@ -28,7 +34,7 @@ export function Notifications() {
28
34
  const [working, setWorking] = useState(false);
29
35
  const [note, setNote] = useState('');
30
36
  if (!signed) {
31
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Notifications" }), _jsx(Soft, { children: "Sign in to see this organization\u2019s webhooks." })] }));
37
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: t('Notifications') }), _jsx(Soft, { children: t('Sign in to see this organization’s webhooks.') })] }));
32
38
  }
33
39
  const act = async (what) => {
34
40
  setWorking(true);
@@ -37,7 +43,7 @@ export function Notifications() {
37
43
  setNote(await what());
38
44
  }
39
45
  catch (e) {
40
- setNote(e.message);
46
+ setNote(told(e, t));
41
47
  }
42
48
  finally {
43
49
  setWorking(false);
@@ -47,34 +53,39 @@ export function Notifications() {
47
53
  const draft = { url, events: patterns(events), description };
48
54
  const why = refuse(draft);
49
55
  if (why) {
50
- setNote(why);
56
+ setNote(t(why));
51
57
  return;
52
58
  }
53
59
  void act(async () => {
54
- const e = await add(t, draft);
60
+ const e = await add(target, draft);
55
61
  setSecret(e.secret);
56
62
  setAdding(false);
57
63
  setUrl('');
58
64
  setEvents('');
59
65
  setDescription('');
60
66
  list.reload();
61
- return `${e.url} is added`;
67
+ return t('{url} is added', { url: e.url });
62
68
  });
63
69
  };
64
70
  const trial = (e) => void act(async () => {
65
- const r = await test(t, e.id);
71
+ const r = await test(target, e.id);
66
72
  if (log?.id === e.id)
67
- setLog({ id: e.id, rows: await deliveries(t, e.id) });
73
+ setLog({ id: e.id, rows: await deliveries(target, e.id) });
74
+ // What the endpoint answered is its own; the sentence around it is ours.
68
75
  return r.delivered
69
- ? `The test reached ${e.url}: ${r.status} in ${r.ms} ms`
70
- : `The test did not reach ${e.url}: ${r.error || (r.status ? `it answered ${r.status}` : 'no answer')}`;
76
+ ? t('The test reached {url}: {status} in {ms} ms', { url: e.url, status: r.status, ms: r.ms })
77
+ : r.error
78
+ ? t('The test did not reach {url}: {error}', { url: e.url, error: r.error })
79
+ : r.status
80
+ ? t('The test did not reach {url}: it answered {status}', { url: e.url, status: r.status })
81
+ : t('The test did not reach {url}: no answer', { url: e.url });
71
82
  });
72
83
  const drop = (e) => void act(async () => {
73
- await remove(t, e.id);
84
+ await remove(target, e.id);
74
85
  if (log?.id === e.id)
75
86
  setLog(null);
76
87
  list.reload();
77
- return `${e.url} is deleted`;
88
+ return t('{url} is deleted', { url: e.url });
78
89
  });
79
90
  const show = (e) => {
80
91
  if (log?.id === e.id) {
@@ -82,16 +93,18 @@ export function Notifications() {
82
93
  return;
83
94
  }
84
95
  void act(async () => {
85
- setLog({ id: e.id, rows: await deliveries(t, e.id) });
96
+ setLog({ id: e.id, rows: await deliveries(target, e.id) });
86
97
  return '';
87
98
  });
88
99
  };
89
- return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Notifications", detail: `Webhooks: events in ${host.org ?? 'this organization'}, POSTed and signed to an address you run.`, action: adding || !host.admin ? undefined : (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " Add webhook"] })) }), adding ? (_jsxs(YStack, { gap: "$3", p: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", children: [_jsx(Field, { label: "Endpoint URL", hint: "https only.", children: _jsx(Input, { value: url, onChangeText: setUrl, placeholder: "https://example.com/hooks/hanzo", "aria-label": "Endpoint URL", autoCapitalize: "none", inputMode: "url" }) }), _jsx(Field, { label: "Events", hint: "Subject patterns, comma separated, such as commerce.order.> \u2014 leave empty for every event.", children: _jsx(Input, { value: events, onChangeText: setEvents, placeholder: "commerce.order.>, event.>", "aria-label": "Events", autoCapitalize: "none" }) }), _jsx(Field, { label: "Description", children: _jsx(Input, { value: description, onChangeText: setDescription, placeholder: "What this endpoint is for", "aria-label": "Description" }) }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAdding(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working || !url.trim(), onPress: create, children: "Add webhook" })] })] })) : null, secret ? (_jsxs(YStack, { gap: "$2", p: "$4", borderWidth: 1, borderColor: "$ink", rounded: "$3", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: "Signing secret" }), _jsx(SizableText, { size: "$1", color: "$soft", children: "Shown this once. Keep it where your endpoint checks each delivery\u2019s signature." }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", style: { ...mono, wordBreak: 'break-all' }, "aria-label": "Signing secret", children: secret }), _jsxs(Button, { size: "sm", variant: "outline", onPress: () => void navigator.clipboard?.writeText(secret).then(() => setNote('The secret is copied')), children: [_jsx(Copy, { size: 14 }), " Copy"] }), _jsx(XStack, { render: "button", "aria-label": "Hide the secret", p: "$1", onPress: () => setSecret(''), children: _jsx(X, { size: 14 }) })] })] })) : null, _jsx(Group, { title: "Webhooks", children: list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && !list.value.length ? (_jsx(Soft, { children: "Reading webhooks\u2026" })) : list.value.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: "No webhooks yet." }) })) : (_jsx(Card, { children: list.value.map((e, i) => (_jsxs(YStack, { borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsx(Row, { first: true, mono: true, leading: _jsx(Webhook, { size: 16 }), title: e.url, detail: [
100
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: t('Notifications'), detail: host.org
101
+ ? t('Webhooks: events in {org}, POSTed and signed to an address you run.', { org: host.org })
102
+ : t('Webhooks: events in this organization, POSTed and signed to an address you run.'), action: adding || !host.admin ? undefined : (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " ", t('Add webhook')] })) }), adding ? (_jsxs(YStack, { gap: "$3", p: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", children: [_jsx(Field, { label: t('Endpoint URL'), hint: t('https only.'), children: _jsx(Input, { value: url, onChangeText: setUrl, placeholder: "https://example.com/hooks/hanzo", "aria-label": t('Endpoint URL'), autoCapitalize: "none", inputMode: "url" }) }), _jsx(Field, { label: t('Events'), hint: t('Subject patterns, comma separated, such as {example} — leave empty for every event.', { example: 'commerce.order.>' }), children: _jsx(Input, { value: events, onChangeText: setEvents, placeholder: "commerce.order.>, event.>", "aria-label": t('Events'), autoCapitalize: "none" }) }), _jsx(Field, { label: t('Description'), children: _jsx(Input, { value: description, onChangeText: setDescription, placeholder: t('What this endpoint is for'), "aria-label": t('Description') }) }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAdding(false), children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working || !url.trim(), onPress: create, children: t('Add webhook') })] })] })) : null, secret ? (_jsxs(YStack, { gap: "$2", p: "$4", borderWidth: 1, borderColor: "$ink", rounded: "$3", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: t('Signing secret') }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Shown this once. Keep it where your endpoint checks each delivery’s signature.') }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", fontFamily: "$mono", "$platform-web": { wordBreak: 'break-all' }, "aria-label": t('Signing secret'), children: secret }), _jsxs(Button, { size: "sm", variant: "outline", onPress: () => void navigator.clipboard?.writeText(secret).then(() => setNote(t('The secret is copied'))), children: [_jsx(Copy, { size: 14 }), " ", t('Copy')] }), _jsx(XStack, { render: "button", "aria-label": t('Hide the secret'), p: "$1", onPress: () => setSecret(''), children: _jsx(X, { size: 14 }) })] })] })) : null, _jsx(Group, { title: t('Webhooks'), children: list.error ? (_jsx(Soft, { children: told(list.error, t) })) : list.loading && !list.value.length ? (_jsx(Soft, { children: t('Reading webhooks…') })) : list.value.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: t('No webhooks yet.') }) })) : (_jsx(Card, { children: list.value.map((e, i) => (_jsxs(YStack, { borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsx(Row, { first: true, mono: true, leading: _jsx(Webhook, { size: 16 }), title: e.url, detail: [
90
103
  e.description,
91
- e.events.length ? e.events.join(', ') : 'every event',
92
- e.status === 'disabled' ? 'disabled' : '',
93
- `${e.deliveries} delivered, ${e.failures} failed this week`,
104
+ e.events.length ? e.events.join(', ') : t('every event'),
105
+ e.status === 'disabled' ? t('disabled') : '',
106
+ t('{delivered} delivered, {failed} failed this week', { delivered: e.deliveries, failed: e.failures }),
94
107
  ]
95
108
  .filter(Boolean)
96
- .join(' · '), trailing: host.admin ? (_jsx(XStack, { render: "button", "aria-label": `Delete ${e.url}`, p: "$1", onPress: () => drop(e), children: _jsx(X, { size: 14 }) })) : undefined }), _jsxs(XStack, { gap: "$2", px: "$3", pb: "$2.5", flexWrap: "wrap", children: [host.admin ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, "aria-label": `Send a test to ${e.url}`, onPress: () => trial(e), children: "Send test" })) : null, _jsx(Button, { size: "sm", variant: "ghost", disabled: working, "aria-label": `Deliveries to ${e.url}`, onPress: () => show(e), children: log?.id === e.id ? 'Hide deliveries' : 'Deliveries' })] }), log?.id === e.id ? (_jsx(YStack, { px: "$3", pb: "$3", gap: "$1.5", children: log.rows.length === 0 ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Nothing delivered yet." })) : (log.rows.map((a, n) => (_jsxs(XStack, { gap: "$3", items: "baseline", children: [_jsx(SizableText, { size: "$1", color: a.status === 'ok' ? '$ink' : '$soft', width: 64, children: a.status === 'ok' ? 'OK' : a.status === 'failed' ? 'Failed' : 'Retrying' }), _jsx(SizableText, { flex: 1, size: "$1", color: "$soft", numberOfLines: 1, style: mono, children: `${a.subject}${a.code ? ` → ${a.code}` : ''}${a.error ? ` · ${a.error}` : ''}` }), _jsx(SizableText, { size: "$1", color: "$soft", "$max-sm": { display: 'none' }, children: day(a.at) })] }, `${a.at}-${n}`)))) })) : null] }, e.id))) })) }), _jsx(Note, { children: note })] }));
109
+ .join(' · '), trailing: host.admin ? (_jsx(XStack, { render: "button", "aria-label": t('Delete {url}', { url: e.url }), p: "$1", onPress: () => drop(e), children: _jsx(X, { size: 14 }) })) : undefined }), _jsxs(XStack, { gap: "$2", px: "$3", pb: "$2.5", flexWrap: "wrap", children: [host.admin ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, "aria-label": t('Send a test to {url}', { url: e.url }), onPress: () => trial(e), children: t('Send test') })) : null, _jsx(Button, { size: "sm", variant: "ghost", disabled: working, "aria-label": t('Deliveries to {url}', { url: e.url }), onPress: () => show(e), children: log?.id === e.id ? t('Hide deliveries') : t('Deliveries') })] }), log?.id === e.id ? (_jsx(YStack, { px: "$3", pb: "$3", gap: "$1.5", children: log.rows.length === 0 ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('Nothing delivered yet.') })) : (log.rows.map((a, n) => (_jsxs(XStack, { gap: "$3", items: "baseline", children: [_jsx(SizableText, { size: "$1", color: a.status === 'ok' ? '$ink' : '$soft', width: 64, children: a.status === 'ok' ? t('OK') : a.status === 'failed' ? t('Failed') : t('Retrying') }), _jsx(SizableText, { flex: 1, size: "$1", color: "$soft", numberOfLines: 1, style: mono, children: `${a.subject}${a.code ? ` → ${a.code}` : ''}${a.error ? ` · ${a.error}` : ''}` }), _jsx(SizableText, { size: "$1", color: "$soft", "$max-sm": { display: 'none' }, children: day(a.at) })] }, `${a.at}-${n}`)))) })) : null] }, e.id))) })) }), _jsx(Note, { children: note })] }));
97
110
  }
@@ -1,4 +1,4 @@
1
- import type { Target } from '../api/call.ts';
1
+ import { type Target } from '../api/call.ts';
2
2
  import { type Standing } from '../standing.ts';
3
3
  /** The view, once read. */
4
4
  export declare function Reading({ s, target, onPlans, onBilling, now, }: {
@@ -16,17 +16,23 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
16
16
  *
17
17
  * It reads as `target`, so it needs no host: a host says where the plans and
18
18
  * Billing are (`onPlans`, `onBilling`).
19
+ *
20
+ * Its words are the reader's language (@hanzo/i18n `t`); a plan's name, a
21
+ * model's and the ways on the server offers are drawn as given.
19
22
  */
20
23
  import { SizableText, XStack, YStack } from '@hanzo/gui';
24
+ import { phrase } from '@hanzo/i18n';
25
+ import { useI18n } from '@hanzo/i18n/react';
21
26
  import { Button, Switch } from '@hanzo/ui';
22
27
  import { useState } from 'react';
23
28
  import { money } from '../api/billing.js';
29
+ import { told } from '../api/call.js';
24
30
  import { allow, CLASSES } from '../api/limits.js';
25
31
  import { INK, Title } from '../meter.js';
26
32
  import { spent, ways, when } from '../plan.js';
27
33
  import { left, useStanding } from '../standing.js';
28
34
  import { Card, day, Group, Note, Row, Soft } from './ui.js';
29
- const NAME = { premium: 'Premium models', ours: 'Hanzo models' };
35
+ const NAME = { premium: phrase('Premium models'), ours: phrase('Hanzo models') };
30
36
  const FILL = { ok: '$ink', near: '$yellow10', limited: '$red10' };
31
37
  /** A thin bar of a share used. */
32
38
  function Bar({ percent, state }) {
@@ -34,11 +40,13 @@ function Bar({ percent, state }) {
34
40
  }
35
41
  /** One measured row: what, how much of it is used, when it starts over, its bar, and what hangs under it. */
36
42
  function Gauge({ title, w, first, says, now, children }) {
43
+ const { t } = useI18n();
37
44
  const at = when(w.resets, now);
38
- return (_jsxs(YStack, { gap: "$2", px: "$3", py: "$3", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", "data-slot": "plan-window", "data-state": w.state, children: [_jsxs(XStack, { items: "baseline", justify: "space-between", flexWrap: "wrap", columnGap: "$3", rowGap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: title }), _jsx(SizableText, { size: "$1", color: INK[w.state], style: { fontVariantNumeric: 'tabular-nums' }, children: [says ?? `${w.percent}% used`, at ? `Resets ${at}` : ''].filter(Boolean).join(' · ') })] }), _jsx(Bar, { percent: w.percent, state: w.state }), children] }));
45
+ return (_jsxs(YStack, { gap: "$2", px: "$3", py: "$3", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", "data-slot": "plan-window", "data-state": w.state, children: [_jsxs(XStack, { items: "baseline", justify: "space-between", flexWrap: "wrap", columnGap: "$3", rowGap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: title }), _jsx(SizableText, { size: "$1", color: INK[w.state], style: { fontVariantNumeric: 'tabular-nums' }, children: [says ?? t('{n}% used', { n: w.percent }), at ? t('Resets {time}', { time: at }) : ''].filter(Boolean).join(' · ') })] }), _jsx(Bar, { percent: w.percent, state: w.state }), children] }));
39
46
  }
40
47
  /** The view, once read. */
41
48
  export function Reading({ s, target, onPlans, onBilling, now, }) {
49
+ const { t } = useI18n();
42
50
  const [after, setAfter] = useState(null);
43
51
  const [busy, setBusy] = useState(false);
44
52
  const [note, setNote] = useState('');
@@ -51,7 +59,7 @@ export function Reading({ s, target, onPlans, onBilling, now, }) {
51
59
  setAfter(await allow(target, on));
52
60
  }
53
61
  catch (e) {
54
- setNote(e.message);
62
+ setNote(told(e, t));
55
63
  }
56
64
  finally {
57
65
  setBusy(false);
@@ -71,38 +79,53 @@ export function Reading({ s, target, onPlans, onBilling, now, }) {
71
79
  const used = s.kind === 'plan' && l ? CLASSES.filter((c) => l.classes[c]?.state === 'limited') : [];
72
80
  const capped = s.kind === 'plan' ? (l?.paused[0] ?? null) : null;
73
81
  const resets = used.map((c) => l.classes[c].resets).sort()[0] ?? '';
82
+ const until = capped ? when(capped.resets, now) : '';
83
+ const model = capped ? capped.model.replace(/\*$/, '') : '';
74
84
  const notice = used.length
75
- ? spent(s.label, used, resets, now)
85
+ ? spent(s.label, used, resets, now, t)
76
86
  : capped
77
- ? `${capped.model.replace(/\*$/, '')} is paused${when(capped.resets, now) ? ` until ${when(capped.resets, now)}` : ''}${capped.fallback ? `; chat continues on ${capped.fallback}` : ''}.`
87
+ ? capped.fallback
88
+ ? until
89
+ ? t('{model} is paused until {time}; chat continues on {fallback}.', { model, time: until, fallback: capped.fallback })
90
+ : t('{model} is paused; chat continues on {fallback}.', { model, fallback: capped.fallback })
91
+ : until
92
+ ? t('{model} is paused until {time}.', { model, time: until })
93
+ : t('{model} is paused.', { model })
78
94
  : '';
79
95
  const head = (_jsxs(XStack, { items: "center", gap: "$3", flexWrap: "wrap", "data-slot": "plan-head", children: [_jsxs(YStack, { flex: 1, minW: 200, gap: "$1", children: [s.label ? _jsx(Title, { label: s.label, size: "$5" }) : null, _jsx(SizableText, { size: "$2", color: "$soft", children: s.kind === 'plan'
80
96
  ? l?.start && l.end
81
- ? `Current period ${day(l.start)} – ${day(l.end)}`
82
- : 'Your plan'
97
+ ? t('Current period {start} – {end}', { start: day(l.start), end: day(l.end) })
98
+ : t('Your plan')
83
99
  : s.kind === 'free'
84
- ? 'Limited usage of free models, from a pool every free account shares.'
85
- : 'No plan. Usage is paid from your credits.' })] }), (upgrade && !notice) || (s.kind !== 'plan' && onPlans) ? (_jsx(Button, { size: "sm", variant: s.kind === 'plan' ? 'outline' : 'primary', onPress: () => (upgrade ? go(upgrade) : onPlans?.()), children: upgrade?.label || 'Upgrade' })) : null] }));
100
+ ? t('Limited usage of free models, from a pool every free account shares.')
101
+ : t('No plan. Usage is paid from your credits.') })] }), (upgrade && !notice) || (s.kind !== 'plan' && onPlans) ? (_jsx(Button, { size: "sm", variant: s.kind === 'plan' ? 'outline' : 'primary', onPress: () => (upgrade ? go(upgrade) : onPlans?.()), children: upgrade?.label || t('Upgrade') })) : null] }));
86
102
  if (s.kind === 'free') {
87
103
  const share = left(s.allowance);
88
104
  const a = s.allowance;
89
- return (_jsxs(YStack, { gap: "$5", "data-slot": "plan-usage", "data-kind": "free", children: [head, a && share !== null ? (_jsxs(Card, { children: [_jsx(Gauge, { first: true, title: a.window === 'hour' ? 'This hour' : 'Today', w: { percent: 100 - share, state: share === 0 ? 'limited' : share <= 20 ? 'near' : 'ok', resets: a.resets }, says: `${share}% left`, now: now }), a.pool ? (_jsx(Row, { title: "Shared pool", detail: a.pool.state === 'exhausted' ? `Used up${when(a.pool.resets, now) ? ` until ${when(a.pool.resets, now)}` : ''}` : a.pool.state === 'busy' ? 'Busy. Try again in a minute.' : 'Every free request is being served.' })) : null] })) : (_jsx(Soft, { children: "The free allowance could not be read." })), _jsxs(XStack, { items: "center", gap: "$3", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$2", color: "$soft", flex: 1, minW: 200, children: "Credits pay as you go for any model, with or without a plan." }), _jsx(Button, { size: "sm", variant: "outline", onPress: onBilling, children: "Add credits" })] })] }));
105
+ return (_jsxs(YStack, { gap: "$5", "data-slot": "plan-usage", "data-kind": "free", children: [head, a && share !== null ? (_jsxs(Card, { children: [_jsx(Gauge, { first: true, title: a.window === 'hour' ? t('This hour') : t('Today'), w: { percent: 100 - share, state: share === 0 ? 'limited' : share <= 20 ? 'near' : 'ok', resets: a.resets }, says: t('{n}% left', { n: share }), now: now }), a.pool ? (_jsx(Row, { title: t('Shared pool'), detail: a.pool.state === 'exhausted'
106
+ ? when(a.pool.resets, now)
107
+ ? t('Used up until {time}', { time: when(a.pool.resets, now) })
108
+ : t('Used up')
109
+ : a.pool.state === 'busy'
110
+ ? t('Busy. Try again in a minute.')
111
+ : t('Every free request is being served.') })) : null] })) : (_jsx(Soft, { children: t('The free allowance could not be read.') })), _jsxs(XStack, { items: "center", gap: "$3", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$2", color: "$soft", flex: 1, minW: 200, children: t('Credits pay as you go for any model, with or without a plan.') }), _jsx(Button, { size: "sm", variant: "outline", onPress: onBilling, children: t('Add credits') })] })] }));
90
112
  }
91
113
  if (s.kind === 'credits') {
92
- return (_jsxs(YStack, { gap: "$5", "data-slot": "plan-usage", "data-kind": "credits", children: [head, _jsx(Card, { children: _jsx(Row, { first: true, title: "Credits", detail: "What your usage is paid from, at each model\u2019s list price.", trailing: _jsx(SizableText, { size: "$2", color: "$ink", "data-slot": "plan-balance", children: s.balance === null ? 'Unavailable' : money(s.balance) }) }) }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", onPress: onBilling, children: "Add funds" }) })] }));
114
+ return (_jsxs(YStack, { gap: "$5", "data-slot": "plan-usage", "data-kind": "credits", children: [head, _jsx(Card, { children: _jsx(Row, { first: true, title: t('Credits'), detail: t('What your usage is paid from, at each model’s list price.'), trailing: _jsx(SizableText, { size: "$2", color: "$ink", "data-slot": "plan-balance", children: s.balance === null ? t('Unavailable') : money(s.balance) }) }) }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", onPress: onBilling, children: t('Add funds') }) })] }));
93
115
  }
94
116
  const on = l?.credits ?? false;
95
- return (_jsxs(YStack, { gap: "$5", "data-slot": "plan-usage", "data-kind": "plan", children: [head, notice ? (_jsxs(XStack, { items: "center", gap: "$2.5", flexWrap: "wrap", px: "$3", py: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", "data-slot": "plan-spent", children: [_jsx(YStack, { width: 8, height: 8, rounded: 4, bg: "$red10", shrink: 0, "aria-hidden": true }), _jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 200, children: notice }), _jsx(XStack, { gap: "$2", flexWrap: "wrap", children: ways(l?.actions ?? []).map((a, i) => (_jsx(Button, { size: "sm", variant: i === 0 ? 'primary' : 'outline', disabled: busy, "data-kind": a.kind, onPress: () => go(a), children: a.label }, a.kind))) })] })) : null, !l ? (_jsx(Soft, { children: "The plan\u2019s usage could not be read." })) : !l.session && !l.day && !Object.keys(l.classes).length ? (_jsx(Soft, { children: "This plan sets no usage limits." })) : (_jsxs(_Fragment, { children: [l.session || l.day ? (_jsx(Group, { title: "Requests", detail: "Every request the plan covers counts in both.", children: _jsxs(Card, { children: [l.session ? _jsx(Gauge, { first: true, title: "Session", w: l.session, says: l.session.resets ? undefined : `${l.session.percent}% used · Starts with your next request`, now: now }) : null, l.day ? _jsx(Gauge, { first: !l.session, title: "Today", w: l.day, now: now }) : null] }) })) : null, Object.keys(l.classes).length ? (_jsx(Group, { title: "By model class", detail: "Each class\u2019s share of the billing period, and of its shorter window where the plan sets one.", children: _jsx(Card, { children: CLASSES.filter((c) => l.classes[c]).map((c, i) => {
117
+ return (_jsxs(YStack, { gap: "$5", "data-slot": "plan-usage", "data-kind": "plan", children: [head, notice ? (_jsxs(XStack, { items: "center", gap: "$2.5", flexWrap: "wrap", px: "$3", py: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", "data-slot": "plan-spent", children: [_jsx(YStack, { width: 8, height: 8, rounded: 4, bg: "$red10", shrink: 0, "aria-hidden": true }), _jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 200, children: notice }), _jsx(XStack, { gap: "$2", flexWrap: "wrap", children: ways(l?.actions ?? [], t).map((a, i) => (_jsx(Button, { size: "sm", variant: i === 0 ? 'primary' : 'outline', disabled: busy, "data-kind": a.kind, onPress: () => go(a), children: a.label }, a.kind))) })] })) : null, !l ? (_jsx(Soft, { children: t('The plan’s usage could not be read.') })) : !l.session && !l.day && !Object.keys(l.classes).length ? (_jsx(Soft, { children: t('This plan sets no usage limits.') })) : (_jsxs(_Fragment, { children: [l.session || l.day ? (_jsx(Group, { title: t('Requests'), detail: t('Every request the plan covers counts in both.'), children: _jsxs(Card, { children: [l.session ? (_jsx(Gauge, { first: true, title: t('Session'), w: l.session, says: l.session.resets ? undefined : [t('{n}% used', { n: l.session.percent }), t('Starts with your next request')].join(' · '), now: now })) : null, l.day ? _jsx(Gauge, { first: !l.session, title: t('Today'), w: l.day, now: now }) : null] }) })) : null, Object.keys(l.classes).length ? (_jsx(Group, { title: t('By model class'), detail: t('Each class’s share of the billing period, and of its shorter window where the plan sets one.'), children: _jsx(Card, { children: CLASSES.filter((c) => l.classes[c]).map((c, i) => {
96
118
  const k = l.classes[c];
97
- return (_jsx(YStack, { "data-class": c, children: _jsx(Gauge, { first: i === 0, title: NAME[c], w: k, says: k.paying === 'credits' ? `${k.percent}% used · Paying from credits` : undefined, now: now, children: k.window ? (_jsxs(XStack, { gap: "$2", items: "center", "data-slot": "plan-short", children: [_jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: "Shorter window" }), _jsx(YStack, { flex: 1, children: _jsx(Bar, { percent: k.window.percent, state: k.window.state }) }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: [`${k.window.percent}%`, when(k.window.resets, now) ? `Resets ${when(k.window.resets, now)}` : ''].filter(Boolean).join(' · ') })] })) : null }) }, c));
98
- }) }) })) : null, _jsx(Group, { title: "When the allowance runs out", detail: "Credits are separate from your plan\u2019s usage allowance. They live in Billing.", children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: "Use credits when my plan\u2019s allowance runs out", detail: on
99
- ? 'On. A model whose included usage is used keeps working, paid from credits — prepaid first, then granted credit where the model takes it — at its list price.'
100
- : 'Off. Chat continues on a free model, and other requests wait for the plan to reset.', trailing: _jsx(Switch, { "aria-label": "Use credits when my plan\u2019s allowance runs out", checked: on, disabled: busy, onCheckedChange: (next) => void choose(next) }) }), _jsx(Row, { title: "Credits", detail: s.balance === 0 ? 'There are no credits to spend yet.' : 'The balance, top-ups and auto-reload.', trailing: _jsx(Button, { size: "sm", variant: "ghost", onPress: onBilling, children: s.balance === 0 ? 'Add credits' : 'Billing' }) })] }) })] })), _jsx(Note, { children: note })] }));
119
+ return (_jsx(YStack, { "data-class": c, children: _jsx(Gauge, { first: i === 0, title: t(NAME[c]), w: k, says: k.paying === 'credits' ? [t('{n}% used', { n: k.percent }), t('Paying from credits')].join(' · ') : undefined, now: now, children: k.window ? (_jsxs(XStack, { gap: "$2", items: "center", "data-slot": "plan-short", children: [_jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: t('Shorter window') }), _jsx(YStack, { flex: 1, children: _jsx(Bar, { percent: k.window.percent, state: k.window.state }) }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: [`${k.window.percent}%`, when(k.window.resets, now) ? t('Resets {time}', { time: when(k.window.resets, now) }) : ''].filter(Boolean).join(' · ') })] })) : null }) }, c));
120
+ }) }) })) : null, _jsx(Group, { title: t('When the allowance runs out'), detail: t('Credits are separate from your plan’s usage allowance. They live in Billing.'), children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: t('Use credits when my plan’s allowance runs out'), detail: on
121
+ ? t('On. A model whose included usage is used keeps working, paid from credits — prepaid first, then granted credit where the model takes it — at its list price.')
122
+ : t('Off. Chat continues on a free model, and other requests wait for the plan to reset.'), trailing: _jsx(Switch, { "aria-label": t('Use credits when my plan’s allowance runs out'), checked: on, disabled: busy, onCheckedChange: (next) => void choose(next) }) }), _jsx(Row, { title: t('Credits'), detail: s.balance === 0 ? t('There are no credits to spend yet.') : t('The balance, top-ups and auto-reload.'), trailing: _jsx(Button, { size: "sm", variant: "ghost", onPress: onBilling, children: s.balance === 0 ? t('Add credits') : t('Billing') }) })] }) })] })), _jsx(Note, { children: note })] }));
101
123
  }
102
124
  /** The plan's usage, read as `target`. */
103
125
  export function Plan({ target, onPlans, onBilling, now }) {
126
+ const { t } = useI18n();
104
127
  const read = useStanding(target);
105
128
  if (read.value)
106
129
  return _jsx(Reading, { s: read.value, target: target, onPlans: onPlans, onBilling: onBilling, now: now });
107
- return _jsx(Soft, { children: read.error ? read.error.message : 'Reading the plan…' });
130
+ return _jsx(Soft, { children: read.error ? told(read.error, t) : t('Reading the plan…') });
108
131
  }
@@ -4,36 +4,42 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  * Hanzo may train on their data, and whether anonymous usage is counted — and
5
5
  * who can see the organization's projects: a public one is in the community
6
6
  * catalogue with its source readable by anyone, until it is made private.
7
+ *
8
+ * Its words are the reader's language (@hanzo/i18n `t`); a project's name is
9
+ * drawn as given.
7
10
  */
8
11
  import { YStack } from '@hanzo/gui';
12
+ import { useI18n } from '@hanzo/i18n/react';
9
13
  import { Button, Switch } from '@hanzo/ui';
10
14
  import { useState } from 'react';
15
+ import { told } from '../api/call.js';
11
16
  import { consent, setConsent } from '../api/consent.js';
12
17
  import { change, projects } from '../api/projects.js';
13
18
  import { useRead } from '../data.js';
14
19
  import { useHost, useTarget } from '../host.js';
15
20
  import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
16
21
  export function Privacy() {
22
+ const { t } = useI18n();
17
23
  const host = useHost();
18
- const t = useTarget();
24
+ const target = useTarget();
19
25
  const signed = Boolean(host.person);
20
- const answers = useRead(signed ? () => consent(t) : null, null, [t, signed]);
21
- const published = useRead(signed ? () => projects(t) : null, [], [t, signed]);
26
+ const answers = useRead(signed ? () => consent(target) : null, null, [target, signed]);
27
+ const published = useRead(signed ? () => projects(target) : null, [], [target, signed]);
22
28
  const [shown, setShown] = useState(null);
23
29
  const [busy, setBusy] = useState('');
24
30
  const [note, setNote] = useState('');
25
31
  if (!signed) {
26
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Privacy" }), _jsx(Soft, { children: "Sign in to see your privacy settings." })] }));
32
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: t('Privacy') }), _jsx(Soft, { children: t('Sign in to see your privacy settings.') })] }));
27
33
  }
28
34
  const now = shown ?? answers.value;
29
35
  const answer = async (change) => {
30
36
  setBusy('consent');
31
37
  setNote('');
32
38
  try {
33
- setShown(await setConsent(t, change));
39
+ setShown(await setConsent(target, change));
34
40
  }
35
41
  catch (e) {
36
- setNote(e.message);
42
+ setNote(told(e, t));
37
43
  }
38
44
  finally {
39
45
  setBusy('');
@@ -43,21 +49,28 @@ export function Privacy() {
43
49
  setBusy(p.slug);
44
50
  setNote('');
45
51
  try {
46
- const saved = await change(t, p.slug, { visibility: p.visibility === 'private' ? 'public' : 'private' });
52
+ const saved = await change(target, p.slug, { visibility: p.visibility === 'private' ? 'public' : 'private' });
47
53
  published.reload();
48
- setNote(`${saved.name} is ${saved.visibility || 'saved'}`);
54
+ // The two visibilities this page offers are said in words; any other is the platform's, as given.
55
+ setNote(saved.visibility === 'public'
56
+ ? t('{name} is public', { name: saved.name })
57
+ : saved.visibility === 'private'
58
+ ? t('{name} is private', { name: saved.name })
59
+ : saved.visibility
60
+ ? t('{name} is {visibility}', { name: saved.name, visibility: saved.visibility })
61
+ : t('{name} is saved', { name: saved.name }));
49
62
  }
50
63
  catch (e) {
51
- setNote(e.message);
64
+ setNote(told(e, t));
52
65
  }
53
66
  finally {
54
67
  setBusy('');
55
68
  }
56
69
  };
57
70
  const training = now?.training ?? '';
58
- return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Privacy", detail: "What Hanzo may do with your data, and what your organization shows the world." }), _jsx(Group, { title: "Your data", detail: "Recorded on your Hanzo account and honoured by every Hanzo surface.", children: answers.error && !now ? (_jsx(Soft, { children: answers.error.message })) : !now ? (_jsx(Soft, { children: "Reading your answers\u2026" })) : (_jsxs(Card, { children: [_jsx(Row, { first: true, title: "Help improve Hanzo models", detail: training === 'granted'
59
- ? 'Hanzo may train on your prompts and runs.'
71
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: t('Privacy'), detail: t('What Hanzo may do with your data, and what your organization shows the world.') }), _jsx(Group, { title: t('Your data'), detail: t('Recorded on your Hanzo account and honoured by every Hanzo surface.'), children: answers.error && !now ? (_jsx(Soft, { children: told(answers.error, t) })) : !now ? (_jsx(Soft, { children: t('Reading your answers…') })) : (_jsxs(Card, { children: [_jsx(Row, { first: true, title: t('Help improve Hanzo models'), detail: training === 'granted'
72
+ ? t('Hanzo may train on your prompts and runs.')
60
73
  : training === 'refused'
61
- ? 'Hanzo does not train on your data.'
62
- : 'Not answered yet. Until you say yes, Hanzo does not train on your data.', trailing: _jsx(Switch, { "aria-label": "Help improve Hanzo models", checked: training === 'granted', disabled: busy === 'consent', onCheckedChange: (on) => void answer({ training: on ? 'granted' : 'refused' }) }) }), _jsx(Row, { title: "Usage insights", detail: "Anonymous counts of how the product is used. No prompts, code or answers.", trailing: _jsx(Switch, { "aria-label": "Usage insights", checked: now.insights, disabled: busy === 'consent', onCheckedChange: (on) => void answer({ insights: on }) }) })] })) }), _jsx(Group, { title: "Projects", detail: "A public project is listed in the community catalogue, and anyone can read its source. Keeping one private needs a paid plan.", children: published.error ? (_jsx(Soft, { children: published.error.message })) : published.loading && !published.value.length ? (_jsx(Soft, { children: "Reading projects\u2026" })) : !published.value.length ? (_jsx(Soft, { children: "Your organization has no projects yet." })) : (_jsx(Card, { children: published.value.map((p, i) => (_jsx(Row, { first: i === 0, title: p.name, detail: p.visibility === 'private' ? 'Private' : p.visibility === 'public' ? 'Public' : 'Visibility not reported', trailing: p.visibility ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy === p.slug, onPress: () => void flip(p), children: p.visibility === 'private' ? 'Make public' : 'Make private' })) : null }, p.slug))) })) }), _jsx(Note, { children: note })] }));
74
+ ? t('Hanzo does not train on your data.')
75
+ : t('Not answered yet. Until you say yes, Hanzo does not train on your data.'), trailing: _jsx(Switch, { "aria-label": t('Help improve Hanzo models'), checked: training === 'granted', disabled: busy === 'consent', onCheckedChange: (on) => void answer({ training: on ? 'granted' : 'refused' }) }) }), _jsx(Row, { title: t('Usage insights'), detail: t('Anonymous counts of how the product is used. No prompts, code or answers.'), trailing: _jsx(Switch, { "aria-label": t('Usage insights'), checked: now.insights, disabled: busy === 'consent', onCheckedChange: (on) => void answer({ insights: on }) }) })] })) }), _jsx(Group, { title: t('Projects'), detail: t('A public project is listed in the community catalogue, and anyone can read its source. Keeping one private needs a paid plan.'), children: published.error ? (_jsx(Soft, { children: told(published.error, t) })) : published.loading && !published.value.length ? (_jsx(Soft, { children: t('Reading projects…') })) : !published.value.length ? (_jsx(Soft, { children: t('Your organization has no projects yet.') })) : (_jsx(Card, { children: published.value.map((p, i) => (_jsx(Row, { first: i === 0, title: p.name, detail: p.visibility === 'private' ? t('Private') : p.visibility === 'public' ? t('Public') : t('Visibility not reported'), trailing: p.visibility ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy === p.slug, onPress: () => void flip(p), children: p.visibility === 'private' ? t('Make public') : t('Make private') })) : null }, p.slug))) })) }), _jsx(Note, { children: note })] }));
63
76
  }
@@ -37,10 +37,11 @@ export declare function Soft({ children }: {
37
37
  }): import("react").JSX.Element;
38
38
  /** `Sep 27, 2026` from an RFC 3339 instant, or '' when there is none. UTC, the calendar the platform bills and resets on. */
39
39
  export declare function day(iso: string): string;
40
- /** A credential the platform answers once: the value to copy, and what it is for. */
41
- export declare function Once({ value, label, children }: {
40
+ /** A credential the platform answers once: the value to copy, what it is, and what its copy control says. */
41
+ export declare function Once({ value, label, copy, children }: {
42
42
  value: string;
43
43
  label: string;
44
+ copy: string;
44
45
  children?: ReactNode;
45
46
  }): import("react").JSX.Element;
46
47
  /** What the last action did, said once, where it was done. */
@@ -3,8 +3,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  * What every Settings section is drawn from, so they read as one page: a
4
4
  * heading with its action, bordered lists of rows, labelled fields, and the
5
5
  * quiet lines for empty, loading and refused.
6
+ *
7
+ * What a section hands them comes already said in the reader's language
8
+ * (@hanzo/i18n `t`), since a row's title may be a name drawn as given; their
9
+ * own few words they say themselves.
6
10
  */
7
11
  import { SizableText, XStack, YStack } from '@hanzo/gui';
12
+ import { useI18n } from '@hanzo/i18n/react';
8
13
  import { CopyButton } from '@hanzo/ui/product';
9
14
  /** A section's title, what it is for, and its one action. */
10
15
  export function Heading({ title, detail, action }) {
@@ -35,9 +40,10 @@ export function day(iso) {
35
40
  const d = iso ? new Date(iso) : null;
36
41
  return d && !Number.isNaN(d.getTime()) ? d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }) : '';
37
42
  }
38
- /** A credential the platform answers once: the value to copy, and what it is for. */
39
- export function Once({ value, label, children }) {
40
- return (_jsxs(YStack, { gap: "$2", px: "$3", py: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$raised", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, minW: 0, size: "$2", color: "$ink", "aria-label": label, style: { fontFamily: 'var(--f-mono, ui-monospace, monospace)', wordBreak: 'break-all' }, children: value }), _jsx(CopyButton, { value: value, label: `Copy ${label.toLowerCase()}` })] }), _jsx(SizableText, { size: "$1", color: "$soft", children: "Copy it now. It is not shown again." }), children] }));
43
+ /** A credential the platform answers once: the value to copy, what it is, and what its copy control says. */
44
+ export function Once({ value, label, copy, children }) {
45
+ const { t } = useI18n();
46
+ return (_jsxs(YStack, { gap: "$2", px: "$3", py: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$raised", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, minW: 0, size: "$2", color: "$ink", "aria-label": label, style: { fontFamily: 'var(--f-mono, ui-monospace, monospace)', wordBreak: 'break-all' }, children: value }), _jsx(CopyButton, { value: value, label: copy })] }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Copy it now. It is not shown again.') }), children] }));
41
47
  }
42
48
  /** What the last action did, said once, where it was done. */
43
49
  export function Note({ children }) {
@@ -4,17 +4,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  * shares; the free allowance on Free; the balance with no plan. What happens
5
5
  * when a plan's allowance runs out is chosen here. Credits themselves — the
6
6
  * balance, top-ups, auto-reload, spend and its limit — are Billing's.
7
+ * Its words are the reader's language (@hanzo/i18n `t`); the org's name is its own.
7
8
  */
8
9
  import { YStack } from '@hanzo/gui';
10
+ import { useI18n } from '@hanzo/i18n/react';
11
+ import { told } from '../api/call.js';
9
12
  import { useHost, useTarget } from '../host.js';
10
13
  import { path } from '../route.js';
11
14
  import { useStanding } from '../standing.js';
12
15
  import { Reading } from './plan.js';
13
16
  import { Heading, Soft } from './ui.js';
14
17
  export function Usage() {
18
+ const { t } = useI18n();
15
19
  const host = useHost();
16
- const t = useTarget();
20
+ const target = useTarget();
17
21
  const signed = Boolean(host.person);
18
- const read = useStanding(t, signed);
19
- return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Usage", detail: `What ${host.org ?? 'this organization'}’s plan includes, and how much of it is used.` }), !signed ? (_jsx(Soft, { children: "Sign in to see this organization\u2019s usage." })) : read.value ? (_jsx(Reading, { s: read.value, target: t, onPlans: () => host.go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => host.go(path({ kind: 'settings', section: 'billing' })) })) : (_jsx(Soft, { children: read.error ? read.error.message : 'Reading the plan…' }))] }));
22
+ const read = useStanding(target, signed);
23
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: t('Usage'), detail: host.org ? t('What {org}’s plan includes, and how much of it is used.', { org: host.org }) : t('What this organization’s plan includes, and how much of it is used.') }), !signed ? (_jsx(Soft, { children: t('Sign in to see this organization’s usage.') })) : read.value ? (_jsx(Reading, { s: read.value, target: target, onPlans: () => host.go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => host.go(path({ kind: 'settings', section: 'billing' })) })) : (_jsx(Soft, { children: read.error ? told(read.error, t) : t('Reading the plan…') }))] }));
20
24
  }