@hanzo/rooms 0.1.0
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/LICENSE-APACHE +205 -0
- package/LICENSE-MIT +21 -0
- package/README.md +23 -0
- package/dist/Account.d.ts +8 -0
- package/dist/Account.js +58 -0
- package/dist/Beluga.d.ts +12 -0
- package/dist/Beluga.js +152 -0
- package/dist/Billing.d.ts +1 -0
- package/dist/Billing.js +31 -0
- package/dist/Board.d.ts +9 -0
- package/dist/Board.js +447 -0
- package/dist/Boxes.d.ts +1 -0
- package/dist/Boxes.js +173 -0
- package/dist/CalScheduler.d.ts +15 -0
- package/dist/CalScheduler.js +196 -0
- package/dist/Chat.d.ts +35 -0
- package/dist/Chat.js +1049 -0
- package/dist/Column.d.ts +15 -0
- package/dist/Column.js +88 -0
- package/dist/Contacts.d.ts +1 -0
- package/dist/Contacts.js +301 -0
- package/dist/Directory.d.ts +24 -0
- package/dist/Directory.js +356 -0
- package/dist/Drive.d.ts +2 -0
- package/dist/Drive.js +466 -0
- package/dist/Find.d.ts +7 -0
- package/dist/Find.js +82 -0
- package/dist/Guide.d.ts +6 -0
- package/dist/Guide.js +272 -0
- package/dist/Home.d.ts +39 -0
- package/dist/Home.js +306 -0
- package/dist/Inbox.d.ts +9 -0
- package/dist/Inbox.js +193 -0
- package/dist/Market.d.ts +3 -0
- package/dist/Market.js +149 -0
- package/dist/Me.d.ts +27 -0
- package/dist/Me.js +251 -0
- package/dist/Meet.d.ts +9 -0
- package/dist/Meet.js +370 -0
- package/dist/ModeMark.d.ts +8 -0
- package/dist/ModeMark.js +18 -0
- package/dist/OrgSwitcher.d.ts +31 -0
- package/dist/OrgSwitcher.js +120 -0
- package/dist/Orgs.d.ts +10 -0
- package/dist/Orgs.js +582 -0
- package/dist/Profile.d.ts +4 -0
- package/dist/Profile.js +67 -0
- package/dist/Prose.d.ts +5 -0
- package/dist/Prose.js +111 -0
- package/dist/ProviderMark.d.ts +17 -0
- package/dist/ProviderMark.js +277 -0
- package/dist/Recents.d.ts +10 -0
- package/dist/Recents.js +91 -0
- package/dist/RightPane.d.ts +13 -0
- package/dist/RightPane.js +426 -0
- package/dist/Room.d.ts +8 -0
- package/dist/Room.js +50 -0
- package/dist/Scene.d.ts +12 -0
- package/dist/Scene.js +105 -0
- package/dist/Settings.d.ts +46 -0
- package/dist/Settings.js +321 -0
- package/dist/Setup.d.ts +1 -0
- package/dist/Setup.js +36 -0
- package/dist/Share.d.ts +3 -0
- package/dist/Share.js +130 -0
- package/dist/Shared.d.ts +1 -0
- package/dist/Shared.js +113 -0
- package/dist/Shell.d.ts +63 -0
- package/dist/Shell.js +1225 -0
- package/dist/Start.d.ts +1 -0
- package/dist/Start.js +96 -0
- package/dist/Support.d.ts +14 -0
- package/dist/Support.js +122 -0
- package/dist/Talk.d.ts +4 -0
- package/dist/Talk.js +107 -0
- package/dist/Upgrade.d.ts +11 -0
- package/dist/Upgrade.js +75 -0
- package/dist/Usage.d.ts +1 -0
- package/dist/Usage.js +21 -0
- package/dist/Vibe.d.ts +2 -0
- package/dist/Vibe.js +188 -0
- package/dist/Work.d.ts +12 -0
- package/dist/Work.js +256 -0
- package/dist/bots/Bots.d.ts +2 -0
- package/dist/bots/Bots.js +456 -0
- package/dist/bots/Lights.d.ts +8 -0
- package/dist/bots/Lights.js +13 -0
- package/dist/bots/api.d.ts +120 -0
- package/dist/bots/api.js +75 -0
- package/dist/bots/face.d.ts +5 -0
- package/dist/bots/face.js +13 -0
- package/dist/bots/ink.d.ts +7 -0
- package/dist/bots/ink.js +29 -0
- package/dist/bots/kit.d.ts +113 -0
- package/dist/bots/kit.js +43 -0
- package/dist/bots/runtime.d.ts +4 -0
- package/dist/bots/runtime.js +326 -0
- package/dist/card.d.ts +52 -0
- package/dist/card.js +52 -0
- package/dist/cast.d.ts +143 -0
- package/dist/cast.js +251 -0
- package/dist/connect.d.ts +29 -0
- package/dist/connect.js +64 -0
- package/dist/contacts-store.d.ts +111 -0
- package/dist/contacts-store.js +276 -0
- package/dist/conversations.d.ts +54 -0
- package/dist/conversations.js +98 -0
- package/dist/copy.d.ts +23 -0
- package/dist/copy.js +39 -0
- package/dist/crew.d.ts +36 -0
- package/dist/crew.js +304 -0
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +102 -0
- package/dist/enso.d.ts +70 -0
- package/dist/enso.js +112 -0
- package/dist/failure.d.ts +57 -0
- package/dist/failure.js +143 -0
- package/dist/ground.d.ts +32 -0
- package/dist/ground.js +32 -0
- package/dist/host.d.ts +76 -0
- package/dist/host.js +76 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +12 -0
- package/dist/kept.d.ts +5 -0
- package/dist/kept.js +27 -0
- package/dist/lib/account.d.ts +49 -0
- package/dist/lib/account.js +229 -0
- package/dist/lib/ai.d.ts +46 -0
- package/dist/lib/ai.js +140 -0
- package/dist/lib/api.d.ts +43 -0
- package/dist/lib/api.js +62 -0
- package/dist/lib/bare.d.ts +9 -0
- package/dist/lib/bare.js +7 -0
- package/dist/lib/coding.d.ts +105 -0
- package/dist/lib/coding.js +87 -0
- package/dist/lib/destination.d.ts +102 -0
- package/dist/lib/destination.js +260 -0
- package/dist/lib/durable.d.ts +66 -0
- package/dist/lib/durable.js +107 -0
- package/dist/lib/first.d.ts +11 -0
- package/dist/lib/first.js +23 -0
- package/dist/lib/home.d.ts +61 -0
- package/dist/lib/home.js +54 -0
- package/dist/lib/host.d.ts +104 -0
- package/dist/lib/host.js +132 -0
- package/dist/lib/hostname.d.ts +1 -0
- package/dist/lib/hostname.js +14 -0
- package/dist/lib/hydrated.d.ts +1 -0
- package/dist/lib/hydrated.js +20 -0
- package/dist/lib/integrations.d.ts +21 -0
- package/dist/lib/integrations.js +1 -0
- package/dist/lib/intent.d.ts +6 -0
- package/dist/lib/intent.js +71 -0
- package/dist/lib/invite.d.ts +155 -0
- package/dist/lib/invite.js +245 -0
- package/dist/lib/limits.d.ts +55 -0
- package/dist/lib/limits.js +119 -0
- package/dist/lib/list.d.ts +9 -0
- package/dist/lib/list.js +11 -0
- package/dist/lib/mark.d.ts +13 -0
- package/dist/lib/mark.js +85 -0
- package/dist/lib/mix.d.ts +22 -0
- package/dist/lib/mix.js +22 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +106 -0
- package/dist/lib/org.d.ts +109 -0
- package/dist/lib/org.js +140 -0
- package/dist/lib/path.d.ts +9 -0
- package/dist/lib/path.js +29 -0
- package/dist/lib/plans.d.ts +193 -0
- package/dist/lib/plans.js +310 -0
- package/dist/lib/prose.d.ts +13 -0
- package/dist/lib/prose.js +62 -0
- package/dist/lib/reach.d.ts +17 -0
- package/dist/lib/reach.js +70 -0
- package/dist/lib/rooms.json +23 -0
- package/dist/lib/served.d.ts +20 -0
- package/dist/lib/served.js +92 -0
- package/dist/lib/session.d.ts +211 -0
- package/dist/lib/session.js +493 -0
- package/dist/lib/share.d.ts +131 -0
- package/dist/lib/share.js +216 -0
- package/dist/lib/signout.d.ts +14 -0
- package/dist/lib/signout.js +40 -0
- package/dist/lib/tags.d.ts +27 -0
- package/dist/lib/tags.js +46 -0
- package/dist/lib/tap.d.ts +17 -0
- package/dist/lib/tap.js +9 -0
- package/dist/lib/tier.d.ts +115 -0
- package/dist/lib/tier.js +207 -0
- package/dist/lib/tiers.d.ts +9 -0
- package/dist/lib/tiers.js +141 -0
- package/dist/lib/todo.d.ts +125 -0
- package/dist/lib/todo.js +379 -0
- package/dist/look.d.ts +1 -0
- package/dist/look.js +21 -0
- package/dist/meters.d.ts +8 -0
- package/dist/meters.js +31 -0
- package/dist/model.d.ts +4 -0
- package/dist/model.js +44 -0
- package/dist/network.d.ts +64 -0
- package/dist/network.js +216 -0
- package/dist/open.d.ts +81 -0
- package/dist/open.js +149 -0
- package/dist/pane.d.ts +92 -0
- package/dist/pane.js +231 -0
- package/dist/progress.d.ts +46 -0
- package/dist/progress.js +51 -0
- package/dist/recent.d.ts +67 -0
- package/dist/recent.js +161 -0
- package/dist/rooms.d.ts +131 -0
- package/dist/rooms.js +195 -0
- package/dist/roster.d.ts +28 -0
- package/dist/roster.js +70 -0
- package/dist/span.d.ts +14 -0
- package/dist/span.js +50 -0
- package/dist/speech.d.ts +44 -0
- package/dist/speech.js +229 -0
- package/dist/stars.d.ts +16 -0
- package/dist/stars.js +75 -0
- package/dist/tabs.d.ts +26 -0
- package/dist/tabs.js +25 -0
- package/dist/team.d.ts +11 -0
- package/dist/team.gen.d.ts +10 -0
- package/dist/team.gen.js +11 -0
- package/dist/team.js +30 -0
- package/dist/tools.d.ts +35 -0
- package/dist/tools.js +143 -0
- package/dist/tours.d.ts +24 -0
- package/dist/tours.js +311 -0
- package/dist/waiting.d.ts +15 -0
- package/dist/waiting.js +17 -0
- package/dist/where.d.ts +54 -0
- package/dist/where.js +14 -0
- package/package.json +108 -0
package/dist/Start.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Start(): import("react").JSX.Element;
|
package/dist/Start.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Starting a run from here — the composer /dev's empty state promised and did
|
|
4
|
+
// not have.
|
|
5
|
+
//
|
|
6
|
+
// The pane already said "Describe it. Ship it." over "in your terminal, your
|
|
7
|
+
// editor, or right here", and `right here` was the one of the three with nothing
|
|
8
|
+
// behind it: the idle state drew install cards and no way to send. The comment
|
|
9
|
+
// that stood in for it said "there is no create-a-run route to offer: a run
|
|
10
|
+
// begins on a machine". THAT IS NO LONGER TRUE — `POST /v1/agents/coding` starts
|
|
11
|
+
// one and answers the session to watch it in, which is the row the working view
|
|
12
|
+
// already renders.
|
|
13
|
+
//
|
|
14
|
+
// WHERE IT RUNS IS THE ONE CHOICE, and it is a field rather than a fork:
|
|
15
|
+
//
|
|
16
|
+
// no target the cloud's own sandbox — what a browser gets, because a
|
|
17
|
+
// browser has no machine to offer
|
|
18
|
+
// a target a machine this org has claimed, routed by id
|
|
19
|
+
//
|
|
20
|
+
// Nothing here reads the user agent to decide. The web sends no target because
|
|
21
|
+
// it HAS none; a surface running on a claimed machine sends that machine's id.
|
|
22
|
+
// One endpoint, one field, and the answer comes back rather than being assumed —
|
|
23
|
+
// `routed` says whether a machine actually took it, so asking for one and being
|
|
24
|
+
// given a sandbox is reported instead of drawn as success.
|
|
25
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
26
|
+
import { Text, XStack, YStack } from '@hanzo/ui';
|
|
27
|
+
import { Composer } from '@hanzo/ui/chat';
|
|
28
|
+
import { useOpen } from './open.js';
|
|
29
|
+
import { CodingRefusal, dispatchable, listTargets, startCoding, } from './lib/coding.js';
|
|
30
|
+
import { BAD } from './lib/mix.js';
|
|
31
|
+
/** A field of the start row: 32px, outlined, in the row's own ink. */
|
|
32
|
+
const FIELD = {
|
|
33
|
+
height: 32,
|
|
34
|
+
rounded: 8,
|
|
35
|
+
borderWidth: 1,
|
|
36
|
+
borderColor: 'var(--white-10)',
|
|
37
|
+
bg: 'transparent',
|
|
38
|
+
color: 'inherit',
|
|
39
|
+
fontSize: '$2',
|
|
40
|
+
};
|
|
41
|
+
const SOFT = '$soft';
|
|
42
|
+
/** The sandbox is a destination like any other, and it is the default one. */
|
|
43
|
+
const SANDBOX = '';
|
|
44
|
+
export function Start() {
|
|
45
|
+
const { openSession } = useOpen();
|
|
46
|
+
const [prompt, setPrompt] = useState('');
|
|
47
|
+
const [repo, setRepo] = useState('');
|
|
48
|
+
const [where, setWhere] = useState(SANDBOX);
|
|
49
|
+
const [targets, setTargets] = useState([]);
|
|
50
|
+
const [busy, setBusy] = useState(false);
|
|
51
|
+
const [tool, setTool] = useState('dev');
|
|
52
|
+
const [err, setErr] = useState(null);
|
|
53
|
+
// The machines this org has claimed. An empty list is an ANSWER — every run
|
|
54
|
+
// goes to a sandbox — so it is not an error and draws no warning. Only a
|
|
55
|
+
// refusal is reported, and even then the composer stays usable: a sandbox run
|
|
56
|
+
// needs no target, so being unable to list machines must not stop a send.
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
let live = true;
|
|
59
|
+
listTargets()
|
|
60
|
+
.then((t) => live && setTargets(t.filter(dispatchable)))
|
|
61
|
+
.catch(() => {
|
|
62
|
+
/* Listing is an enhancement here; the sandbox needs nothing from it. */
|
|
63
|
+
});
|
|
64
|
+
return () => {
|
|
65
|
+
live = false;
|
|
66
|
+
};
|
|
67
|
+
}, []);
|
|
68
|
+
const send = useCallback(async () => {
|
|
69
|
+
const task = prompt.trim();
|
|
70
|
+
if (!task || busy)
|
|
71
|
+
return;
|
|
72
|
+
setBusy(true);
|
|
73
|
+
setErr(null);
|
|
74
|
+
try {
|
|
75
|
+
const run = await startCoding({
|
|
76
|
+
prompt: task,
|
|
77
|
+
repo: repo.trim() || undefined,
|
|
78
|
+
tool: tool || 'dev',
|
|
79
|
+
targetId: where || undefined,
|
|
80
|
+
});
|
|
81
|
+
setPrompt('');
|
|
82
|
+
// The run is ACCEPTED, not finished. Opening its session is what puts the
|
|
83
|
+
// reader where it narrates itself, which is the view this pane replaces.
|
|
84
|
+
openSession(run.sessionId);
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
setErr(e instanceof CodingRefusal ? e.message : 'Could not start the run');
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
setBusy(false);
|
|
91
|
+
}
|
|
92
|
+
}, [prompt, repo, tool, where, busy, openSession]);
|
|
93
|
+
return (_jsxs(YStack, { width: "100%", gap: "$2", children: [_jsx(Composer, { value: prompt, onChange: setPrompt, onSend: () => void send(), busy: busy, placeholder: "Describe what to build\u2026", label: "Describe what to build" }), _jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsxs(Text, { render: _jsx("select", { value: tool, onChange: (e) => setTool(e.currentTarget.value), "aria-label": "Agent Harness" }), ...FIELD, px: 8, children: [_jsx("option", { value: "dev", children: "Dev" }), _jsx("option", { value: "claude", children: "Claude" }), _jsx("option", { value: "codex", children: "Codex" }), _jsx("option", { value: "python", children: "Python" })] }), _jsx(Text, { render: _jsx("input", { value: repo, onChange: (e) => setRepo(e.currentTarget.value), placeholder: "owner/repo (optional)", "aria-label": "Repository" }), ...FIELD, flex: 1, minW: 160, px: 10, outlineStyle: "none" }), targets.length > 0 ? (_jsxs(Text, { render: _jsx("select", { value: where, onChange: (e) => setWhere(e.currentTarget.value), "aria-label": "Where to run" }), ...FIELD, px: 8, children: [_jsx("option", { value: SANDBOX, children: "Sandbox" }), targets.map((t) => (_jsxs("option", { value: t.id, children: [t.label || t.host || t.id, t.capacity ? ` — ${t.capacity}` : ''] }, t.id)))] })) : null] }), err ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: err })) : (_jsx(Text, { fontSize: "$2", color: SOFT, children: targets.length > 0 && where !== SANDBOX
|
|
94
|
+
? 'Runs on your machine.'
|
|
95
|
+
: 'Runs in an isolated sandbox with Free AI / Enso.' }))] }));
|
|
96
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The strip across the top of every page while a SuperAdmin is inside an
|
|
3
|
+
* organization that is not theirs: which one, as whom, and the way out. It reads
|
|
4
|
+
* the token's `assumed` claim, so it is there exactly as long as IAM's re-scoped
|
|
5
|
+
* token is.
|
|
6
|
+
*/
|
|
7
|
+
export declare function SupportBanner(): import("react").JSX.Element;
|
|
8
|
+
/**
|
|
9
|
+
* Every organization, for a SuperAdmin to step into. The list and the search are
|
|
10
|
+
* IAM's, a page at a time; picking one asks IAM to step in.
|
|
11
|
+
*/
|
|
12
|
+
export declare function SupportPicker({ onClose }: {
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
}): import("react").JSX.Element;
|
package/dist/Support.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Support mode: a SuperAdmin steps into any organization to see what it sees.
|
|
4
|
+
//
|
|
5
|
+
// Everything here is IAM's. The list is IAM's organization list, which answers a
|
|
6
|
+
// SuperAdmin every organization and anyone else only their own; the step in and
|
|
7
|
+
// the step out are IAM's `assume` and `release`, which answer the operator's own
|
|
8
|
+
// token re-scoped and record each step in the organization's audit log
|
|
9
|
+
// (lib/auth/session.ts `support`). This file only draws them, and only for the
|
|
10
|
+
// person IAM signs as a SuperAdmin (`superAdmin`); nothing here decides who may.
|
|
11
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
12
|
+
import { Text, View, XStack, YStack } from '@hanzo/gui';
|
|
13
|
+
import { Button } from '@hanzo/ui';
|
|
14
|
+
import { X } from 'lucide-react';
|
|
15
|
+
import { useHydrated } from './lib/hydrated.js';
|
|
16
|
+
import { api } from './lib/api.js';
|
|
17
|
+
import { abandon, assumed, bearer, named, support } from './lib/session.js';
|
|
18
|
+
import { BAD, mix } from './lib/mix.js';
|
|
19
|
+
/** The banner rides above the room's sheets, the picker between them. gui carries a `calc()` to the page unchanged;
|
|
20
|
+
* its types only name custom properties. */
|
|
21
|
+
const BANNER = 'calc(var(--z-modal) + 30)';
|
|
22
|
+
const DIALOG = 'calc(var(--z-modal) + 20)';
|
|
23
|
+
const SCRIM = mix('var(--pure-black)', 28, 'srgb');
|
|
24
|
+
const muted = 'var(--muted-foreground)';
|
|
25
|
+
/** A page of IAM's organization list: `q` narrows it, `cursor` continues it. */
|
|
26
|
+
async function page(q, cursor) {
|
|
27
|
+
const query = new URLSearchParams({ limit: '20' });
|
|
28
|
+
if (q.trim())
|
|
29
|
+
query.set('q', q.trim());
|
|
30
|
+
if (cursor)
|
|
31
|
+
query.set('cursor', cursor);
|
|
32
|
+
const res = await fetch(`${api()}/v1/iam/organizations?${query}`, {
|
|
33
|
+
headers: { Authorization: `Bearer ${bearer() ?? ''}` },
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok)
|
|
36
|
+
throw new Error(`IAM answered ${res.status}.`);
|
|
37
|
+
return (await res.json());
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The strip across the top of every page while a SuperAdmin is inside an
|
|
41
|
+
* organization that is not theirs: which one, as whom, and the way out. It reads
|
|
42
|
+
* the token's `assumed` claim, so it is there exactly as long as IAM's re-scoped
|
|
43
|
+
* token is.
|
|
44
|
+
*/
|
|
45
|
+
export function SupportBanner() {
|
|
46
|
+
const hydrated = useHydrated();
|
|
47
|
+
const [org, setOrg] = useState(null);
|
|
48
|
+
const [who, setWho] = useState('');
|
|
49
|
+
const [busy, setBusy] = useState(false);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!hydrated)
|
|
52
|
+
return;
|
|
53
|
+
setOrg(assumed());
|
|
54
|
+
setWho(named()?.email || named()?.name || '');
|
|
55
|
+
}, [hydrated]);
|
|
56
|
+
if (!org)
|
|
57
|
+
return null;
|
|
58
|
+
// Leaving asks IAM to release. When IAM cannot answer, the way out is the
|
|
59
|
+
// person's own session: the SDK's next refresh assumes nothing.
|
|
60
|
+
const leave = () => {
|
|
61
|
+
setBusy(true);
|
|
62
|
+
support(null).catch(() => abandon());
|
|
63
|
+
};
|
|
64
|
+
return (_jsxs(XStack, { role: "status", "aria-label": "Support mode", position: "fixed", t: 52, l: "50%", x: "-50%", maxW: "calc(100vw - 32px)", z: BANNER, items: "center", justify: "center", gap: 12, flexWrap: "wrap", pt: 6, pr: 8, pb: 6, pl: 16, rounded: 999, bg: "var(--foreground)", children: [_jsxs(Text, { fontSize: "$2", color: "var(--background)", children: ["Support mode: you are in ", _jsx("strong", { children: org }), who ? ` as ${who}` : '', ". IAM records it under your name."] }), _jsx(Button, { size: "sm", onClick: leave, disabled: busy, children: busy ? 'Leaving' : 'Leave' })] }));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Every organization, for a SuperAdmin to step into. The list and the search are
|
|
68
|
+
* IAM's, a page at a time; picking one asks IAM to step in.
|
|
69
|
+
*/
|
|
70
|
+
export function SupportPicker({ onClose }) {
|
|
71
|
+
const [q, setQ] = useState('');
|
|
72
|
+
const [rows, setRows] = useState([]);
|
|
73
|
+
const [cursor, setCursor] = useState('');
|
|
74
|
+
const [loading, setLoading] = useState(false);
|
|
75
|
+
const [refused, setRefused] = useState(null);
|
|
76
|
+
const [stepping, setStepping] = useState(null);
|
|
77
|
+
// Only the newest read lands: a search typed while a page is in flight
|
|
78
|
+
// supersedes it, so a slow answer to an old query never overwrites the new one.
|
|
79
|
+
const asked = useRef(0);
|
|
80
|
+
const load = useCallback(async (query, from) => {
|
|
81
|
+
const mine = ++asked.current;
|
|
82
|
+
setLoading(true);
|
|
83
|
+
setRefused(null);
|
|
84
|
+
try {
|
|
85
|
+
const next = await page(query, from);
|
|
86
|
+
if (mine !== asked.current)
|
|
87
|
+
return;
|
|
88
|
+
setRows((had) => {
|
|
89
|
+
// IAM's cursor is a position, so an org created mid-walk can shift a row
|
|
90
|
+
// across a page edge; each org is kept once.
|
|
91
|
+
const all = from ? [...had, ...(next.organizations ?? [])] : next.organizations ?? [];
|
|
92
|
+
return all.filter((o, i) => all.findIndex((x) => x.name === o.name) === i);
|
|
93
|
+
});
|
|
94
|
+
setCursor(next.cursor ?? '');
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
if (mine === asked.current)
|
|
98
|
+
setRefused(e instanceof Error ? e.message : 'The list did not load.');
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
if (mine === asked.current)
|
|
102
|
+
setLoading(false);
|
|
103
|
+
}
|
|
104
|
+
}, []);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const id = setTimeout(() => void load(q, ''), 250);
|
|
107
|
+
return () => clearTimeout(id);
|
|
108
|
+
}, [q, load]);
|
|
109
|
+
const enter = (org) => {
|
|
110
|
+
setStepping(org);
|
|
111
|
+
setRefused(null);
|
|
112
|
+
support(org).catch((e) => {
|
|
113
|
+
setRefused(e instanceof Error ? e.message : 'IAM did not step in.');
|
|
114
|
+
setStepping(null);
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
return (_jsx(YStack, { role: "dialog", "aria-modal": true, "aria-label": "All organizations", onKeyDown: (e) => {
|
|
118
|
+
// gui types the native event; on the web it is the DOM's, which carries the key.
|
|
119
|
+
if ('key' in e && e.key === 'Escape')
|
|
120
|
+
onClose();
|
|
121
|
+
}, position: "fixed", inset: 0, z: DIALOG, bg: SCRIM, overflowY: "auto", py: "6vh", px: 16, onClick: onClose, children: _jsxs(YStack, { onClick: (e) => e.stopPropagation(), width: "100%", maxW: 560, mx: "auto", bg: "var(--background)", rounded: 16, pt: 24, px: 24, pb: 16, children: [_jsxs(XStack, { items: "flex-start", justify: "space-between", gap: 12, children: [_jsxs(YStack, { shrink: 1, minW: "auto", children: [_jsx(Text, { render: "h2", fontSize: "$7", fontWeight: "500", color: "var(--text-primary)", children: "All organizations" }), _jsx(Text, { render: "p", mt: 6, fontSize: "$3", color: muted, lineHeight: 21, children: "Support mode. Step into any organization to see its chats, agents and billing as it does. IAM records every step under your name." })] }), _jsx(Button, { variant: "ghost", size: "icon-sm", mr: -8, onClick: onClose, "aria-label": "Close", children: _jsx(X, { size: 16, "aria-hidden": true }) })] }), _jsx(Text, { render: _jsx("input", { value: q, onChange: (e) => setQ(e.target.value), placeholder: "Find an organization", "aria-label": "Find an organization", autoFocus: true }), width: "100%", mt: 16, fontSize: "$3", px: 12, py: 10, rounded: 10, borderWidth: 1, borderColor: "var(--border)", bg: "var(--background)", color: "var(--foreground)" }), _jsxs(View, { role: "list", "aria-label": "Organizations", mt: 10, display: "grid", gap: 2, children: [rows.map((o) => (_jsxs(XStack, { render: "button", role: "listitem", onClick: () => enter(o.name), disabled: stepping !== null, items: "baseline", justify: "space-between", gap: 12, width: "100%", px: 12, py: 10, rounded: 10, borderWidth: 0, bg: "transparent", cursor: "pointer", children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "var(--foreground)", text: "left", children: o.displayName || o.name }), _jsx(Text, { fontSize: "$2", color: muted, text: "left", children: stepping === o.name ? 'Stepping in' : o.name })] }, o.name))), !loading && rows.length === 0 && !refused ? (_jsx(Text, { render: "p", mx: 12, my: 8, fontSize: "$3", color: muted, children: "No organization matches." })) : null] }), refused ? (_jsx(Text, { render: "p", mx: 12, my: 8, fontSize: "$3", color: BAD, children: refused })) : null, cursor ? (_jsx(Button, { self: "flex-start", mt: 8, onClick: () => void load(q, cursor), disabled: loading, children: loading ? 'Loading' : 'More' })) : null] }) }));
|
|
122
|
+
}
|
package/dist/Talk.d.ts
ADDED
package/dist/Talk.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// A room, read and answered.
|
|
4
|
+
//
|
|
5
|
+
// The org's rooms live on /home: the rail lists them, this pane reads one, and
|
|
6
|
+
// the composer answers in it. It draws the same three parts every other room on
|
|
7
|
+
// this site draws — the header, the transcript, the one composer — from the
|
|
8
|
+
// same components, so a room is not a second design.
|
|
9
|
+
//
|
|
10
|
+
// WHO SAID IT is the roster's answer (`roster.ts`). A message carries the
|
|
11
|
+
// account that wrote it, and the name and face beside each row are looked up
|
|
12
|
+
// there, once, for people and agents alike; an account the roster lacks is
|
|
13
|
+
// printed as the id it is. The transcript is polled by `rooms.ts`, so an answer
|
|
14
|
+
// — a teammate's or an agent's — lands without a reload.
|
|
15
|
+
//
|
|
16
|
+
// WHAT YOU SAY IS DRAWN AT ONCE, in the quiet ink and without a stamp, and it
|
|
17
|
+
// stays only if the platform keeps it: the accepted row replaces it, a refusal
|
|
18
|
+
// drops it and hands the words back to the field. Nothing the platform did not
|
|
19
|
+
// accept is on screen for longer than one round trip.
|
|
20
|
+
import { useRef, useState } from 'react';
|
|
21
|
+
import { ChannelHeader } from '@hanzo/ui/agents';
|
|
22
|
+
import { Composer } from '@hanzo/ui/chat';
|
|
23
|
+
import { Box, Text, XStack, YStack } from '@hanzo/ui';
|
|
24
|
+
import { Users } from 'lucide-react';
|
|
25
|
+
import { useIam } from '@hanzo/iam/react';
|
|
26
|
+
import { Face } from './cast.js';
|
|
27
|
+
import { SpeakButton } from './speech.js';
|
|
28
|
+
import { say } from './failure.js';
|
|
29
|
+
import { useOpen } from './open.js';
|
|
30
|
+
import { Beside } from './Shell.js';
|
|
31
|
+
import { memberOf, useMe, useRoster } from './roster.js';
|
|
32
|
+
import { useRoomMessages } from './rooms.js';
|
|
33
|
+
import { BAD } from './lib/mix.js';
|
|
34
|
+
/** The clock a transcript reads by: the time for today, the date before that. */
|
|
35
|
+
function when(ms) {
|
|
36
|
+
const then = new Date(ms);
|
|
37
|
+
const sameDay = new Date().toDateString() === then.toDateString();
|
|
38
|
+
return then.toLocaleString(undefined, sameDay ? { hour: 'numeric', minute: '2-digit' } : { month: 'short', day: 'numeric' });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* WHAT A ROOM IS CALLED. A channel carries a name; a direct message does not —
|
|
42
|
+
* its members are its name — so one falls back to the other rather than drawing
|
|
43
|
+
* an empty header. The sigil is how a client DRAWS a room and is not stored, so
|
|
44
|
+
* it is added here and only where there is a name to put it on.
|
|
45
|
+
*/
|
|
46
|
+
const titleOf = (room) => room.name ? `#${room.name}` : room.direct ? 'Direct message' : 'Room';
|
|
47
|
+
/** Adds `@name ` to a draft, after a space where the draft needs one. */
|
|
48
|
+
const mention = (draft, name) => `${draft && !/\s$/.test(draft) ? `${draft} ` : draft}@${name} `;
|
|
49
|
+
export function Talk({ room }) {
|
|
50
|
+
const { messages, failed, send } = useRoomMessages(room.id, room.space);
|
|
51
|
+
const { members, wrong } = useRoster();
|
|
52
|
+
const me = useMe();
|
|
53
|
+
const { aside, showAside } = useOpen();
|
|
54
|
+
const [draft, setDraft] = useState('');
|
|
55
|
+
const [pending, setPending] = useState([]);
|
|
56
|
+
const [refused, setRefused] = useState(null);
|
|
57
|
+
const minted = useRef(0);
|
|
58
|
+
const agents = (members ?? []).filter((one) => one.agent);
|
|
59
|
+
const mine = me ? memberOf(members, me) : undefined;
|
|
60
|
+
const speak = async () => {
|
|
61
|
+
const text = draft.trim();
|
|
62
|
+
if (!text)
|
|
63
|
+
return;
|
|
64
|
+
const key = ++minted.current;
|
|
65
|
+
setDraft('');
|
|
66
|
+
setRefused(null);
|
|
67
|
+
setPending((held) => [...held, { key, text }]);
|
|
68
|
+
try {
|
|
69
|
+
await send(text);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
setDraft(text);
|
|
73
|
+
setRefused(say(e, 'this room', 'save'));
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
setPending((held) => held.filter((one) => one.key !== key));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const name = (of) => setDraft((held) => mention(held, of));
|
|
80
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(ChannelHeader, { name: titleOf(room), children: [room.topic ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: room.topic })) : null, _jsx(Box, { render: "button", "aria-label": aside === 'roster' ? 'Hide directory & roster' : 'Show directory & roster', "aria-pressed": aside === 'roster', onClick: () => showAside(aside === 'roster' ? null : 'roster'), ml: "auto", p: "$2", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(Users, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", p: "$4", gap: "$3", children: [failed ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: say(failed, 'this room') })) : messages === null ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Reading the room." })) : messages.length === 0 && pending.length === 0 ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Nothing has been said here yet." })) : (messages.map((one) => (_jsx(Line, { member: memberOf(members, one.author), fallback: one.author, stamp: when(one.createdOn), text: one.text }, one.id)))), pending
|
|
81
|
+
.filter((one) => !messages?.some((said) => said.author === me && said.text === one.text))
|
|
82
|
+
.map((one) => (_jsx(Line, { member: mine, fallback: "You", text: one.text, soft: true }, `pending-${one.key}`)))] }), _jsxs(YStack, { p: "$3", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Composer, { value: draft, onChange: setDraft, onSend: () => void speak(), placeholder: `Message ${titleOf(room)}`, label: `Message ${titleOf(room)}`, hint: agents.length ? 'Mention an agent with @name and it answers.' : undefined, children: agents.length ? (_jsx(XStack, { gap: "$1", flexWrap: "wrap", shrink: 1, minW: 0, children: agents.map((one) => (_jsx(Box, { render: "button", "aria-label": `Mention ${one.name}`, onClick: () => name(one.name), px: "$2", py: "$1", rounded: "$10", hoverStyle: { bg: '$hover' }, children: _jsxs(Text, { fontSize: "$2", color: "$soft", children: ["@", one.name] }) }, one.id))) })) : null }), refused ? (_jsx(Text, { fontSize: "$2", color: BAD, children: refused })) : null] }), _jsx(Beside, { children: aside === 'roster' ? _jsx(Roster, { members: members, wrong: wrong, onMention: name }) : null })] }));
|
|
83
|
+
}
|
|
84
|
+
/** One thing said: a face, a name, the stamp where the platform gave one. */
|
|
85
|
+
function Line({ member, fallback, stamp, text, soft, }) {
|
|
86
|
+
const ink = soft ? '$soft' : '$ink';
|
|
87
|
+
return (_jsxs(XStack, { gap: "$3", items: "flex-start", children: [_jsx(Face, { src: member?.avatar, name: member?.name ?? fallback, size: 32, person: true }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsxs(XStack, { gap: "$2", items: "baseline", children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: ink, numberOfLines: 1, children: member?.name ?? fallback }), member?.agent ? (_jsx(Box, { px: "$1.5", py: "$0.5", rounded: "$2", bg: "$hover", children: _jsx(Text, { fontSize: "$1", color: "$soft", fontWeight: "600", children: "Agent" }) })) : null, stamp ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: stamp })) : null] }), _jsx(Text, { fontSize: "$3", color: ink, children: text }), _jsx(XStack, { mt: "$1", children: _jsx(SpeakButton, { text: text, agentName: member?.name ?? fallback, compact: true }) })] })] }));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Who is here, beside the room. An agent is pressable — pressing it names it in
|
|
91
|
+
* the draft, which is how it is asked something — and a person is a person:
|
|
92
|
+
* IAM publishes no route that messages one, so the row identifies rather than
|
|
93
|
+
* opens.
|
|
94
|
+
*/
|
|
95
|
+
function Roster({ members, wrong, onMention, }) {
|
|
96
|
+
const { isAuthenticated } = useIam();
|
|
97
|
+
const line = !isAuthenticated
|
|
98
|
+
? 'Sign in to see who is here.'
|
|
99
|
+
: wrong
|
|
100
|
+
? say(wrong, "this org's roster")
|
|
101
|
+
: members === null
|
|
102
|
+
? 'Reading the roster.'
|
|
103
|
+
: members.length < 2
|
|
104
|
+
? 'Just you.'
|
|
105
|
+
: null;
|
|
106
|
+
return (_jsxs(YStack, { children: [_jsx(XStack, { height: 48, shrink: 0, items: "center", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: _jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Roster" }) }), line ? (_jsx(Text, { p: "$3", fontSize: "$2", color: "$soft", children: line })) : (_jsx(YStack, { p: "$2", gap: "$0.5", children: (members ?? []).map((one) => (_jsx(Box, { render: one.agent ? 'button' : 'div', onClick: one.agent ? () => onMention(one.name) : undefined, "aria-label": one.agent ? `Mention ${one.name}` : undefined, width: "100%", minW: 0, items: "flex-start", px: "$2", py: "$1.5", rounded: "$3", hoverStyle: one.agent ? { bg: '$hover' } : undefined, children: _jsxs(XStack, { items: "center", gap: "$2", width: "100%", children: [_jsx(Face, { src: one.avatar, name: one.name, size: 24, person: true }), _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, flex: 1, minW: 0, text: "left", children: one.name }), one.agent ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "agent" })) : null] }) }, one.id))) }))] }));
|
|
107
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** What the reader reached for: a model by its name, and the checkout a refusal named. */
|
|
2
|
+
export interface Ask {
|
|
3
|
+
model?: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
}
|
|
6
|
+
/** Whether a signed-in reader's organization is on the Free plan. A tier not yet read, or refused, is not Free. */
|
|
7
|
+
export declare function useFree(signedIn: boolean): boolean;
|
|
8
|
+
export declare function Upgrade({ ask, onClose }: {
|
|
9
|
+
ask: Ask | null;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
}): import("react").JSX.Element;
|
package/dist/Upgrade.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// The ask to upgrade, for a reader on the Free plan.
|
|
4
|
+
//
|
|
5
|
+
// A Free reader who reaches for something Free does not include is shown the
|
|
6
|
+
// first personal plan that charges (`usePlan('personal')`), priced by the
|
|
7
|
+
// catalog for a month or for a year, with checkout at hanzo.ai/pay. `useFree`
|
|
8
|
+
// says who is on Free; `Upgrade` draws the ask it is handed and nothing when it
|
|
9
|
+
// is handed none. A refusal's ask is read by `planRequired` in `failure.ts`.
|
|
10
|
+
import { useEffect, useState } from 'react';
|
|
11
|
+
import { Blocks, Brain, Gauge, ListChecks, Sparkles, X } from 'lucide-react';
|
|
12
|
+
import { Anchor, Box, Dialog, DialogContent, DialogTitle, Text, ToggleGroup, ToggleGroupItem, XStack, YStack } from '@hanzo/ui';
|
|
13
|
+
import { track } from './lib/tags.js';
|
|
14
|
+
import { org } from './lib/session.js';
|
|
15
|
+
import { useTier } from './lib/tier.js';
|
|
16
|
+
import { charge, planCheckoutUrl, quote, saving, term, usePlan } from './lib/plans.js';
|
|
17
|
+
import { site } from './where.js';
|
|
18
|
+
/** The free rung's name in the billing catalog. */
|
|
19
|
+
const FREE = 'free';
|
|
20
|
+
/** Whether a signed-in reader's organization is on the Free plan. A tier not yet read, or refused, is not Free. */
|
|
21
|
+
export function useFree(signedIn) {
|
|
22
|
+
const { tier } = useTier(signedIn, org());
|
|
23
|
+
return signedIn && tier?.tier?.name === FREE;
|
|
24
|
+
}
|
|
25
|
+
const ROWS = [
|
|
26
|
+
[Gauge, 'Higher usage limits'],
|
|
27
|
+
[Sparkles, 'Access to all Hanzo models'],
|
|
28
|
+
[Brain, 'Memory that carries across conversations'],
|
|
29
|
+
[Blocks, 'Build and prototype with Hanzo Studio'],
|
|
30
|
+
[ListChecks, 'Power through tasks with Hanzo Team'],
|
|
31
|
+
];
|
|
32
|
+
/** The page checkout returns to: this one, query and all. */
|
|
33
|
+
function here() {
|
|
34
|
+
if (typeof window === 'undefined')
|
|
35
|
+
return undefined;
|
|
36
|
+
const { origin, pathname, search } = window.location;
|
|
37
|
+
return `${origin}${pathname}${search}`;
|
|
38
|
+
}
|
|
39
|
+
export function Upgrade({ ask, onClose }) {
|
|
40
|
+
const { plan } = usePlan('personal');
|
|
41
|
+
const [cycle, setCycle] = useState('month');
|
|
42
|
+
const shown = ask !== null && plan !== null;
|
|
43
|
+
// The plan is shown to a reader who reached for it: GA4's view_item.
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (shown && plan)
|
|
46
|
+
track('product_viewed', {
|
|
47
|
+
currency: 'USD',
|
|
48
|
+
value: charge(plan, 'month'),
|
|
49
|
+
items: [{ item_id: plan.id, item_name: plan.name }],
|
|
50
|
+
});
|
|
51
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
52
|
+
}, [shown]);
|
|
53
|
+
if (!plan)
|
|
54
|
+
return null;
|
|
55
|
+
const save = saving(plan);
|
|
56
|
+
const interval = save === null ? 'month' : cycle;
|
|
57
|
+
const label = `Upgrade to ${plan.name} for ${quote(plan, interval)}`;
|
|
58
|
+
const href = term(ask?.href ?? planCheckoutUrl(plan, here()), interval);
|
|
59
|
+
const buy = () => {
|
|
60
|
+
const items = [{ item_id: plan.id, item_name: plan.name }];
|
|
61
|
+
track('plan_clicked', { plan: plan.id, interval, cta: label, items });
|
|
62
|
+
track('checkout_started', { currency: 'USD', value: charge(plan, interval), items, interval });
|
|
63
|
+
};
|
|
64
|
+
return (_jsx(Dialog, { open: ask !== null, onOpenChange: (open) => (open ? undefined : onClose()), children: _jsxs(DialogContent, { showCloseButton: false, width: "calc(100vw - 32px)", maxW: 440, bg: "var(--popover)", rounded: "$9", p: "$5", gap: "$5", $sm: { p: '$6' }, children: [_jsx(Box, { render: "button", "aria-label": "Close", onClick: onClose, position: "absolute", t: "$4", r: "$4", p: "$1.5", rounded: "$2", bg: "transparent", borderWidth: 0, cursor: "pointer", hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 18, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) }), _jsx(DialogTitle
|
|
65
|
+
// inline-style: design's serif face. gui names Zen and Zen Mono only; a family it cannot name resets the rung to 15px.
|
|
66
|
+
, {
|
|
67
|
+
// inline-style: design's serif face. gui names Zen and Zen Mono only; a family it cannot name resets the rung to 15px.
|
|
68
|
+
style: { fontFamily: 'var(--font-serif)' }, fontSize: "$8", lineHeight: "$9", fontWeight: "400", color: "$ink", pr: "$6", children: ask?.model ? `Use ${ask.model} with Hanzo ${plan.name}` : `Upgrade to Hanzo ${plan.name}` }), _jsx(YStack, { render: "ul", gap: "$3.5", m: 0, p: 0, children: ROWS.map(([Icon, line]) => (_jsxs(XStack, { render: "li", items: "center", gap: "$3", children: [_jsx(Icon, { size: 18, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { fontSize: "$4", color: "$ink", children: line })] }, line))) }), save === null ? null : (_jsx(ToggleGroup, { type: "single", value: cycle, onValueChange: (v) => {
|
|
69
|
+
if (v === 'month' || v === 'year')
|
|
70
|
+
setCycle(v);
|
|
71
|
+
}, disableDeactivation: true, "aria-label": "Billing term", ...{ width: '100%', p: 4, rounded: 9999, bg: 'var(--surface-2)' }, children: [
|
|
72
|
+
['month', 'Monthly'],
|
|
73
|
+
['year', `Yearly · Save ${save}%`],
|
|
74
|
+
].map(([value, name]) => (_jsx(ToggleGroupItem, { value: value, variant: "outline", flex: 1, rounded: "$10", borderColor: "transparent", children: name }, value))) })), _jsxs(YStack, { gap: "$3", children: [_jsx(Anchor, { href: href, onPress: buy, "data-ga": true, bg: "var(--primary)", color: "var(--primary-foreground)", rounded: "$4", py: "$3", px: "$4", fontSize: "$4", fontWeight: "500", text: "center", textDecorationLine: "none", hoverStyle: { bg: 'var(--primary-hover)' }, children: label }), _jsxs(XStack, { justify: "center", gap: "$5", children: [_jsx(Anchor, { href: site("/pricing"), fontSize: "$3", color: "$soft", hoverStyle: { color: '$ink' }, children: "See all plans" }), _jsx(Box, { render: "button", onClick: onClose, bg: "transparent", borderWidth: 0, p: 0, cursor: "pointer", children: _jsx(Text, { fontSize: "$3", color: "$soft", hoverStyle: { color: '$ink' }, children: "Not now" }) })] })] }), _jsx(Text, { fontSize: "$1", color: "$soft", text: "center", children: "Prices and plans are subject to change at Hanzo's discretion." })] }) }));
|
|
75
|
+
}
|
package/dist/Usage.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Usage(): import("react").JSX.Element;
|
package/dist/Usage.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Free's usage, said where it is spent. Free is limited, and what it spends
|
|
4
|
+
// comes from one pool every free user shares, so the line says both, and what
|
|
5
|
+
// is left of today's when billing states a day window for the plan (the tier's
|
|
6
|
+
// `windows`, lib/hanzo/tier.ts). It renders nothing for any other plan, or
|
|
7
|
+
// while the plan is unread.
|
|
8
|
+
import { useIam } from '@hanzo/iam/react';
|
|
9
|
+
import { Text } from '@hanzo/ui';
|
|
10
|
+
import { org } from './lib/session.js';
|
|
11
|
+
import { useTier } from './lib/tier.js';
|
|
12
|
+
/** The free rung's name in the billing catalog. */
|
|
13
|
+
const FREE = 'free';
|
|
14
|
+
export function Usage() {
|
|
15
|
+
const { isAuthenticated } = useIam();
|
|
16
|
+
const { tier } = useTier(isAuthenticated, org());
|
|
17
|
+
if (tier?.tier?.name !== FREE)
|
|
18
|
+
return null;
|
|
19
|
+
const today = tier.windows?.find((w) => /day/i.test(w.span));
|
|
20
|
+
return (_jsxs(Text, { role: "status", fontSize: "$1", color: "$soft", text: "center", children: ["Free \u00B7 limited, shared pool", today ? ` · ${today.remaining} of ${today.limit} left today` : ''] }));
|
|
21
|
+
}
|
package/dist/Vibe.d.ts
ADDED