@hanzo/rooms 0.1.38 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/Account.js +18 -9
- package/dist/Chat.js +122 -199
- package/dist/Column.js +16 -15
- package/dist/Directory.d.ts +4 -9
- package/dist/Directory.js +60 -155
- package/dist/Me.js +20 -16
- package/dist/Prose.js +5 -4
- package/dist/RightPane.d.ts +20 -28
- package/dist/RightPane.js +107 -327
- package/dist/Settings.js +42 -28
- package/dist/Shell.js +19 -22
- package/dist/dev.js +42 -26
- package/dist/host.d.ts +5 -9
- package/dist/host.js +5 -6
- package/dist/lib/destination.d.ts +6 -6
- package/dist/lib/destination.js +6 -6
- package/dist/lib/host.d.ts +50 -31
- package/dist/lib/host.js +98 -48
- package/dist/lib/limits.d.ts +13 -9
- package/dist/lib/limits.js +20 -0
- package/dist/lib/plans.d.ts +2 -13
- package/dist/lib/plans.js +2 -19
- package/dist/lib/session.d.ts +9 -12
- package/dist/lib/session.js +7 -19
- package/dist/pane.d.ts +4 -57
- package/dist/pane.js +13 -159
- package/package.json +3 -5
- package/dist/Billing.d.ts +0 -1
- package/dist/Billing.js +0 -32
- package/dist/enso.d.ts +0 -72
- package/dist/enso.js +0 -112
- package/dist/model.d.ts +0 -4
- package/dist/model.js +0 -44
package/dist/Settings.js
CHANGED
|
@@ -21,7 +21,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
21
21
|
//
|
|
22
22
|
// The panes that are not yet built say so in one sentence rather than pretending
|
|
23
23
|
// to a screen. An honest empty pane is a smaller lie than an invented one.
|
|
24
|
-
import { dev } from './lib/host.js';
|
|
24
|
+
import { app, dev } from './lib/host.js';
|
|
25
25
|
import { useMemo, useState } from 'react';
|
|
26
26
|
import { ArrowLeft, ArrowUpRight, ExternalLink } from 'lucide-react';
|
|
27
27
|
import { Box, Button, Separator, Text, XStack, YStack, } from '@hanzo/ui';
|
|
@@ -33,17 +33,16 @@ import { useModels } from '@hanzo/ai/react';
|
|
|
33
33
|
import { ModelPicker } from '@hanzo/ui/models';
|
|
34
34
|
import { parseModels } from '@hanzo/ui/models/catalog';
|
|
35
35
|
import { UsagePanel } from '@hanzo/usage/panel';
|
|
36
|
+
import { administers, Credits, label, Plan, Title, useChoice } from '@hanzo/build';
|
|
36
37
|
import { Catalog } from './Directory.js';
|
|
37
|
-
import {
|
|
38
|
-
import { checkoutUrl, payPage } from './lib/pay.js';
|
|
39
|
-
import { planName } from './lib/plans.js';
|
|
38
|
+
import { payPage } from './lib/pay.js';
|
|
40
39
|
import { api } from './lib/api.js';
|
|
41
40
|
import { renewal, useSubscription, useTier } from './lib/tier.js';
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
41
|
+
import { spendShown, useLimits } from './lib/limits.js';
|
|
42
|
+
import { showSettings } from './open.js';
|
|
44
43
|
import { Look } from './look.js';
|
|
45
44
|
import { ProviderMark } from './ProviderMark.js';
|
|
46
|
-
import { org, pick } from './lib/session.js';
|
|
45
|
+
import { bearer, org, pick } from './lib/session.js';
|
|
47
46
|
import { site } from './where.js';
|
|
48
47
|
/** A choice among a few: a hairline edge and a corner. The fill and the edge's
|
|
49
48
|
* ink are the call site's, because they say which one is chosen. */
|
|
@@ -77,13 +76,15 @@ const GROUPS = [
|
|
|
77
76
|
},
|
|
78
77
|
],
|
|
79
78
|
},
|
|
79
|
+
// Plugins and skills are what an agent brings to a run, and Customize is
|
|
80
|
+
// their one home: these rows lead there rather than drawing a second list.
|
|
80
81
|
{
|
|
81
82
|
label: 'Customize',
|
|
82
83
|
rows: [
|
|
83
84
|
{ id: 'apps', label: 'Apps' },
|
|
84
85
|
{ id: 'channels', label: 'Channels' },
|
|
85
|
-
{ id: 'plugins', label: 'Plugins' },
|
|
86
|
-
{ id: 'skills', label: 'Skills' },
|
|
86
|
+
{ id: 'plugins', label: 'Plugins', href: site(app('-/customize/plugins')) },
|
|
87
|
+
{ id: 'skills', label: 'Skills', href: site(app('-/customize')) },
|
|
87
88
|
{ id: 'memory', label: 'Memory', note: 'Nothing is remembered between conversations yet.' },
|
|
88
89
|
],
|
|
89
90
|
},
|
|
@@ -145,9 +146,8 @@ export function SettingsPane({ id }) {
|
|
|
145
146
|
}
|
|
146
147
|
/** One pane. */
|
|
147
148
|
function Pane({ id, note }) {
|
|
148
|
-
if (id === 'apps' || id === 'channels'
|
|
149
|
+
if (id === 'apps' || id === 'channels')
|
|
149
150
|
return _jsx(Catalog, { tab: id });
|
|
150
|
-
}
|
|
151
151
|
if (id === 'general')
|
|
152
152
|
return _jsx(Look, {});
|
|
153
153
|
if (id === 'account')
|
|
@@ -155,7 +155,7 @@ function Pane({ id, note }) {
|
|
|
155
155
|
if (id === 'billing')
|
|
156
156
|
return _jsx(Billing, {});
|
|
157
157
|
if (id === 'usage')
|
|
158
|
-
return _jsx(Usage, {
|
|
158
|
+
return _jsx(Usage, {});
|
|
159
159
|
if (id === 'keys')
|
|
160
160
|
return _jsx(Keys, {});
|
|
161
161
|
if (id === 'model')
|
|
@@ -178,8 +178,9 @@ function Pane({ id, note }) {
|
|
|
178
178
|
function Model() {
|
|
179
179
|
const { models: served } = useModels();
|
|
180
180
|
const models = useMemo(() => parseModels(served).filter((m) => m.class !== 'premium'), [served]);
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
// The one choice Chat and Dev share (@hanzo/build): changed here, changed in both composers.
|
|
182
|
+
const [choice, choose] = useChoice();
|
|
183
|
+
return (_jsxs(YStack, { gap: "$3", maxW: 420, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Used for new chats and new runs. An agent with its own model keeps that choice. Premium models are picked per conversation in the composer." }), _jsx(XStack, { self: "flex-start", children: _jsx(ModelPicker, { models: models, scope: "chat", value: choice.model, onChange: (id) => choose({ model: id }), size: "sm" }) })] }));
|
|
183
184
|
}
|
|
184
185
|
/**
|
|
185
186
|
* WHAT THE GATEWAY SERVES, read from the same `/v1/models` the rooms read.
|
|
@@ -216,37 +217,50 @@ function Account() {
|
|
|
216
217
|
return (_jsx(Box, { render: _jsx("button", { type: "button" }), onClick: () => { pick(o.name); switchOrg(o.name); }, ...tap, children: _jsxs(XStack, { items: "center", justify: "space-between", py: "$2", width: "100%", gap: "$2", children: [_jsx(Text, { fontSize: "$3", color: here ? '$ink' : '$soft', children: o.displayName || o.name }), here && currentRole ? _jsx(Text, { fontSize: "$2", color: "$soft", children: currentRole }) : null] }) }, o.name));
|
|
217
218
|
}))] }), _jsx(Separator, {}), _jsx(XStack, { children: _jsx(Button, { size: "sm", onClick: () => logout(), children: "Sign out" }) })] }));
|
|
218
219
|
}
|
|
219
|
-
/**
|
|
220
|
+
/** Where the plan's and billing's reads go: this browser's bearer, in its organization. */
|
|
221
|
+
function useTarget() {
|
|
222
|
+
const scoped = org();
|
|
223
|
+
return useMemo(() => ({ api: api(), token: bearer, org: scoped }), [scoped]);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The plan and its term, then the credits — @hanzo/build's `Credits`, the one
|
|
227
|
+
* view every Billing page draws: the balance, grants, auto-reload, this month's
|
|
228
|
+
* spend and its limit, said to be separate from the plan's usage. Money moves on
|
|
229
|
+
* the payment host, so adding funds and a card both open it.
|
|
230
|
+
*/
|
|
220
231
|
function Billing() {
|
|
221
232
|
const { isAuthenticated } = useIam();
|
|
233
|
+
const t = useTarget();
|
|
222
234
|
const { tier } = useTier(isAuthenticated, org());
|
|
223
235
|
const sub = useSubscription(isAuthenticated, org());
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
if (!isAuthenticated)
|
|
237
|
+
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see billing." });
|
|
238
|
+
// WHAT THE CUSTOMER BOUGHT, by its family: `Max` with `20x` beside it.
|
|
239
|
+
const plan = tier ? label(tier.plan, tier.tier.name) ?? { name: 'Free', tag: '' } : null;
|
|
240
|
+
return (_jsxs(YStack, { gap: "$5", children: [plan ? (_jsxs(YStack, { gap: "$1", "data-slot": "billing-plan", children: [_jsx(Title, { label: plan, size: "$5" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: sub ? renewal(sub) : tier?.plan ? 'Your plan.' : 'No paid plan. Usage is paid from credits.' })] })) : (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Reading your plan\u2026" })), _jsx(Credits, { target: t, admin: administers(bearer(), org()), add: payPage() }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: "$2", color: "$soft", children: "Payment" }), _jsx(Text, { fontSize: "$3", color: "$soft", children: "A card is entered on the payment host and nowhere else, so this is the one control here that opens another surface." }), _jsx(XStack, { children: _jsx(Box, { render: _jsx("a", { href: payPage(), target: "_blank", rel: "noreferrer" }), ...tap, children: _jsxs(XStack, { items: "center", gap: "$2", py: "$2", children: [_jsx(Text, { fontSize: "$3", color: "$ink", children: "Manage plan, payment method and invoices" }), _jsx(ExternalLink, { size: 13, "aria-hidden": true })] }) }) })] })] }));
|
|
228
241
|
}
|
|
229
242
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
243
|
+
* Usage: @hanzo/build's `Plan`, the one view every Usage page draws — the plan's
|
|
244
|
+
* windows and each class of model's share, as shares; the free allowance on
|
|
245
|
+
* Free; the balance with no plan — and the choice of paying from credits once
|
|
246
|
+
* the allowance runs out. A reader with no paid plan also sees what was spent,
|
|
247
|
+
* over time (@hanzo/usage), because money is their meter.
|
|
234
248
|
*/
|
|
235
|
-
function Usage(
|
|
249
|
+
function Usage() {
|
|
236
250
|
const { accessToken, isAuthenticated } = useIam();
|
|
251
|
+
const t = useTarget();
|
|
237
252
|
const read = useLimits(isAuthenticated, org());
|
|
238
253
|
if (!isAuthenticated || !accessToken) {
|
|
239
254
|
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see your usage." });
|
|
240
255
|
}
|
|
241
|
-
const plan =
|
|
242
|
-
if (
|
|
256
|
+
const plan = _jsx(Plan, { target: t, onPlans: () => window.location.assign(site('/pricing')), onBilling: () => showSettings('billing') });
|
|
257
|
+
if (!spendShown(read))
|
|
243
258
|
return plan;
|
|
244
259
|
// The address is read HERE rather than at import: the panel sends the bearer
|
|
245
260
|
// with every read, and a credentialed read to an absolute address from a page
|
|
246
261
|
// the gateway does not admit dies in preflight — which drew this panel as an
|
|
247
262
|
// account that had spent nothing.
|
|
248
|
-
|
|
249
|
-
return plan ? (_jsxs(YStack, { gap: "$6", children: [plan, spend] })) : (spend);
|
|
263
|
+
return (_jsxs(YStack, { gap: "$6", children: [plan, _jsx(UsagePanel, { baseUrl: api(), token: accessToken, upgrade: payPage(), title: "Spend" })] }));
|
|
250
264
|
}
|
|
251
265
|
/** Keys are minted and revoked through the console, which holds the one issuer. */
|
|
252
266
|
function Keys() {
|
package/dist/Shell.js
CHANGED
|
@@ -68,7 +68,7 @@ import { Face } from "./cast.js";
|
|
|
68
68
|
import { useStarred, titleOf } from "./stars.js";
|
|
69
69
|
import { roomName } from "./network.js";
|
|
70
70
|
import { useTeamRooms, teamKey, roomHref } from "./rooms.js";
|
|
71
|
-
import { apex, dev, ENTRY, room as moves, WORKSPACE } from "./lib/host.js";
|
|
71
|
+
import { apex, app, dev, ENTRY, room as moves, WORKSPACE } from "./lib/host.js";
|
|
72
72
|
import { netKey } from "./conversations.js";
|
|
73
73
|
import { FIRST, SettingsList, SettingsPane, } from "./Settings.js";
|
|
74
74
|
import { Profile } from "./Profile.js";
|
|
@@ -1083,33 +1083,30 @@ function Frame({ mode, children, navigate, back, forward, }) {
|
|
|
1083
1083
|
/**
|
|
1084
1084
|
* THE SURFACES THAT ARE NOT ROOMS, said once.
|
|
1085
1085
|
*
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1089
|
-
* offers the same set, so this says
|
|
1090
|
-
* how it draws them. Written twice
|
|
1091
|
-
* one list and not the other.
|
|
1086
|
+
* Customize is Dev's page, Usage a pane of Settings, Terminals another
|
|
1087
|
+
* product at its own host, Docs a page. The column draws each as a
|
|
1088
|
+
* row of its own under the quiet label Setup, right under the rooms — nothing
|
|
1089
|
+
* waits behind a More — and the palette offers the same set, so this says
|
|
1090
|
+
* WHAT they are and each surface says only how it draws them. Written twice
|
|
1091
|
+
* they had already parted: Settings was in one list and not the other.
|
|
1092
1092
|
*
|
|
1093
1093
|
* ONE VERB PER ROW, and no href beside it. A row that carried both would be
|
|
1094
1094
|
* two ways to reach one place, and the two would answer differently the day
|
|
1095
1095
|
* one of them learned to close the sheet behind it.
|
|
1096
1096
|
*/
|
|
1097
1097
|
const tools = useMemo(() => [
|
|
1098
|
+
// CUSTOMIZE is the one home of what an agent brings to a run — skills,
|
|
1099
|
+
// connectors, plugins, agents — in Dev (@hanzo/build). One row leads
|
|
1100
|
+
// there; a second list here would be the second copy that drifts.
|
|
1098
1101
|
{
|
|
1099
|
-
id: "
|
|
1100
|
-
label: "
|
|
1102
|
+
id: "customize",
|
|
1103
|
+
label: "Customize",
|
|
1101
1104
|
icon: Blocks,
|
|
1102
1105
|
go: () => {
|
|
1103
|
-
navigate?.(site("
|
|
1106
|
+
navigate?.(site(app("-/customize")));
|
|
1104
1107
|
dismiss();
|
|
1105
1108
|
},
|
|
1106
1109
|
},
|
|
1107
|
-
{
|
|
1108
|
-
id: "skills",
|
|
1109
|
-
label: "Skills",
|
|
1110
|
-
icon: Workflow,
|
|
1111
|
-
go: () => showSettings("skills"),
|
|
1112
|
-
},
|
|
1113
1110
|
// TERMINALS ARE TABS, and Tabs is its own product at its own host —
|
|
1114
1111
|
// hanzo.app's row pointed there too. A new tab for the reason that row
|
|
1115
1112
|
// used one: a reader who follows it inside this one loses the project
|
|
@@ -1200,7 +1197,7 @@ function Frame({ mode, children, navigate, back, forward, }) {
|
|
|
1200
1197
|
// to disagree about which one you are standing in.
|
|
1201
1198
|
const workspace = currentOrg?.displayName || currentOrg?.name || "Hanzo";
|
|
1202
1199
|
const owner = currentOrgId ?? "";
|
|
1203
|
-
// The
|
|
1200
|
+
// The folder's rows and Setup's, narrowed by the column's filter like every other list.
|
|
1204
1201
|
const products = CONSOLE_PRODUCTS.filter((prod) => matches(prod.label, filter));
|
|
1205
1202
|
const more = tools.filter((tool) => matches(tool.label, filter));
|
|
1206
1203
|
// The static export prerenders the signed-out room, so the rail's identity
|
|
@@ -1218,17 +1215,17 @@ function Frame({ mode, children, navigate, back, forward, }) {
|
|
|
1218
1215
|
return;
|
|
1219
1216
|
e.preventDefault();
|
|
1220
1217
|
navigate("/");
|
|
1221
|
-
}, "aria-label": `${brand()} home`, title: "Home" }), "data-slot": "home", display: "flex", items: "center", justify: "center", width: 28, height: 28, $touchable: { width: 40, height: 40 }, color: "inherit", children: _jsx(HanzoMark, { size: 18 }) }), settled && isAuthenticated ? (_jsx(XStack, { shrink: 1, minW: 0, children: _jsx(OrgProjectSwitcher, { ...orgState, alwaysShow: true }) })) : null] }),
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1218
|
+
}, "aria-label": `${brand()} home`, title: "Home" }), "data-slot": "home", display: "flex", items: "center", justify: "center", width: 28, height: 28, $touchable: { width: 40, height: 40 }, color: "inherit", children: _jsx(HanzoMark, { size: 18 }) }), settled && isAuthenticated ? (_jsx(XStack, { shrink: 1, minW: 0, children: _jsx(OrgProjectSwitcher, { ...orgState, alwaysShow: true }) })) : null] }), _jsx(YStack, { "data-slot": "places-list", px: "$2", pb: "$2", "$max-md": { display: "none" }, children: places.filter((place) => matches(place.label, filter)).map((place) => (_jsx(SidebarItem, { icon: _jsx(place.icon, { size: 16, "aria-hidden": true }), active: place.id === mode, onPress: () => {
|
|
1219
|
+
navigate?.(place.route);
|
|
1220
|
+
onPick?.();
|
|
1221
|
+
}, children: place.label }, place.id))) }), more.length ? (_jsx(YStack, { "data-slot": "setup-list", px: "$2", pb: "$2", children: _jsx(SidebarSection, { label: "Setup", children: more.map((tool) => (_jsx(SidebarItem, { icon: _jsx(tool.icon, { size: 16, "aria-hidden": true }), onPress: tool.go, children: tool.label }, tool.id))) }) })) : null, _jsx(XStack, { px: "$3", pb: "$2", children: _jsxs(XStack, { flex: 1, height: 30, items: "center", gap: "$2", px: "$2", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Search, { size: 13, "aria-hidden": true }), _jsx(Text, { render: _jsx("input", { value: filter, onChange: (e) => setFilter(e.target.value), onKeyDown: (e) => {
|
|
1225
1222
|
if (e.key === "Escape")
|
|
1226
1223
|
setFilter("");
|
|
1227
1224
|
},
|
|
1228
1225
|
// FILTER, not Search. Two controls that both said Search did two
|
|
1229
1226
|
// different things: this one narrows the list beside you, and the
|
|
1230
1227
|
// bar above opens the palette that finds anything anywhere.
|
|
1231
|
-
placeholder: "Filter", "aria-label": "Filter sidebar" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$2" })] }) }), settingsPane ? (_jsx(SidebarScroll, { children: _jsx(SettingsList, { active: settingsPane, filter: filter, onPick: pick, onBack: () => showSettings(null) }) })) : (_jsx(_Fragment, { children: _jsx(SidebarScroll, { children: _jsxs(_Fragment, { children: [settled ? (_jsx(Index, { mode: mode, owner: owner, filter: filter, onPick: onPick, navigate: navigate })) : null, products.length ? (_jsx(SidebarFolder, { name: "Cloud & Platform", open: filter ? true : undefined, children: products.map((prod) => (_jsx(SidebarItem, { icon: _jsx(prod.icon, { size: 13, "aria-hidden": true }), onPress: () => window.open(prod.href, "_blank", "noopener"), children: prod.label }, prod.id))) })) : null
|
|
1228
|
+
placeholder: "Filter", "aria-label": "Filter sidebar" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$2" })] }) }), settingsPane ? (_jsx(SidebarScroll, { children: _jsx(SettingsList, { active: settingsPane, filter: filter, onPick: pick, onBack: () => showSettings(null) }) })) : (_jsx(_Fragment, { children: _jsx(SidebarScroll, { children: _jsxs(_Fragment, { children: [settled ? (_jsx(Index, { mode: mode, owner: owner, filter: filter, onPick: onPick, navigate: navigate })) : null, products.length ? (_jsx(SidebarFolder, { name: "Cloud & Platform", open: filter ? true : undefined, children: products.map((prod) => (_jsx(SidebarItem, { icon: _jsx(prod.icon, { size: 13, "aria-hidden": true }), onPress: () => window.open(prod.href, "_blank", "noopener"), children: prod.label }, prod.id))) })) : null] }) }) })), settled && onApex ? _jsx(Leave, { href: signedIn ? ENTRY : WORKSPACE, children: "Workspace" }) : null, settled ? (signedIn ? (_jsxs(_Fragment, { children: [superAdmin() ? (_jsx(SidebarItem, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), onPress: () => setSupporting(true), children: "All organizations" })) : null, _jsx(SidebarItem, { icon: _jsx(UserPlus, { size: 15, "aria-hidden": true }), onPress: () => window.open(invite(), "_blank", "noopener"), children: "Invite Teammates" }), _jsx(SidebarItem, { icon: _jsx(Share2, { size: 15, "aria-hidden": true }), onPress: () => {
|
|
1232
1229
|
navigate?.(site("/referral"));
|
|
1233
1230
|
dismiss();
|
|
1234
1231
|
}, children: "Share Hanzo" }), _jsx(SidebarItem, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), onPress: () => window.open(BILLING, "_blank", "noopener"), children: "Upgrade" }), _jsx(Account, { onToggleCollapse: toggle, onProfile: () => setProfiling(true), onAppearance: () => showSettings("general"), onUsage: () => showSettings("usage"), onKeys: () => showSettings("keys") })] })) : null) : null] }));
|
package/dist/dev.js
CHANGED
|
@@ -7,13 +7,19 @@
|
|
|
7
7
|
// to leave. The name drawn at the top left is Hanzo Dev: this host is the
|
|
8
8
|
// dev agent. Chat, on the rooms shell, is Hanzo AI.
|
|
9
9
|
//
|
|
10
|
-
// THE ADDRESS IS
|
|
11
|
-
// it in the builder's
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
10
|
+
// THE ADDRESS IS A PATH. Dev is /dev, and every place the builder shows is a
|
|
11
|
+
// path under it in the builder's real-path grammar (lib/host.ts `app`, `place`;
|
|
12
|
+
// @hanzo/build/route `href`, `parse`): a run, a screen, a Settings section, a
|
|
13
|
+
// Customize tab, the plans and a project all survive a reload, and Back steps
|
|
14
|
+
// through them. The host hands the rooms the path (`<Rooms path>`); a move is a
|
|
15
|
+
// native history entry, which the Next router folds into `usePathname`, so
|
|
16
|
+
// reading the address and moving it are one path.
|
|
17
|
+
//
|
|
18
|
+
// A PANE THAT ASKS FOR SETTINGS (`showSettings`, open.ts) is answered here: the
|
|
19
|
+
// rooms' shell draws its own Settings pane, and the app has none, so Chat's
|
|
20
|
+
// "See usage" opens the builder's Usage — the page the account menu's Usage
|
|
21
|
+
// opens — rather than nothing.
|
|
22
|
+
import { useEffect, useMemo } from 'react';
|
|
17
23
|
import { useLook, useRooms } from './host.js';
|
|
18
24
|
import { useIam } from '@hanzo/iam/react';
|
|
19
25
|
import { useSignOut } from './lib/signout.js';
|
|
@@ -21,8 +27,9 @@ import { administers, path, route } from '@hanzo/build';
|
|
|
21
27
|
import { bearer, named, org, orgs, pick } from './lib/session.js';
|
|
22
28
|
import { enter } from './lib/destination.js';
|
|
23
29
|
import { api } from './lib/api.js';
|
|
24
|
-
import { app,
|
|
30
|
+
import { app, go, place } from './lib/host.js';
|
|
25
31
|
import { link } from './lib/tags.js';
|
|
32
|
+
import { showSettings, useOpen } from './open.js';
|
|
26
33
|
/** Where the builder links out: every one of them an address of this site. */
|
|
27
34
|
const LINKS = {
|
|
28
35
|
github: app('-/sync'),
|
|
@@ -32,15 +39,28 @@ const LINKS = {
|
|
|
32
39
|
};
|
|
33
40
|
/** The person's theme, when they have chosen one the builder can name. */
|
|
34
41
|
const theme = (t) => (t === 'light' || t === 'dark' || t === 'system' ? t : undefined);
|
|
35
|
-
/** The
|
|
36
|
-
const
|
|
42
|
+
/** The builder address a pathname names: Dev's place, or New for a path that is not Dev's. */
|
|
43
|
+
const at = (pathname) => {
|
|
44
|
+
const p = pathname ? place(pathname) : null;
|
|
45
|
+
return p?.mode === 'dev' ? p.at : '';
|
|
46
|
+
};
|
|
37
47
|
/** The host for a signed-in reader. */
|
|
38
48
|
export function useDevHost() {
|
|
39
49
|
const { user, accessToken } = useIam();
|
|
40
50
|
const logout = useSignOut();
|
|
41
51
|
const { router, path: pathname } = useRooms();
|
|
42
|
-
const here =
|
|
52
|
+
const here = path(route(at(pathname)));
|
|
43
53
|
const look = useLook();
|
|
54
|
+
const { settings } = useOpen();
|
|
55
|
+
// The builder names every Settings section the rooms' pane does that it has;
|
|
56
|
+
// one it does not have opens Settings itself.
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!settings)
|
|
59
|
+
return;
|
|
60
|
+
showSettings(null);
|
|
61
|
+
const asked = route(`-/settings/${settings}`);
|
|
62
|
+
go(app(path(asked.kind === 'settings' ? asked : { kind: 'settings', section: 'general' })));
|
|
63
|
+
}, [settings]);
|
|
44
64
|
// The token's claims change only when the token does: a sign-in, a refresh.
|
|
45
65
|
const memberships = useMemo(() => orgs(), [accessToken]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
46
66
|
const scoped = useMemo(() => org(), [accessToken]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
@@ -68,19 +88,12 @@ export function useDevHost() {
|
|
|
68
88
|
person: { name, email, avatar },
|
|
69
89
|
admin: administers(bearer(), scoped),
|
|
70
90
|
path: here,
|
|
71
|
-
go: (to, how) =>
|
|
72
|
-
const next = app(canonical(to));
|
|
73
|
-
if (window.location.pathname === next && !window.location.search)
|
|
74
|
-
return;
|
|
75
|
-
if (how?.replace)
|
|
76
|
-
window.history.replaceState(null, '', next);
|
|
77
|
-
else
|
|
78
|
-
window.history.pushState(null, '', next);
|
|
79
|
-
},
|
|
91
|
+
go: (to, how) => go(app(to), how?.replace),
|
|
80
92
|
links: LINKS,
|
|
81
|
-
// An address of
|
|
82
|
-
// this tab. platform.hanzo.ai is
|
|
83
|
-
//
|
|
93
|
+
// An address of the app moves the app, another of this site moves in the
|
|
94
|
+
// router, and another origin is left in this tab. platform.hanzo.ai is
|
|
95
|
+
// another product, and a click that names it stays here — everything the
|
|
96
|
+
// builder had it for is in its Settings.
|
|
84
97
|
open: (href) => {
|
|
85
98
|
let url;
|
|
86
99
|
try {
|
|
@@ -91,10 +104,13 @@ export function useDevHost() {
|
|
|
91
104
|
}
|
|
92
105
|
if (url.hostname === 'platform.hanzo.ai')
|
|
93
106
|
return;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
else
|
|
107
|
+
const to = `${url.pathname}${url.search}${url.hash}`;
|
|
108
|
+
if (url.origin !== window.location.origin)
|
|
97
109
|
window.location.assign(link(url.href));
|
|
110
|
+
else if (place(url.pathname))
|
|
111
|
+
go(to);
|
|
112
|
+
else
|
|
113
|
+
router.push(to);
|
|
98
114
|
},
|
|
99
115
|
signIn: () => enter(),
|
|
100
116
|
signOut: () => void logout(),
|
package/dist/host.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* configure({ site: 'https://hanzo.ai', home: '/', signUp: '/start' })
|
|
9
9
|
*
|
|
10
|
-
* <Rooms router={useRouter()}
|
|
10
|
+
* <Rooms router={useRouter()} path={usePathname()} route={route} Link={Link}>
|
|
11
11
|
* <Room mode="cal" />
|
|
12
12
|
* </Rooms>
|
|
13
13
|
*
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* any component and on pages the rooms never draw. So the host states them once
|
|
18
18
|
* with `configure()`, in a module its root imports, before anything renders.
|
|
19
19
|
*
|
|
20
|
-
* The ROUTER, the
|
|
20
|
+
* The ROUTER, the path and the route change as the page moves, so they ride
|
|
21
21
|
* React context, and a room reads them with `useRooms()`.
|
|
22
22
|
*/
|
|
23
23
|
import { type AnchorHTMLAttributes, type ComponentType, type ReactNode } from 'react';
|
|
@@ -34,21 +34,17 @@ export interface Router {
|
|
|
34
34
|
}
|
|
35
35
|
interface Moving {
|
|
36
36
|
router: Router;
|
|
37
|
-
/** The address's query, for the rooms that read one (Chat's `?chat=`). */
|
|
38
|
-
search: URLSearchParams | null;
|
|
39
37
|
/** The address's path, for the rooms that read one (Dev's, under `/dev`). */
|
|
40
38
|
path: string | null;
|
|
41
39
|
route: string;
|
|
42
40
|
Link: Link;
|
|
43
41
|
}
|
|
44
|
-
export interface RoomsProps extends Omit<Moving, '
|
|
45
|
-
/** Absent where no mounted room reads the query, so a static export needs no Suspense for it. */
|
|
46
|
-
search?: URLSearchParams | null;
|
|
42
|
+
export interface RoomsProps extends Omit<Moving, 'path'> {
|
|
47
43
|
/** Absent where no mounted room reads the path. */
|
|
48
44
|
path?: string | null;
|
|
49
45
|
children?: ReactNode;
|
|
50
46
|
}
|
|
51
|
-
/** The router, the
|
|
47
|
+
/** The router, the route and the link the host mounted the rooms with. */
|
|
52
48
|
export declare const useRooms: () => Moving;
|
|
53
49
|
/** The org in scope, by the id the appearance layers are keyed on. */
|
|
54
50
|
export declare function useOrg(): {
|
|
@@ -76,5 +72,5 @@ export declare function useLook(): {
|
|
|
76
72
|
* keeps their choice in step with IAM.
|
|
77
73
|
*/
|
|
78
74
|
export declare function Look(): any;
|
|
79
|
-
export declare function Rooms({ router,
|
|
75
|
+
export declare function Rooms({ router, path, route, Link, children }: RoomsProps): import("react").JSX.Element;
|
|
80
76
|
export {};
|
package/dist/host.js
CHANGED
|
@@ -9,7 +9,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
9
9
|
*
|
|
10
10
|
* configure({ site: 'https://hanzo.ai', home: '/', signUp: '/start' })
|
|
11
11
|
*
|
|
12
|
-
* <Rooms router={useRouter()}
|
|
12
|
+
* <Rooms router={useRouter()} path={usePathname()} route={route} Link={Link}>
|
|
13
13
|
* <Room mode="cal" />
|
|
14
14
|
* </Rooms>
|
|
15
15
|
*
|
|
@@ -19,7 +19,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
19
19
|
* any component and on pages the rooms never draw. So the host states them once
|
|
20
20
|
* with `configure()`, in a module its root imports, before anything renders.
|
|
21
21
|
*
|
|
22
|
-
* The ROUTER, the
|
|
22
|
+
* The ROUTER, the path and the route change as the page moves, so they ride
|
|
23
23
|
* React context, and a room reads them with `useRooms()`.
|
|
24
24
|
*/
|
|
25
25
|
import { createContext, useContext } from 'react';
|
|
@@ -34,12 +34,11 @@ const Context = createContext({
|
|
|
34
34
|
back: () => window.history.back(),
|
|
35
35
|
forward: () => window.history.forward(),
|
|
36
36
|
},
|
|
37
|
-
search: null,
|
|
38
37
|
path: null,
|
|
39
38
|
route: '/',
|
|
40
39
|
Link: Plain,
|
|
41
40
|
});
|
|
42
|
-
/** The router, the
|
|
41
|
+
/** The router, the route and the link the host mounted the rooms with. */
|
|
43
42
|
export const useRooms = () => useContext(Context);
|
|
44
43
|
/** The org in scope, by the id the appearance layers are keyed on. */
|
|
45
44
|
export function useOrg() {
|
|
@@ -72,6 +71,6 @@ export function Look() {
|
|
|
72
71
|
useLook();
|
|
73
72
|
return null;
|
|
74
73
|
}
|
|
75
|
-
export function Rooms({ router,
|
|
76
|
-
return _jsx(Context.Provider, { value: { router,
|
|
74
|
+
export function Rooms({ router, path = null, route, Link, children }) {
|
|
75
|
+
return _jsx(Context.Provider, { value: { router, path, route, Link }, children: children });
|
|
77
76
|
}
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
* person who cannot be sent back to their page must still be signed in, so a
|
|
20
20
|
* refusal degrades to the default and never to an exception.
|
|
21
21
|
*/
|
|
22
|
-
/** The default on the apex:
|
|
23
|
-
* (
|
|
24
|
-
* typed /login, a /signup funnel, a stranger
|
|
25
|
-
* mid-flight). */
|
|
22
|
+
/** The default on the apex: `/`, which takes anyone signed in to the app, in
|
|
23
|
+
* the mode they were last in (lib/host.ts `resume`). It answers the case where
|
|
24
|
+
* there is no page to go back to (a typed /login, a /signup funnel, a stranger
|
|
25
|
+
* who cleared their storage mid-flight). */
|
|
26
26
|
export declare const DEFAULT_DESTINATION = "/";
|
|
27
27
|
/** Where a sign-in with no page to go back to lands on `hostname`: the app on
|
|
28
28
|
* the apex, and the rooms' Home everywhere else. */
|
|
@@ -47,8 +47,8 @@ export declare function door(): string;
|
|
|
47
47
|
/**
|
|
48
48
|
* Send this reader to sign in, and back to this page after.
|
|
49
49
|
*
|
|
50
|
-
* Every signed-out door into the app is this one call:
|
|
51
|
-
*
|
|
50
|
+
* Every signed-out door into the app is this one call: /chat, /dev and every
|
|
51
|
+
* path under them, a room. The page is written down, and the browser goes to `to`,
|
|
52
52
|
* which asks IAM for an existing session before it draws a form
|
|
53
53
|
* (components/auth/panel.tsx `Gate`). On a `guest` host the same address is asked for on hanzo.ai instead. With no `to`
|
|
54
54
|
* the page is `door()`'s; a control that says "Sign in" names LOGIN.
|
package/dist/lib/destination.js
CHANGED
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
import { apex, guest, SIGNIN } from "./host.js";
|
|
23
23
|
import { SHARED } from "./share.js";
|
|
24
24
|
import { where } from "../where.js";
|
|
25
|
-
/** The default on the apex:
|
|
26
|
-
* (
|
|
27
|
-
* typed /login, a /signup funnel, a stranger
|
|
28
|
-
* mid-flight). */
|
|
25
|
+
/** The default on the apex: `/`, which takes anyone signed in to the app, in
|
|
26
|
+
* the mode they were last in (lib/host.ts `resume`). It answers the case where
|
|
27
|
+
* there is no page to go back to (a typed /login, a /signup funnel, a stranger
|
|
28
|
+
* who cleared their storage mid-flight). */
|
|
29
29
|
export const DEFAULT_DESTINATION = "/";
|
|
30
30
|
/** Where a sign-in with no page to go back to lands on `hostname`: the app on
|
|
31
31
|
* the apex, and the rooms' Home everywhere else. */
|
|
@@ -107,8 +107,8 @@ export function door() {
|
|
|
107
107
|
/**
|
|
108
108
|
* Send this reader to sign in, and back to this page after.
|
|
109
109
|
*
|
|
110
|
-
* Every signed-out door into the app is this one call:
|
|
111
|
-
*
|
|
110
|
+
* Every signed-out door into the app is this one call: /chat, /dev and every
|
|
111
|
+
* path under them, a room. The page is written down, and the browser goes to `to`,
|
|
112
112
|
* which asks IAM for an existing session before it draws a form
|
|
113
113
|
* (components/auth/panel.tsx `Gate`). On a `guest` host the same address is asked for on hanzo.ai instead. With no `to`
|
|
114
114
|
* the page is `door()`'s; a control that says "Sign in" names LOGIN.
|
package/dist/lib/host.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which host this export is read on,
|
|
2
|
+
* Which host this export is read on, what that host keeps, and the app's
|
|
3
|
+
* addresses — the one module that writes and reads them.
|
|
3
4
|
*
|
|
4
5
|
* hanzo.ai is the site for a stranger and the app for a member: the web app is
|
|
5
|
-
*
|
|
6
|
-
* workspace, its own app (hanzo-apps/team) with Home
|
|
7
|
-
* still carries the rooms, so a room asked for on the
|
|
8
|
-
* room on hanzo.team, and `output: 'export'` has no
|
|
9
|
-
* decided in the browser off the hostname, here.
|
|
6
|
+
* /chat and /dev once signed in, and `/` takes a member to the mode they were
|
|
7
|
+
* last in. hanzo.team is the workspace, its own app (hanzo-apps/team) with Home
|
|
8
|
+
* at its root. This export still carries the rooms, so a room asked for on the
|
|
9
|
+
* apex is sent to the same room on hanzo.team, and `output: 'export'` has no
|
|
10
|
+
* server to do that, so it is decided in the browser off the hostname, here.
|
|
10
11
|
*
|
|
11
12
|
* Two predicates, one per product: `apex()` for the app, `team()` for the
|
|
12
13
|
* workspace. Every host that is neither (localhost, a preview) draws the whole
|
|
@@ -30,37 +31,55 @@ export declare const ENTRY = "https://hanzo.team/?signin";
|
|
|
30
31
|
*/
|
|
31
32
|
export declare const START = "https://hanzo.team/start";
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
34
|
+
* THE APP'S ADDRESSES ARE PATHS, in two modes at two roots. Chat is /chat, a
|
|
35
|
+
* new conversation, and /chat/<id> one of the reader's. Dev is /dev, Dev's New,
|
|
36
|
+
* and every place of @hanzo/build's builder is a path under it, written and
|
|
37
|
+
* read by `href`/`parse` (@hanzo/build/route): /dev/run/<id>, /dev/projects,
|
|
38
|
+
* /dev/projects/<org>/<repo> a repository, /dev/projects/<slug> a deployed
|
|
39
|
+
* site, /dev/issues, /dev/artifacts, /dev/templates, /dev/automations[/<id>],
|
|
40
|
+
* /dev/machines, /dev/environments, /dev/customize[/<tab>],
|
|
41
|
+
* /dev/settings[/<section>], /dev/plans. The export holds one page per root,
|
|
42
|
+
* /chat and /dev, and the edge serves it for every path under it (hanzo.ai's
|
|
43
|
+
* lib/edge.ts `APP`), so every address reloads.
|
|
41
44
|
*/
|
|
45
|
+
export declare const CHAT = "/chat";
|
|
42
46
|
export declare const DEV = "/dev";
|
|
43
|
-
/**
|
|
44
|
-
export declare const app: (path: string) => string;
|
|
45
|
-
/** The app address a pathname names under `/dev`, or null for a pathname that is not Dev's. */
|
|
46
|
-
export declare const under: (pathname: string | null | undefined) => string | null;
|
|
47
|
-
/**
|
|
48
|
-
* Chat, the app's default mode: `/` is a new conversation and `?chat=<id>` one
|
|
49
|
-
* of the reader's. So the address says which mode the app is in — a path under
|
|
50
|
-
* `/dev` for Dev, anything else Chat — and a reload keeps it.
|
|
51
|
-
*/
|
|
52
|
-
export declare const TALK = "chat";
|
|
53
|
-
/** `/` in Chat, on a conversation, or `/` itself for a new one. */
|
|
47
|
+
/** Chat on a conversation, or /chat itself for a new one. */
|
|
54
48
|
export declare const talk: (id?: string | null) => string;
|
|
49
|
+
/** Dev at a builder address (`route` in @hanzo/build); '' is New, /dev. What is not an address is New. */
|
|
50
|
+
export declare const app: (at: string) => string;
|
|
51
|
+
/** A place in the app: Chat on a conversation or none, or Dev at a builder address. */
|
|
52
|
+
export type Place = {
|
|
53
|
+
mode: 'chat';
|
|
54
|
+
thread: string | null;
|
|
55
|
+
} | {
|
|
56
|
+
mode: 'dev';
|
|
57
|
+
at: string;
|
|
58
|
+
};
|
|
59
|
+
/** The place a path names, or null when it names none of the app's. */
|
|
60
|
+
export declare function place(pathname: string): Place | null;
|
|
61
|
+
/** An address as analytics counts it: a record folded to its kind, so every conversation is one page. */
|
|
62
|
+
export declare function page(pathname: string): string;
|
|
55
63
|
/**
|
|
56
|
-
* Dev opened on one run,
|
|
57
|
-
* link, a room's Dev, the console's "open in the builder". It signs a
|
|
58
|
-
* in on the way (app/(app)/dev/_door.tsx), and is reached through
|
|
59
|
-
* here on hanzo.ai, across to hanzo.ai from hanzo.team. Only a value
|
|
60
|
-
* exactly a session id, a repository or a site's slug is carried;
|
|
61
|
-
* opens /dev itself, so no caller can make this name another
|
|
64
|
+
* Dev opened on one run, repository or site, from anywhere: every "Try Hanzo
|
|
65
|
+
* Dev" link, a room's Dev, the console's "open in the builder". It signs a
|
|
66
|
+
* stranger in on the way (app/(app)/dev/_door.tsx), and is reached through
|
|
67
|
+
* `site()`: here on hanzo.ai, across to hanzo.ai from hanzo.team. Only a value
|
|
68
|
+
* that is exactly a session id, a repository or a site's slug is carried;
|
|
69
|
+
* anything else opens /dev itself, so no caller can make this name another
|
|
70
|
+
* address.
|
|
62
71
|
*/
|
|
63
72
|
export declare const dev: (ref?: string | null) => string;
|
|
73
|
+
/**
|
|
74
|
+
* Move the app to an address of this page: a history entry, or in place of this
|
|
75
|
+
* one. No document load and no route change — the Next router folds a native
|
|
76
|
+
* history entry into `usePathname`, so the app follows the path it reads.
|
|
77
|
+
*/
|
|
78
|
+
export declare function go(address: string, replace?: boolean): void;
|
|
79
|
+
/** Keep the mode the reader is in, for `resume`. */
|
|
80
|
+
export declare function remember(mode: Place['mode']): void;
|
|
81
|
+
/** Where `/` takes a member: the mode they were last in, Chat when none is kept. */
|
|
82
|
+
export declare function resume(): string;
|
|
64
83
|
/**
|
|
65
84
|
* The workspace's rooms: every route under app/(app) the apex does not keep.
|
|
66
85
|
*
|