@hanzo/rooms 0.1.67 → 0.1.69
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/dist/Account.js +8 -11
- package/dist/App.js +38 -9
- package/dist/Column.d.ts +10 -3
- package/dist/Column.js +4 -4
- package/dist/Rail.d.ts +5 -4
- package/dist/Rail.js +5 -6
- package/dist/Settings.d.ts +2 -10
- package/dist/Settings.js +7 -219
- package/dist/SettingsPane.d.ts +10 -0
- package/dist/SettingsPane.js +222 -0
- package/dist/Shell.js +5 -3
- package/dist/Standing.d.ts +4 -0
- package/dist/Standing.js +16 -0
- package/dist/lib/host.d.ts +5 -0
- package/dist/lib/host.js +6 -1
- package/dist/where.d.ts +1 -1
- package/dist/where.js +1 -1
- package/package.json +3 -1
package/dist/Account.js
CHANGED
|
@@ -3,28 +3,28 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
/**
|
|
4
4
|
* WHO YOU ARE, at the foot of the sidebar: the person, and the person's menu.
|
|
5
5
|
*
|
|
6
|
-
* The plan leads it — @hanzo/build's `Meter
|
|
7
|
-
* with: the plan by its family and its
|
|
8
|
-
* Free, the balance only with no plan. Then profile, appearance, usage, billing,
|
|
6
|
+
* The plan leads it — @hanzo/build's `Meter` (Standing.tsx, loaded with the
|
|
7
|
+
* menu), the block every account menu opens with: the plan by its family and its
|
|
8
|
+
* windows as shares, the free allowance on Free, the balance only with no plan. Then profile, appearance, usage, billing,
|
|
9
9
|
* API keys, security and sign out — the account, and nothing that belongs to the
|
|
10
10
|
* workspace. Which workspace you stand in is the switcher at the TOP of the
|
|
11
11
|
* sidebar, and what it may spend is its Billing: a plan never sits beside a
|
|
12
12
|
* balance here.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import { lazy, Suspense, useEffect, useRef, useState } from 'react';
|
|
15
15
|
import { Activity, ChevronsUpDown, CreditCard, KeyRound, LogOut, PanelLeftClose, Palette, Shield, UserRound } from 'lucide-react';
|
|
16
|
-
import { Meter, useStanding } from '@hanzo/build';
|
|
17
16
|
import { XStack, YStack, Text, Box } from '@hanzo/ui';
|
|
18
17
|
import { sheet } from '@hanzo/ui/glass';
|
|
19
18
|
import { useIam } from '@hanzo/iam/react';
|
|
20
19
|
import { useSignOut } from './lib/signout.js';
|
|
21
20
|
import { useAccount } from './lib/account.js';
|
|
22
21
|
import { Face } from './cast.js';
|
|
23
|
-
import {
|
|
24
|
-
import { bearer, org } from './lib/session.js';
|
|
22
|
+
import { iam } from './lib/api.js';
|
|
25
23
|
import { link } from './lib/tags.js';
|
|
26
24
|
import { showSettings } from './open.js';
|
|
27
25
|
import { site } from './where.js';
|
|
26
|
+
/** The plan at the menu's head, loaded with the menu. */
|
|
27
|
+
const Standing = lazy(() => import('./Standing.js').then((m) => ({ default: m.Standing })));
|
|
28
28
|
function Row({ icon, onPress, children, }) {
|
|
29
29
|
return (_jsx(Box, { render: "button", onClick: onPress, width: "100%", px: "$3", py: "$2", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$2", children: [icon, _jsx(Text, { fontSize: "$2", color: "$ink", children: children })] }) }));
|
|
30
30
|
}
|
|
@@ -35,9 +35,6 @@ export function Account({ onToggleCollapse, onProfile, onAppearance, onUsage, on
|
|
|
35
35
|
const { user: account } = useAccount();
|
|
36
36
|
const [open, setOpen] = useState(false);
|
|
37
37
|
const holder = useRef(null);
|
|
38
|
-
const scoped = org();
|
|
39
|
-
const target = useMemo(() => ({ api: api(), token: bearer, org: scoped }), [scoped]);
|
|
40
|
-
const plan = useStanding(target, open);
|
|
41
38
|
useEffect(() => {
|
|
42
39
|
if (!open)
|
|
43
40
|
return;
|
|
@@ -64,5 +61,5 @@ export function Account({ onToggleCollapse, onProfile, onAppearance, onUsage, on
|
|
|
64
61
|
// @ts-expect-error — ref is forwarded to the DOM node by the primitive.
|
|
65
62
|
, {
|
|
66
63
|
// @ts-expect-error — ref is forwarded to the DOM node by the primitive.
|
|
67
|
-
ref: holder, position: "relative", borderTopWidth: 1, borderColor: "$borderColor", children: [open ? (_jsxs(YStack, { role: "menu", "aria-label": "Account", position: "absolute", b: "calc(100% + 6px)", l: "$2", r: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", ...sheet(2), py: "$2", gap: "$1", z: "var(--z-modal)", children: [_jsx(
|
|
64
|
+
ref: holder, position: "relative", borderTopWidth: 1, borderColor: "$borderColor", children: [open ? (_jsxs(YStack, { role: "menu", "aria-label": "Account", position: "absolute", b: "calc(100% + 6px)", l: "$2", r: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", ...sheet(2), py: "$2", gap: "$1", z: "var(--z-modal)", children: [_jsx(Suspense, { fallback: null, children: _jsx(Standing, { onPlans: act(() => window.location.assign(site('/pricing'))), onBilling: act(() => showSettings('billing')) }) }), _jsx(Box, { height: 1, bg: "$borderColor", my: "$1" }), _jsx(Row, { icon: _jsx(UserRound, { size: 15, "aria-hidden": true }), onPress: act(() => (onProfile ? onProfile() : window.location.assign(link(`${iam()}/account`)))), children: "Profile" }), onAppearance ? (_jsx(Row, { icon: _jsx(Palette, { size: 15, "aria-hidden": true }), onPress: act(onAppearance), children: "Appearance" })) : null, onUsage ? (_jsx(Row, { icon: _jsx(Activity, { size: 15, "aria-hidden": true }), onPress: act(onUsage), children: "Usage" })) : null, _jsx(Row, { icon: _jsx(CreditCard, { size: 15, "aria-hidden": true }), onPress: act(() => showSettings('billing')), children: "Billing" }), onKeys ? (_jsx(Row, { icon: _jsx(KeyRound, { size: 15, "aria-hidden": true }), onPress: act(onKeys), children: "API keys" })) : null, _jsx(Row, { icon: _jsx(Shield, { size: 15, "aria-hidden": true }), onPress: act(() => window.location.assign(link(`${iam()}/account/security`))), children: "Security" }), _jsx(Box, { height: 1, bg: "$borderColor", my: "$1" }), _jsx(Row, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), onPress: act(() => void logout()), children: "Sign out" })] })) : null, _jsxs(XStack, { items: "center", width: "100%", children: [_jsx(Box, { render: "button", onClick: () => setOpen((v) => !v), "aria-expanded": open, "aria-haspopup": "menu", "aria-label": "Account", flex: 1, minW: 0, px: "$3", py: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(Face, { src: face, name: called, size: 22 }), _jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, children: called }), email && email !== called ? (_jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 1, children: email })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true })] }) }), onToggleCollapse ? (_jsx(Box, { render: "button", onClick: onToggleCollapse, "aria-label": "Collapse sidebar", p: "$2", mr: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(PanelLeftClose, { size: 16, "aria-hidden": true }) })) : null] })] }));
|
|
68
65
|
}
|
package/dist/App.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
// The web app as the whole window, in
|
|
4
|
-
// product's AI
|
|
3
|
+
// The web app as the whole window, in three modes: Chat, a conversation with
|
|
4
|
+
// the product's AI; Dev, @hanzo/build's `<Builder>` without a rail of its own;
|
|
5
|
+
// and Studio, @hanzo/studio's `<Studio>` the same way, its places (`nav`) in the
|
|
6
|
+
// sidebar where Dev's stand for Dev.
|
|
5
7
|
//
|
|
6
8
|
// OUTER the rail (Rail.tsx) and the sidebar beside it (Column.tsx), on the
|
|
7
9
|
// window's floor, with the window's bar above them where the host has
|
|
@@ -14,11 +16,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
14
16
|
// search, New, Dev's places and the reader's Recents. The address says the
|
|
15
17
|
// mode, as a path (lib/host `talk`, `app`, `place`): `/` and `/chat` are Chat on
|
|
16
18
|
// a new conversation, `/chat/<id>` one of the reader's, and a path under `/dev`
|
|
17
|
-
// is Dev: `/dev/run/<id>` a run, `/dev/projects/<org>/<repo>` a project
|
|
19
|
+
// is Dev: `/dev/run/<id>` a run, `/dev/projects/<org>/<repo>` a project; a path
|
|
20
|
+
// under `/studio` is Studio, its own address after it (`c/<id>`, `library`). Moving
|
|
18
21
|
// between the modes is a history entry (`go`), never a load, so a host serves
|
|
19
22
|
// this one component for every path under each.
|
|
20
23
|
//
|
|
21
|
-
// Every host draws this: hanzo.ai at `/`, `/chat` and `/
|
|
24
|
+
// Every host draws this: hanzo.ai at `/`, `/chat`, `/dev` and `/studio`, and the desktop
|
|
22
25
|
// app as its main window. The host mounts IamProvider and `<Rooms>` with its
|
|
23
26
|
// router and the path, and draws this only for a signed-in reader; what a
|
|
24
27
|
// stranger sees is the host's.
|
|
@@ -46,13 +49,19 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
46
49
|
// EVERY MOVE PUTS THE DRAWER AWAY, the one to where the reader already is
|
|
47
50
|
// included.
|
|
48
51
|
//
|
|
52
|
+
// STUDIO LOADS WITH ITS MODE. Its module (and three.js behind a 3D tab) is
|
|
53
|
+
// fetched the first time the reader stands in Studio; its places are a few
|
|
54
|
+
// lines and come with the app. Another organization opens it at its New, so
|
|
55
|
+
// nothing it read under the last one stays.
|
|
56
|
+
//
|
|
49
57
|
// THE PLANS' OWN ADDRESS LEAVES for the pay site in its place (dev.tsx `leave`),
|
|
50
58
|
// so the builder's own plans are never drawn.
|
|
51
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
59
|
+
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
52
60
|
import { ArrowLeft, ArrowRight, Menu } from 'lucide-react';
|
|
53
61
|
import { AiProvider } from '@hanzo/ai/react';
|
|
54
62
|
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
55
63
|
import { Builder, HostProvider, path as written, route } from '@hanzo/build';
|
|
64
|
+
import { nav as places } from '@hanzo/studio';
|
|
56
65
|
import { SidebarIconButton } from '@hanzo/ui/chat';
|
|
57
66
|
import { useAi } from './lib/ai.js';
|
|
58
67
|
import { Chat } from './Chat.js';
|
|
@@ -66,8 +75,10 @@ import { empty, openThread, useOpen } from './open.js';
|
|
|
66
75
|
import { Paned } from './Shell.js';
|
|
67
76
|
import { floor, pane } from './ground.js';
|
|
68
77
|
import { pick } from './lib/session.js';
|
|
69
|
-
import { app, go, place, talk } from './lib/host.js';
|
|
78
|
+
import { app, go, place, studio, talk } from './lib/host.js';
|
|
70
79
|
import { wordmark } from './where.js';
|
|
80
|
+
/** Studio, fetched the first time the reader stands in it. */
|
|
81
|
+
const Studio = lazy(() => import('@hanzo/studio').then((m) => ({ default: m.Studio })));
|
|
71
82
|
/** `/` is Chat on a new conversation. */
|
|
72
83
|
const HOME = { mode: 'chat', thread: null };
|
|
73
84
|
/** The bar's height, px: the reference window's title bar. */
|
|
@@ -85,7 +96,7 @@ const INSET = {
|
|
|
85
96
|
left: 'max(var(--pane-gap), var(--safe-left))',
|
|
86
97
|
};
|
|
87
98
|
/** Where each mode was last, so the rail goes back to it. */
|
|
88
|
-
const last = { chat: '', dev: '' };
|
|
99
|
+
const last = { chat: '', dev: '', studio: '' };
|
|
89
100
|
/** Whether a key press is the reader typing, where the app's keys are the field's. */
|
|
90
101
|
const typing = (e) => {
|
|
91
102
|
const el = e.target;
|
|
@@ -119,6 +130,22 @@ function Window({ bar }) {
|
|
|
119
130
|
const at = place(here) ?? HOME;
|
|
120
131
|
const mode = at.mode;
|
|
121
132
|
const where = at.mode === 'dev' ? at.at : null;
|
|
133
|
+
// Studio's host: Dev's, at Studio's address, its moves under /studio, and
|
|
134
|
+
// another organization opening Studio's New in it.
|
|
135
|
+
const made = at.mode === 'studio' ? at.at : '';
|
|
136
|
+
const maker = useMemo(() => ({
|
|
137
|
+
...host,
|
|
138
|
+
name: 'Hanzo Studio',
|
|
139
|
+
path: made,
|
|
140
|
+
go: (to, how) => {
|
|
141
|
+
shut();
|
|
142
|
+
go(studio(to), how?.replace);
|
|
143
|
+
},
|
|
144
|
+
chooseOrg: (next) => {
|
|
145
|
+
pick(next);
|
|
146
|
+
window.location.assign(studio(''));
|
|
147
|
+
},
|
|
148
|
+
}), [host, made, shut]);
|
|
122
149
|
// A run floats two panes of its own, so the pane it stands in is not cut.
|
|
123
150
|
const run = where !== null && route(where).kind === 'run';
|
|
124
151
|
const asked = at.mode === 'chat' ? at.thread : null;
|
|
@@ -202,11 +229,13 @@ function Window({ bar }) {
|
|
|
202
229
|
}, [bar, router, move]);
|
|
203
230
|
const toChat = () => move(last.chat || talk());
|
|
204
231
|
const toDev = () => move(last.dev || app(''));
|
|
232
|
+
const toStudio = () => move(last.studio || studio(''));
|
|
205
233
|
// Another organization: picked, and the app opened in it at the mode's New.
|
|
206
234
|
const org = (o) => {
|
|
207
235
|
pick(o);
|
|
208
|
-
window.location.assign(mode === 'dev' ? app('') : talk());
|
|
236
|
+
window.location.assign(mode === 'dev' ? app('') : mode === 'studio' ? studio('') : talk());
|
|
209
237
|
};
|
|
238
|
+
const sidebar = mode === 'studio' ? places(maker, (p) => maker.go(p)) : undefined;
|
|
210
239
|
const flip = () => setClosed(!closed);
|
|
211
|
-
return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsxs(YStack, { "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer, onPress: () => setDrawer(true), label: "Open sidebar" }) }), _jsx(Mark, { label: "Back", "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Forward", "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), flex: 1, minW: 0, minH: 0, pr: "var(--pane-gap)", pb: "var(--pane-gap)", pt: bar ? 0 : 'var(--pane-gap)', "$max-md": { p: 0 }, children: [_jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, at: where, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, go: move, onOrg: org }) }), closed ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), seek: seek }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, ml: "var(--pane-gap)", "$max-md": { ml: 0 }, children: [bar ? null : (_jsxs(XStack, { "data-slot": "app-bar", height: 44, px: "$1", gap: "$1", items: "center", shrink: 0, $md: { display: 'none' }, children: [_jsx(SidebarIconButton, { label: "Open sidebar", onPress: () => setDrawer(true), width: 44, height: 44, rounded: 999, children: _jsx(Menu, { size: 18 }) }), _jsx(Text, { fontSize: "$4", fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) })] })), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer ? (_jsxs(_Fragment, { children: [_jsx(YStack, { render: "button", "aria-label": "Close sidebar", onPress: () => setDrawer(false), bg: DIM, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-overlay)", $md: { display: 'none' } }), _jsx(YStack, { "data-slot": "drawer", position: "absolute", t: INSET.top, b: INSET.bottom, l: INSET.left, width: "min(320px, calc(100% - 2 * var(--pane-gap) - 48px))", z: "var(--z-modal)", $md: { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, seek: seek, drawer: true, foot: _jsx(Rail, { host: host, mode: mode, at: where, open: true, flat: true, onChat: toChat, onDev: toDev, go: move, onOrg: org }) }) })] })) : null] }) }) }));
|
|
240
|
+
return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsxs(YStack, { "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer, onPress: () => setDrawer(true), label: "Open sidebar" }) }), _jsx(Mark, { label: "Back", "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Forward", "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), flex: 1, minW: 0, minH: 0, pr: "var(--pane-gap)", pb: "var(--pane-gap)", pt: bar ? 0 : 'var(--pane-gap)', "$max-md": { p: 0 }, children: [_jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, at: where, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, onStudio: toStudio, go: move, onOrg: org }) }), closed ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), seek: seek, places: sidebar }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, ml: "var(--pane-gap)", "$max-md": { ml: 0 }, children: [bar ? null : (_jsxs(XStack, { "data-slot": "app-bar", height: 44, px: "$1", gap: "$1", items: "center", shrink: 0, $md: { display: 'none' }, children: [_jsx(SidebarIconButton, { label: "Open sidebar", onPress: () => setDrawer(true), width: 44, height: 44, rounded: 999, children: _jsx(Menu, { size: 18 }) }), _jsx(Text, { fontSize: "$4", fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) })] })), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : mode === 'studio' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(YStack, { flex: 1, minH: 0, height: "100%", children: _jsx(Suspense, { fallback: null, children: _jsx(Studio, { host: maker, rail: false }) }) }) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer ? (_jsxs(_Fragment, { children: [_jsx(YStack, { render: "button", "aria-label": "Close sidebar", onPress: () => setDrawer(false), bg: DIM, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-overlay)", $md: { display: 'none' } }), _jsx(YStack, { "data-slot": "drawer", position: "absolute", t: INSET.top, b: INSET.bottom, l: INSET.left, width: "min(320px, calc(100% - 2 * var(--pane-gap) - 48px))", z: "var(--z-modal)", $md: { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, seek: seek, drawer: true, places: sidebar, foot: _jsx(Rail, { host: host, mode: mode, at: where, open: true, flat: true, onChat: toChat, onDev: toDev, onStudio: toStudio, go: move, onOrg: org }) }) })] })) : null] }) }) }));
|
|
212
241
|
}
|
package/dist/Column.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type Host } from '@hanzo/build';
|
|
2
|
+
import { type RailLink } from '@hanzo/ui/chat';
|
|
2
3
|
import { type ReactNode } from 'react';
|
|
3
|
-
import { type
|
|
4
|
-
export declare function Column({ host, mode, thread, go, onShut, seek, drawer, foot, }: {
|
|
4
|
+
import { type Place } from './lib/host.js';
|
|
5
|
+
export declare function Column({ host, mode, thread, go, onShut, seek, drawer, foot, places, }: {
|
|
5
6
|
host: Host;
|
|
6
|
-
mode:
|
|
7
|
+
mode: Place['mode'];
|
|
7
8
|
/** The Chat conversation the pane holds, or null. */
|
|
8
9
|
thread: string | null;
|
|
9
10
|
/** Moves the app to an address of this page. */
|
|
@@ -16,4 +17,10 @@ export declare function Column({ host, mode, thread, go, onShut, seek, drawer, f
|
|
|
16
17
|
drawer?: boolean;
|
|
17
18
|
/** What stands at its foot: the drawer's rail, laid flat. */
|
|
18
19
|
foot?: ReactNode;
|
|
20
|
+
/** The mode's places, where they are not Dev's (Studio's `nav`). */
|
|
21
|
+
places?: {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
links: RailLink[];
|
|
25
|
+
}[];
|
|
19
26
|
}): import("react").JSX.Element;
|
package/dist/Column.js
CHANGED
|
@@ -89,7 +89,7 @@ function Activity({ host }) {
|
|
|
89
89
|
];
|
|
90
90
|
return (_jsx(DropdownMenu, { minWidth: 280, trigger: _jsx(XStack, { render: "button", "aria-label": "Activity", ...{ title: 'Activity' }, ...HEAD, items: "center", justify: "center", cursor: "pointer", opacity: 0.7, hoverStyle: { bg: '$hover', opacity: 1 }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: _jsx(Bell, { size: 16, "aria-hidden": true }) }), items: items }));
|
|
91
91
|
}
|
|
92
|
-
export function Column({ host, mode, thread, go, onShut, seek = 0, drawer = false, foot, }) {
|
|
92
|
+
export function Column({ host, mode, thread, go, onShut, seek = 0, drawer = false, foot, places, }) {
|
|
93
93
|
const [words, setWords] = useState('');
|
|
94
94
|
// The search field shows once asked for, and while it holds words.
|
|
95
95
|
const [seeking, setSeeking] = useState(false);
|
|
@@ -98,7 +98,7 @@ export function Column({ host, mode, thread, go, onShut, seek = 0, drawer = fals
|
|
|
98
98
|
const uid = useId();
|
|
99
99
|
const span = useSpan(SPAN, WIDTH, FLOOR, CEIL);
|
|
100
100
|
const dev = (p) => host.go(p);
|
|
101
|
-
const groups = nav(host, dev);
|
|
101
|
+
const groups = places ?? nav(host, dev);
|
|
102
102
|
const open = (row) => (row.kind === 'chat' ? go(talk(row.id)) : dev(row.id));
|
|
103
103
|
// Chat's New; Dev's is the first of its places.
|
|
104
104
|
const create = () => {
|
|
@@ -119,13 +119,13 @@ export function Column({ host, mode, thread, go, onShut, seek = 0, drawer = fals
|
|
|
119
119
|
}, [seek]);
|
|
120
120
|
return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
|
|
121
121
|
// inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
|
|
122
|
-
style: drawer ? undefined : span.style, position: "relative", flex: drawer ? 1 : undefined, shrink: drawer ? 1 : 0, minW: 0, height: "100%", width: drawer ? undefined : 'var(--span)', children: [_jsxs(Sidebar, { "data-slot": "app-rail", role: "navigation", "aria-label": brand(), ...(drawer ? { ...pane(), ...FLOAT } : { bg: 'transparent', borderWidth: 0 }), ...FILL, px: "$2", pt: drawer ? '$2' : 0, pb: "$2", gap: "$0", children: [_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: "$2", pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) }), _jsx(Activity, { host: host }), _jsx(SidebarIconButton, { label: mode === 'chat' ? 'Search chats' : 'Search runs', onPress: seeking ? done : ask, "aria-expanded": seeking || !!words, ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) })] }), seeking || words ? (_jsxs(XStack, { "data-slot": "app-filter", items: "center", gap: "$2", pl: "$3", pr: "$1", height: 36, rounded: 999, borderWidth: 1, borderColor: "$borderColor", bg: "$edge", shrink: 0, mb: "$1.5", children: [_jsx(Search, { size: 15, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { render: _jsx("input", { ref: field, value: words, onChange: (e) => setWords(e.target.value), onKeyDown: (e) => {
|
|
122
|
+
style: drawer ? undefined : span.style, position: "relative", flex: drawer ? 1 : undefined, shrink: drawer ? 1 : 0, minW: 0, height: "100%", width: drawer ? undefined : 'var(--span)', children: [_jsxs(Sidebar, { "data-slot": "app-rail", role: "navigation", "aria-label": brand(), ...(drawer ? { ...pane(), ...FLOAT } : { bg: 'transparent', borderWidth: 0 }), ...FILL, px: "$2", pt: drawer ? '$2' : 0, pb: "$2", gap: "$0", children: [_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: "$2", pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) }), _jsx(Activity, { host: host }), mode === 'studio' ? null : (_jsx(SidebarIconButton, { label: mode === 'chat' ? 'Search chats' : 'Search runs', onPress: seeking ? done : ask, "aria-expanded": seeking || !!words, ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) }))] }), seeking || words ? (_jsxs(XStack, { "data-slot": "app-filter", items: "center", gap: "$2", pl: "$3", pr: "$1", height: 36, rounded: 999, borderWidth: 1, borderColor: "$borderColor", bg: "$edge", shrink: 0, mb: "$1.5", children: [_jsx(Search, { size: 15, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { render: _jsx("input", { ref: field, value: words, onChange: (e) => setWords(e.target.value), onKeyDown: (e) => {
|
|
123
123
|
if (e.key === 'Escape')
|
|
124
124
|
done();
|
|
125
125
|
}, onBlur: () => {
|
|
126
126
|
if (!words)
|
|
127
127
|
setSeeking(false);
|
|
128
|
-
}, placeholder: mode === 'chat' ? 'Search chats' : 'Search runs', "aria-label": "Filter recents" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$3" }), words ? (_jsx(SidebarIconButton, { label: "Clear search", onPress: done, width: 28, height: 28, rounded: 999, children: _jsx(X, { size: 14, "aria-hidden": true }) })) : null] })) : null, mode === 'chat' ? (_jsx(YStack, { role: "list", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, bg: "transparent", hoverStyle: { bg: '$hover' }, icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "New chat" }) }) }) })) : (_jsx(YStack, { "data-slot": "app-places", shrink: 0, children: groups.map((g, i) => (_jsxs(YStack, { pt: i ? '$2' : 0, children: [i ? (_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$3", color: "$soft", children: g.label })) : null, _jsx(YStack, { role: "list", "aria-labelledby": i ? `${uid}-${g.id}` : undefined, "aria-label": i ? undefined : g.label, gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.id === NEW ? _jsx(SquarePen, { size: 16, "aria-hidden": true }) : link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: _jsx(Text, { fontSize: "$3", color: link.active ? '$ink' : '$quiet', children: link.label }) }) }, link.id))) })] }, g.id))) })), _jsx(Recents, { host: host, mode: mode, thread: thread, words: words, onOpen: open, onNew: create }), _jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] }), foot] }), drawer ? null : (_jsx(Grip, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: "Resize sidebar",
|
|
128
|
+
}, placeholder: mode === 'chat' ? 'Search chats' : 'Search runs', "aria-label": "Filter recents" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$3" }), words ? (_jsx(SidebarIconButton, { label: "Clear search", onPress: done, width: 28, height: 28, rounded: 999, children: _jsx(X, { size: 14, "aria-hidden": true }) })) : null] })) : null, mode === 'chat' ? (_jsx(YStack, { role: "list", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, bg: "transparent", hoverStyle: { bg: '$hover' }, icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "New chat" }) }) }) })) : (_jsx(YStack, { "data-slot": "app-places", shrink: 0, children: groups.map((g, i) => (_jsxs(YStack, { pt: i ? '$2' : 0, children: [i ? (_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$3", color: "$soft", children: g.label })) : null, _jsx(YStack, { role: "list", "aria-labelledby": i ? `${uid}-${g.id}` : undefined, "aria-label": i ? undefined : g.label, gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.id === NEW ? _jsx(SquarePen, { size: 16, "aria-hidden": true }) : link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: _jsx(Text, { fontSize: "$3", color: link.active ? '$ink' : '$quiet', children: link.label }) }) }, link.id))) })] }, g.id))) })), mode === 'studio' ? null : _jsx(Recents, { host: host, mode: mode, thread: thread, words: words, onOpen: open, onNew: create }), _jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] }), foot] }), drawer ? null : (_jsx(Grip, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: "Resize sidebar",
|
|
129
129
|
// In the gutter between the sidebar and the panel, not over the sidebar's own rows.
|
|
130
130
|
r: GUTTER_END, width: "var(--pane-gap)" }))] }));
|
|
131
131
|
}
|
package/dist/Rail.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { type Host } from '@hanzo/build';
|
|
3
|
-
import type
|
|
3
|
+
import { type Place } from './lib/host.js';
|
|
4
4
|
/** The rail's width, px. */
|
|
5
5
|
export declare const RAIL = 48;
|
|
6
6
|
/** One mark: an icon button named for where it goes, its name its tooltip, lit where the reader is. */
|
|
@@ -18,9 +18,9 @@ export declare function Toggle({ open, onPress, label }: {
|
|
|
18
18
|
onPress: () => void;
|
|
19
19
|
label?: string;
|
|
20
20
|
}): import("react").JSX.Element;
|
|
21
|
-
export declare function Rail({ host, mode, at, open, toggle, flat, onChat, onDev, go, onOrg, }: {
|
|
21
|
+
export declare function Rail({ host, mode, at, open, toggle, flat, onChat, onDev, onStudio, go, onOrg, }: {
|
|
22
22
|
host: Host;
|
|
23
|
-
mode:
|
|
23
|
+
mode: Place['mode'];
|
|
24
24
|
/** Dev's builder address, or null in Chat. */
|
|
25
25
|
at: string | null;
|
|
26
26
|
/** The sidebar beside the rail is open. */
|
|
@@ -29,9 +29,10 @@ export declare function Rail({ host, mode, at, open, toggle, flat, onChat, onDev
|
|
|
29
29
|
toggle?: () => void;
|
|
30
30
|
/** Laid along the foot of the phone's drawer rather than down the window's edge. */
|
|
31
31
|
flat?: boolean;
|
|
32
|
-
/** To Chat and
|
|
32
|
+
/** To Chat, Dev and Studio, each where the reader left it. */
|
|
33
33
|
onChat: () => void;
|
|
34
34
|
onDev: () => void;
|
|
35
|
+
onStudio: () => void;
|
|
35
36
|
/** Moves the app to an address of this page. */
|
|
36
37
|
go: (address: string) => void;
|
|
37
38
|
/** Work in another organization. */
|
package/dist/Rail.js
CHANGED
|
@@ -5,8 +5,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
5
5
|
// menu of the rest, and the account at the foot. It is how the app moves
|
|
6
6
|
// between its products; there is no switch anywhere else.
|
|
7
7
|
//
|
|
8
|
-
// Studio is
|
|
9
|
-
//
|
|
8
|
+
// Studio is the app's third mode (@hanzo/studio at /studio), each of the three
|
|
9
|
+
// opened where the reader left it.
|
|
10
10
|
//
|
|
11
11
|
// Library, Scheduled tasks and Customize are Dev's own places (@hanzo/build's
|
|
12
12
|
// Artifacts, Automations and Customize), raised to the rail; each is lit while
|
|
@@ -27,8 +27,7 @@ import { XStack, YStack } from '@hanzo/gui';
|
|
|
27
27
|
import { route } from '@hanzo/build';
|
|
28
28
|
import { DropdownMenu } from '@hanzo/ui';
|
|
29
29
|
import { Me } from './Me.js';
|
|
30
|
-
import { app,
|
|
31
|
-
import { site } from './where.js';
|
|
30
|
+
import { app, TABS, WORKSPACE } from './lib/host.js';
|
|
32
31
|
/** The rail's width, px. */
|
|
33
32
|
export const RAIL = 48;
|
|
34
33
|
/** A mark, px: a 36px square on a 44px pitch. */
|
|
@@ -72,7 +71,7 @@ export function Mark({ label, on = false, onPress, children, ...rest }) {
|
|
|
72
71
|
export function Toggle({ open, onPress, label }) {
|
|
73
72
|
return (_jsx(Mark, { label: label ?? (open ? 'Close sidebar' : 'Open sidebar'), "data-slot": "sidebar-toggle", "aria-expanded": open, onPress: onPress, children: _jsx(PanelLeft, { size: 18, "aria-hidden": true }) }));
|
|
74
73
|
}
|
|
75
|
-
export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev, go, onOrg, }) {
|
|
74
|
+
export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev, onStudio, go, onOrg, }) {
|
|
76
75
|
const here = mode === 'dev' ? raised(at) : null;
|
|
77
76
|
const to = (p) => () => go(app(p));
|
|
78
77
|
// Everything else the app reaches, behind More: on a phone the raised places first, then Dev's other places, then the products on hosts of their own.
|
|
@@ -95,5 +94,5 @@ export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev
|
|
|
95
94
|
{ key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) },
|
|
96
95
|
];
|
|
97
96
|
const Stack = flat ? XStack : YStack;
|
|
98
|
-
return (_jsxs(Stack, { "data-slot": "rail", render: "nav", "aria-label": "Products", ...(flat ? { pt: '$2', mt: '$2', borderTopWidth: 1, borderColor: '$borderColor', gap: '$1' } : { width: RAIL, flex: 1, pt: '$2', pb: '$2', gap: 8 }), shrink: 0, items: "center", children: [toggle ? (_jsx(YStack, { pb: 8, children: _jsx(Toggle, { open: open, onPress: toggle }) })) : null, _jsx(Mark, { label: "Chat", on: mode === 'chat', onPress: onChat, "data-slot": "rail-chat", children: _jsx(MessageSquare, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Dev", on: mode === 'dev' && !here, onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Studio",
|
|
97
|
+
return (_jsxs(Stack, { "data-slot": "rail", render: "nav", "aria-label": "Products", ...(flat ? { pt: '$2', mt: '$2', borderTopWidth: 1, borderColor: '$borderColor', gap: '$1' } : { width: RAIL, flex: 1, pt: '$2', pb: '$2', gap: 8 }), shrink: 0, items: "center", children: [toggle ? (_jsx(YStack, { pb: 8, children: _jsx(Toggle, { open: open, onPress: toggle }) })) : null, _jsx(Mark, { label: "Chat", on: mode === 'chat', onPress: onChat, "data-slot": "rail-chat", children: _jsx(MessageSquare, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Dev", on: mode === 'dev' && !here, onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Studio", on: mode === 'studio', onPress: onStudio, "data-slot": "rail-studio", children: _jsx(WandSparkles, { size: 18, "aria-hidden": true }) }), flat ? null : (_jsxs(_Fragment, { children: [_jsx(Mark, { label: "Library", on: here === 'library', onPress: to(RAISED.library), "data-slot": "rail-library", children: _jsx(Library, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Scheduled tasks", on: here === 'scheduled', onPress: to(RAISED.scheduled), "data-slot": "rail-scheduled", children: _jsx(Clock, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Customize", on: here === 'customize', onPress: to(RAISED.customize), "data-slot": "rail-customize", children: _jsx(Puzzle, { size: 18, "aria-hidden": true }) })] })), _jsx(DropdownMenu, { placement: flat ? 'top-start' : 'right-start', minWidth: 220, trigger: _jsx(XStack, { render: "button", "aria-label": "More", ...{ title: 'More' }, "data-slot": "rail-more", width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", cursor: "pointer", opacity: 0.62, hoverStyle: { bg: '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, $touchable: { width: 44, height: 44 }, children: _jsx(Ellipsis, { size: 18, "aria-hidden": true }) }), items: rest }), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, above: flat, onOrg: onOrg })] }));
|
|
99
98
|
}
|
package/dist/Settings.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type Row = {
|
|
|
12
12
|
/** One sentence, for a pane we have not built a screen for yet. */
|
|
13
13
|
note?: string;
|
|
14
14
|
};
|
|
15
|
+
/** Every row, flat — what the pane looks a chosen id up in. */
|
|
16
|
+
export declare const ROWS: Row[];
|
|
15
17
|
/** The row Settings opens on when nothing has asked for another. */
|
|
16
18
|
export declare const FIRST: string;
|
|
17
19
|
/**
|
|
@@ -34,13 +36,3 @@ export declare function SettingsList({ active, filter, onPick, onBack, }: {
|
|
|
34
36
|
/** Back to the rooms. */
|
|
35
37
|
onBack: () => void;
|
|
36
38
|
}): import("react").JSX.Element;
|
|
37
|
-
/**
|
|
38
|
-
* The room, while a settings row is open.
|
|
39
|
-
*
|
|
40
|
-
* It scrolls itself, the way the column beside a room does: `main` gives its
|
|
41
|
-
* child a floor and no overflow, so a pane that did not would push the whole
|
|
42
|
-
* shell down the page.
|
|
43
|
-
*/
|
|
44
|
-
export declare function SettingsPane({ id }: {
|
|
45
|
-
id: string;
|
|
46
|
-
}): import("react").JSX.Element;
|
package/dist/Settings.js
CHANGED
|
@@ -3,8 +3,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
// Settings: the sidebar's second level, and the pane it opens in the room.
|
|
4
4
|
//
|
|
5
5
|
// TWO PARTS, ONE TAXONOMY. `SettingsList` is the rail's list — the shell swaps
|
|
6
|
-
// its own rows for these and back. `SettingsPane` is what
|
|
7
|
-
// a row is chosen
|
|
6
|
+
// its own rows for these and back. `SettingsPane` (SettingsPane.tsx) is what
|
|
7
|
+
// fills the room while a row is chosen, loaded only then: it carries the plan,
|
|
8
|
+
// the usage panel, the model picker and the catalog, none of which a room's
|
|
9
|
+
// first screen draws. Both read `GROUPS`, so the list can only offer a row the
|
|
8
10
|
// pane can draw.
|
|
9
11
|
//
|
|
10
12
|
// Which row is open is the shell's `showSettings` value and nothing here: a
|
|
@@ -22,31 +24,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
22
24
|
// The panes that are not yet built say so in one sentence rather than pretending
|
|
23
25
|
// to a screen. An honest empty pane is a smaller lie than an invented one.
|
|
24
26
|
import { app, dev } from './lib/host.js';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { Box, Button, Separator, Text, XStack, YStack, } from '@hanzo/ui';
|
|
27
|
+
import { ArrowLeft, ArrowUpRight } from 'lucide-react';
|
|
28
|
+
import { Text } from '@hanzo/ui';
|
|
28
29
|
import { SidebarItem, SidebarSection } from '@hanzo/ui/chat';
|
|
29
|
-
import { tap } from './lib/tap.js';
|
|
30
|
-
import { useIam, useIamIdentity, useOrganizations } from '@hanzo/iam/react';
|
|
31
|
-
import { useSignOut } from './lib/signout.js';
|
|
32
|
-
import { useModels } from '@hanzo/ai/react';
|
|
33
|
-
import { ModelPicker } from '@hanzo/ui/models';
|
|
34
|
-
import { parseModels } from '@hanzo/ui/models/catalog';
|
|
35
|
-
import { UsagePanel } from '@hanzo/usage/panel';
|
|
36
|
-
import { administers, Credits, label, Plan, Title, useChoice } from '@hanzo/build';
|
|
37
|
-
import { Catalog } from './Directory.js';
|
|
38
|
-
import { payPage } from './lib/pay.js';
|
|
39
|
-
import { api } from './lib/api.js';
|
|
40
|
-
import { renewal, useSubscription, useTier } from './lib/tier.js';
|
|
41
|
-
import { spendShown, useLimits } from './lib/limits.js';
|
|
42
|
-
import { showSettings } from './open.js';
|
|
43
|
-
import { Look } from './look.js';
|
|
44
|
-
import { ProviderMark } from './ProviderMark.js';
|
|
45
|
-
import { bearer, org, pick } from './lib/session.js';
|
|
46
30
|
import { site } from './where.js';
|
|
47
|
-
/** A choice among a few: a hairline edge and a corner. The fill and the edge's
|
|
48
|
-
* ink are the call site's, because they say which one is chosen. */
|
|
49
|
-
const CHOICE = { rounded: 8, borderWidth: 1, borderStyle: 'solid', cursor: 'pointer' };
|
|
50
31
|
const GROUPS = [
|
|
51
32
|
{
|
|
52
33
|
label: 'Settings',
|
|
@@ -109,7 +90,7 @@ const GROUPS = [
|
|
|
109
90
|
},
|
|
110
91
|
];
|
|
111
92
|
/** Every row, flat — what the pane looks a chosen id up in. */
|
|
112
|
-
const ROWS = GROUPS.flatMap((g) => g.rows);
|
|
93
|
+
export const ROWS = GROUPS.flatMap((g) => g.rows);
|
|
113
94
|
/** The row Settings opens on when nothing has asked for another. */
|
|
114
95
|
export const FIRST = ROWS[0].id;
|
|
115
96
|
/**
|
|
@@ -133,196 +114,3 @@ export function SettingsList({ active, filter, onPick, onBack, }) {
|
|
|
133
114
|
// pressed.
|
|
134
115
|
icon: r.href ? _jsx(ArrowUpRight, { size: 13, "aria-hidden": true }) : undefined, active: r.id === active, "aria-current": r.id === active ? 'true' : undefined, onPress: () => onPick(r), children: r.label }, r.id))) }, g.label)))] }));
|
|
135
116
|
}
|
|
136
|
-
/**
|
|
137
|
-
* The room, while a settings row is open.
|
|
138
|
-
*
|
|
139
|
-
* It scrolls itself, the way the column beside a room does: `main` gives its
|
|
140
|
-
* child a floor and no overflow, so a pane that did not would push the whole
|
|
141
|
-
* shell down the page.
|
|
142
|
-
*/
|
|
143
|
-
export function SettingsPane({ id }) {
|
|
144
|
-
const row = ROWS.find((r) => r.id === id);
|
|
145
|
-
return (_jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", children: _jsxs(YStack, { p: "$5", gap: "$4", maxW: 860, children: [_jsx(Text, { fontSize: "$6", fontWeight: "600", color: "$ink", children: row?.label ?? 'Settings' }), _jsx(Pane, { id: id, note: row?.note })] }) }));
|
|
146
|
-
}
|
|
147
|
-
/** One pane. */
|
|
148
|
-
function Pane({ id, note }) {
|
|
149
|
-
if (id === 'apps' || id === 'channels')
|
|
150
|
-
return _jsx(Catalog, { tab: id });
|
|
151
|
-
if (id === 'general')
|
|
152
|
-
return _jsx(Look, {});
|
|
153
|
-
if (id === 'account')
|
|
154
|
-
return _jsx(Account, {});
|
|
155
|
-
if (id === 'billing')
|
|
156
|
-
return _jsx(Billing, {});
|
|
157
|
-
if (id === 'usage')
|
|
158
|
-
return _jsx(Usage, {});
|
|
159
|
-
if (id === 'model')
|
|
160
|
-
return _jsx(Model, {});
|
|
161
|
-
if (id === 'models')
|
|
162
|
-
return _jsx(Served, {});
|
|
163
|
-
if (id === 'agent')
|
|
164
|
-
return _jsx(AgentSettings, {});
|
|
165
|
-
if (id === 'capabilities')
|
|
166
|
-
return _jsx(Capabilities, {});
|
|
167
|
-
if (id === 'memory')
|
|
168
|
-
return _jsx(MemorySettings, {});
|
|
169
|
-
return (_jsx(Text, { fontSize: "$3", color: "$soft", children: note ?? 'Nothing to configure here yet.' }));
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* One default in one place; agents may still bring their own model. A premium
|
|
173
|
-
* model is never a default: it is picked per conversation, in the composer, so
|
|
174
|
-
* this picker offers the rest of the catalog.
|
|
175
|
-
*/
|
|
176
|
-
function Model() {
|
|
177
|
-
const { models: served } = useModels();
|
|
178
|
-
const models = useMemo(() => parseModels(served).filter((m) => m.class !== 'premium'), [served]);
|
|
179
|
-
// The one choice Chat and Dev share (@hanzo/build): changed here, changed in both composers.
|
|
180
|
-
const [choice, choose] = useChoice();
|
|
181
|
-
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" }) })] }));
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* WHAT THE GATEWAY SERVES, read from the same `/v1/models` the rooms read.
|
|
185
|
-
*
|
|
186
|
-
* Counted, never typed: the catalog moves without this file, so a number written
|
|
187
|
-
* here is wrong by the next deploy. An empty answer says the account reaches
|
|
188
|
-
* nothing rather than drawing a zero, and the mark comes from the model's own
|
|
189
|
-
* id so a family wears the logo of the lab that makes it.
|
|
190
|
-
*/
|
|
191
|
-
function Served() {
|
|
192
|
-
const { models, loading, error } = useModels();
|
|
193
|
-
if (loading) {
|
|
194
|
-
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Reading the catalog." });
|
|
195
|
-
}
|
|
196
|
-
if (error) {
|
|
197
|
-
return _jsxs(Text, { fontSize: "$3", color: "$soft", children: ["The catalog did not answer: ", error.message] });
|
|
198
|
-
}
|
|
199
|
-
if (models.length === 0) {
|
|
200
|
-
return (_jsx(Text, { fontSize: "$3", color: "$soft", children: "No model answers on this account. Sign in to reach the ones your org opens." }));
|
|
201
|
-
}
|
|
202
|
-
return (_jsxs(YStack, { gap: "$3", children: [_jsxs(Text, { fontSize: "$3", color: "$soft", children: [models.length, " models answer at /v1/models on this account."] }), _jsx(YStack, { children: models.map((m) => (_jsxs(XStack, { items: "center", gap: "$2.5", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(ProviderMark, { model: m.id, org: m.owned_by, size: 16, ink: true }), _jsx(Text, { fontSize: "$2", color: "$ink", flex: 1, children: m.id }), m.owned_by ? _jsx(Text, { fontSize: "$1", color: "$soft", children: m.owned_by }) : null] }, m.id))) }), _jsx(XStack, { children: _jsx(Box, { render: _jsx("a", { href: site('/models') }), ...tap, children: _jsxs(XStack, { items: "center", gap: "$2", py: "$2", children: [_jsx(Text, { fontSize: "$3", color: "$ink", children: "Browse the model catalog" }), _jsx(ArrowUpRight, { size: 13, "aria-hidden": true })] }) }) })] }));
|
|
203
|
-
}
|
|
204
|
-
/** Who is signed in, and where they can act. */
|
|
205
|
-
function Account() {
|
|
206
|
-
const identity = useIamIdentity();
|
|
207
|
-
const { isAuthenticated } = useIam();
|
|
208
|
-
const logout = useSignOut();
|
|
209
|
-
const { organizations, currentOrgId, currentRole, switchOrg } = useOrganizations();
|
|
210
|
-
if (!isAuthenticated || !identity) {
|
|
211
|
-
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see your account." });
|
|
212
|
-
}
|
|
213
|
-
return (_jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsx(YStack, { width: 44, height: 44, rounded: 22, bg: "$borderColor", items: "center", justify: "center", children: _jsx(Text, { fontSize: "$3", color: "$ink", children: identity.initials }) }), _jsxs(YStack, { gap: "$1", flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$4", color: "$ink", children: identity.name }), identity.email ? _jsx(Text, { fontSize: "$2", color: "$soft", children: identity.email }) : null] })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: "$2", color: "$soft", children: "Organizations" }), organizations.length === 0 ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "You are not a member of an organization." })) : (organizations.map((o) => {
|
|
214
|
-
const here = o.name === currentOrgId;
|
|
215
|
-
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));
|
|
216
|
-
}))] }), _jsx(Separator, {}), _jsx(XStack, { children: _jsx(Button, { size: "sm", onClick: () => logout(), children: "Sign out" }) })] }));
|
|
217
|
-
}
|
|
218
|
-
/** Where the plan's and billing's reads go: this browser's bearer, in its organization. */
|
|
219
|
-
function useTarget() {
|
|
220
|
-
const scoped = org();
|
|
221
|
-
return useMemo(() => ({ api: api(), token: bearer, org: scoped }), [scoped]);
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* The plan and its term, then the credits — @hanzo/build's `Credits`, the one
|
|
225
|
-
* view every Billing page draws: the balance, grants, auto-reload, this month's
|
|
226
|
-
* spend and its limit, said to be separate from the plan's usage. Money moves on
|
|
227
|
-
* the payment host, so adding funds and a card both open it.
|
|
228
|
-
*/
|
|
229
|
-
function Billing() {
|
|
230
|
-
const { isAuthenticated } = useIam();
|
|
231
|
-
const t = useTarget();
|
|
232
|
-
const { tier } = useTier(isAuthenticated, org());
|
|
233
|
-
const sub = useSubscription(isAuthenticated, org());
|
|
234
|
-
if (!isAuthenticated)
|
|
235
|
-
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see billing." });
|
|
236
|
-
// WHAT THE CUSTOMER BOUGHT, by its family: `Max` with `20x` beside it.
|
|
237
|
-
const plan = tier ? label(tier.plan, tier.tier.name) ?? { name: 'Free', tag: '' } : null;
|
|
238
|
-
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 })] }) }) })] })] }));
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Usage: @hanzo/build's `Plan`, the one view every Usage page draws — the plan's
|
|
242
|
-
* windows and each class of model's share, as shares; the free allowance on
|
|
243
|
-
* Free; the balance with no plan — and the choice of paying from credits once
|
|
244
|
-
* the allowance runs out. A reader with no paid plan also sees what was spent,
|
|
245
|
-
* over time (@hanzo/usage), because money is their meter.
|
|
246
|
-
*/
|
|
247
|
-
function Usage() {
|
|
248
|
-
const { accessToken, isAuthenticated } = useIam();
|
|
249
|
-
const t = useTarget();
|
|
250
|
-
const read = useLimits(isAuthenticated, org());
|
|
251
|
-
if (!isAuthenticated || !accessToken) {
|
|
252
|
-
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see your usage." });
|
|
253
|
-
}
|
|
254
|
-
const plan = _jsx(Plan, { target: t, onPlans: () => window.location.assign(site('/pricing')), onBilling: () => showSettings('billing') });
|
|
255
|
-
if (!spendShown(read))
|
|
256
|
-
return plan;
|
|
257
|
-
// The address is read HERE rather than at import: the panel sends the bearer
|
|
258
|
-
// with every read, and a credentialed read to an absolute address from a page
|
|
259
|
-
// the gateway does not admit dies in preflight — which drew this panel as an
|
|
260
|
-
// account that had spent nothing.
|
|
261
|
-
return (_jsxs(YStack, { gap: "$6", children: [plan, _jsx(UsagePanel, { baseUrl: api(), token: accessToken, upgrade: payPage(), title: "Spend" })] }));
|
|
262
|
-
}
|
|
263
|
-
const HISTORICAL_PERSONAS = [
|
|
264
|
-
{ id: 'architect', name: 'Software Architect', role: 'Full-stack & systems design' },
|
|
265
|
-
{ id: 'satoshi', name: 'Satoshi Nakamoto', role: 'Distributed cryptography & consensus' },
|
|
266
|
-
{ id: 'turing', name: 'Alan Turing', role: 'Algorithmic logic & formal verification' },
|
|
267
|
-
{ id: 'ada', name: 'Ada Lovelace', role: 'Computational precision & analysis' },
|
|
268
|
-
{ id: 'davinci', name: 'Leonardo da Vinci', role: 'Polymathic engineering & design' },
|
|
269
|
-
];
|
|
270
|
-
function AgentSettings() {
|
|
271
|
-
const [instructions, setInstructions] = useState(() => {
|
|
272
|
-
if (typeof window === 'undefined')
|
|
273
|
-
return '';
|
|
274
|
-
return localStorage.getItem('hanzo.agent.instructions') || 'You are an autonomous AI software engineer at Hanzo. Think step-by-step and write robust code.';
|
|
275
|
-
});
|
|
276
|
-
const [persona, setPersona] = useState(() => {
|
|
277
|
-
if (typeof window === 'undefined')
|
|
278
|
-
return 'architect';
|
|
279
|
-
return localStorage.getItem('hanzo.agent.persona') || 'architect';
|
|
280
|
-
});
|
|
281
|
-
const [autonomy, setAutonomy] = useState(() => {
|
|
282
|
-
if (typeof window === 'undefined')
|
|
283
|
-
return 'autonomous';
|
|
284
|
-
return localStorage.getItem('hanzo.agent.autonomy') || 'autonomous';
|
|
285
|
-
});
|
|
286
|
-
const [saved, setSaved] = useState(false);
|
|
287
|
-
const save = () => {
|
|
288
|
-
localStorage.setItem('hanzo.agent.instructions', instructions);
|
|
289
|
-
localStorage.setItem('hanzo.agent.persona', persona);
|
|
290
|
-
localStorage.setItem('hanzo.agent.autonomy', autonomy);
|
|
291
|
-
setSaved(true);
|
|
292
|
-
setTimeout(() => setSaved(false), 2000);
|
|
293
|
-
};
|
|
294
|
-
return (_jsxs(YStack, { gap: "$4", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Nothing is bound to this surface yet \u2014 when you bind a personal agent, its persona, tools and memory live here rather than in a shared team." }), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Persona & Historical Character" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Pick an intellectual archetype for agent reasoning and tone." }), _jsx(XStack, { flexWrap: "wrap", gap: "$2", pt: "$1", children: HISTORICAL_PERSONAS.map((p) => {
|
|
295
|
-
const active = persona === p.id;
|
|
296
|
-
return (_jsx(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setPersona(p.id), ...CHOICE, bg: active ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: active ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', py: 6, px: 12, children: _jsx(Text, { fontSize: "$2", fontWeight: active ? '600' : '400', color: active ? '$ink' : '$soft', children: p.name }) }, p.id));
|
|
297
|
-
}) })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "System Prompt Instructions" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Custom system directions prepended to all coding and research runs." }), _jsx(Text, { render: _jsx("textarea", { value: instructions, onChange: (e) => setInstructions(e.target.value), rows: 4 }), width: "100%", bg: "var(--panel, color-mix(in srgb, var(--pure-black) 30%, transparent))", color: "inherit", borderWidth: 1, borderColor: "var(--borderColor, var(--white-15))", rounded: 8, p: 10, fontSize: "$2", fontFamily: "$mono" })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Autonomy & Permissions" }), _jsxs(XStack, { gap: "$2", children: [_jsxs(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setAutonomy('autonomous'), ...CHOICE, flex: 1, p: 10, bg: autonomy === 'autonomous' ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: autonomy === 'autonomous' ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Autonomous" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Executes tools & writes code without interrupting." })] }), _jsxs(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setAutonomy('supervised'), ...CHOICE, flex: 1, p: 10, bg: autonomy === 'supervised' ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: autonomy === 'supervised' ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Supervised" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Requests confirmation before file writes and commands." })] })] })] }), _jsx(XStack, { self: "flex-start", pt: "$2", children: _jsx(Button, { size: "sm", onClick: save, children: saved ? 'Saved' : 'Save agent preferences' }) })] }));
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* WHAT A RUN MAY REACH FOR — the four capabilities, described and not measured.
|
|
301
|
-
*
|
|
302
|
-
* Each row used to carry a badge reading "Enabled", and the MCP row read
|
|
303
|
-
* "Active (:8080/mcp)". Nothing asked. No call stands behind any of the four,
|
|
304
|
-
* so the badges said "on" for a reader whose run had none of it, and the port
|
|
305
|
-
* was one developer's machine printed on everybody's settings pane. A green
|
|
306
|
-
* light nobody wired is worse than no light: it is read as a check that passed.
|
|
307
|
-
*
|
|
308
|
-
* These are facts about the PRODUCT — what a run is allowed to do — which is
|
|
309
|
-
* what this pane is for. Whether a given run got them is a fact about that run,
|
|
310
|
-
* and the run is where it belongs.
|
|
311
|
-
*/
|
|
312
|
-
function Capabilities() {
|
|
313
|
-
return (_jsxs(YStack, { gap: "$3", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "What models and agent swarms are granted access to during tasks." }), _jsx(YStack, { gap: "$2", pt: "$1", children: [
|
|
314
|
-
{ title: 'Filesystem (Read / Write)', desc: 'Create, modify, and delete workspace files in the target repository.' },
|
|
315
|
-
{ title: 'Shell & Terminal Exec', desc: 'Execute build, test, and shell commands in isolated sandbox VMs.' },
|
|
316
|
-
{ title: 'Live Web & Search', desc: 'Query internet search APIs and fetch web documentation.' },
|
|
317
|
-
{ title: 'MCP Protocol Server', desc: 'Expose local and remote tools via Model Context Protocol at /mcp.' },
|
|
318
|
-
].map((cap) => (_jsxs(YStack, { gap: "$0.5", p: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: cap.title }), _jsx(Text, { fontSize: "$2", color: "$soft", children: cap.desc })] }, cap.title))) })] }));
|
|
319
|
-
}
|
|
320
|
-
function MemorySettings() {
|
|
321
|
-
const [cleared, setCleared] = useState(false);
|
|
322
|
-
const clearMemory = () => {
|
|
323
|
-
localStorage.removeItem('hanzo.chat.history');
|
|
324
|
-
setCleared(true);
|
|
325
|
-
setTimeout(() => setCleared(false), 2000);
|
|
326
|
-
};
|
|
327
|
-
return (_jsxs(YStack, { gap: "$3", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Semantic memory and conversation recall across sessions." }), _jsxs(YStack, { gap: "$0.5", p: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Semantic Vector Index" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Automatically indexes past workspace sessions into vector memory." })] }), _jsx(XStack, { self: "flex-start", pt: "$2", children: _jsx(Button, { size: "sm", variant: "outline", onClick: clearMemory, children: cleared ? 'Memory cleared' : 'Clear local memory cache' }) })] }));
|
|
328
|
-
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The room, while a settings row is open.
|
|
3
|
+
*
|
|
4
|
+
* It scrolls itself, the way the column beside a room does: `main` gives its
|
|
5
|
+
* child a floor and no overflow, so a pane that did not would push the whole
|
|
6
|
+
* shell down the page.
|
|
7
|
+
*/
|
|
8
|
+
export declare function SettingsPane({ id }: {
|
|
9
|
+
id: string;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// The room while a settings row is open: every section's screen (Settings.tsx
|
|
4
|
+
// holds the rows). Loaded when a row is chosen, never with the room.
|
|
5
|
+
import { useMemo, useState } from 'react';
|
|
6
|
+
import { ArrowUpRight, ExternalLink } from 'lucide-react';
|
|
7
|
+
import { Box, Button, Separator, Text, XStack, YStack, } from '@hanzo/ui';
|
|
8
|
+
import { tap } from './lib/tap.js';
|
|
9
|
+
import { useIam, useIamIdentity, useOrganizations } from '@hanzo/iam/react';
|
|
10
|
+
import { useSignOut } from './lib/signout.js';
|
|
11
|
+
import { useModels } from '@hanzo/ai/react';
|
|
12
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
13
|
+
import { parseModels } from '@hanzo/ui/models/catalog';
|
|
14
|
+
import { UsagePanel } from '@hanzo/usage/panel';
|
|
15
|
+
import { administers, Credits, label, Plan, Title, useChoice } from '@hanzo/build';
|
|
16
|
+
import { Catalog } from './Directory.js';
|
|
17
|
+
import { payPage } from './lib/pay.js';
|
|
18
|
+
import { api } from './lib/api.js';
|
|
19
|
+
import { renewal, useSubscription, useTier } from './lib/tier.js';
|
|
20
|
+
import { spendShown, useLimits } from './lib/limits.js';
|
|
21
|
+
import { showSettings } from './open.js';
|
|
22
|
+
import { Look } from './look.js';
|
|
23
|
+
import { ProviderMark } from './ProviderMark.js';
|
|
24
|
+
import { bearer, org, pick } from './lib/session.js';
|
|
25
|
+
import { site } from './where.js';
|
|
26
|
+
import { ROWS } from './Settings.js';
|
|
27
|
+
/** A choice among a few: a hairline edge and a corner. The fill and the edge's
|
|
28
|
+
* ink are the call site's, because they say which one is chosen. */
|
|
29
|
+
const CHOICE = { rounded: 8, borderWidth: 1, borderStyle: 'solid', cursor: 'pointer' };
|
|
30
|
+
/**
|
|
31
|
+
* The room, while a settings row is open.
|
|
32
|
+
*
|
|
33
|
+
* It scrolls itself, the way the column beside a room does: `main` gives its
|
|
34
|
+
* child a floor and no overflow, so a pane that did not would push the whole
|
|
35
|
+
* shell down the page.
|
|
36
|
+
*/
|
|
37
|
+
export function SettingsPane({ id }) {
|
|
38
|
+
const row = ROWS.find((r) => r.id === id);
|
|
39
|
+
return (_jsx(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", children: _jsxs(YStack, { p: "$5", gap: "$4", maxW: 860, children: [_jsx(Text, { fontSize: "$6", fontWeight: "600", color: "$ink", children: row?.label ?? 'Settings' }), _jsx(Pane, { id: id, note: row?.note })] }) }));
|
|
40
|
+
}
|
|
41
|
+
/** One pane. */
|
|
42
|
+
function Pane({ id, note }) {
|
|
43
|
+
if (id === 'apps' || id === 'channels')
|
|
44
|
+
return _jsx(Catalog, { tab: id });
|
|
45
|
+
if (id === 'general')
|
|
46
|
+
return _jsx(Look, {});
|
|
47
|
+
if (id === 'account')
|
|
48
|
+
return _jsx(Account, {});
|
|
49
|
+
if (id === 'billing')
|
|
50
|
+
return _jsx(Billing, {});
|
|
51
|
+
if (id === 'usage')
|
|
52
|
+
return _jsx(Usage, {});
|
|
53
|
+
if (id === 'model')
|
|
54
|
+
return _jsx(Model, {});
|
|
55
|
+
if (id === 'models')
|
|
56
|
+
return _jsx(Served, {});
|
|
57
|
+
if (id === 'agent')
|
|
58
|
+
return _jsx(AgentSettings, {});
|
|
59
|
+
if (id === 'capabilities')
|
|
60
|
+
return _jsx(Capabilities, {});
|
|
61
|
+
if (id === 'memory')
|
|
62
|
+
return _jsx(MemorySettings, {});
|
|
63
|
+
return (_jsx(Text, { fontSize: "$3", color: "$soft", children: note ?? 'Nothing to configure here yet.' }));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* One default in one place; agents may still bring their own model. A premium
|
|
67
|
+
* model is never a default: it is picked per conversation, in the composer, so
|
|
68
|
+
* this picker offers the rest of the catalog.
|
|
69
|
+
*/
|
|
70
|
+
function Model() {
|
|
71
|
+
const { models: served } = useModels();
|
|
72
|
+
const models = useMemo(() => parseModels(served).filter((m) => m.class !== 'premium'), [served]);
|
|
73
|
+
// The one choice Chat and Dev share (@hanzo/build): changed here, changed in both composers.
|
|
74
|
+
const [choice, choose] = useChoice();
|
|
75
|
+
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" }) })] }));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* WHAT THE GATEWAY SERVES, read from the same `/v1/models` the rooms read.
|
|
79
|
+
*
|
|
80
|
+
* Counted, never typed: the catalog moves without this file, so a number written
|
|
81
|
+
* here is wrong by the next deploy. An empty answer says the account reaches
|
|
82
|
+
* nothing rather than drawing a zero, and the mark comes from the model's own
|
|
83
|
+
* id so a family wears the logo of the lab that makes it.
|
|
84
|
+
*/
|
|
85
|
+
function Served() {
|
|
86
|
+
const { models, loading, error } = useModels();
|
|
87
|
+
if (loading) {
|
|
88
|
+
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Reading the catalog." });
|
|
89
|
+
}
|
|
90
|
+
if (error) {
|
|
91
|
+
return _jsxs(Text, { fontSize: "$3", color: "$soft", children: ["The catalog did not answer: ", error.message] });
|
|
92
|
+
}
|
|
93
|
+
if (models.length === 0) {
|
|
94
|
+
return (_jsx(Text, { fontSize: "$3", color: "$soft", children: "No model answers on this account. Sign in to reach the ones your org opens." }));
|
|
95
|
+
}
|
|
96
|
+
return (_jsxs(YStack, { gap: "$3", children: [_jsxs(Text, { fontSize: "$3", color: "$soft", children: [models.length, " models answer at /v1/models on this account."] }), _jsx(YStack, { children: models.map((m) => (_jsxs(XStack, { items: "center", gap: "$2.5", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(ProviderMark, { model: m.id, org: m.owned_by, size: 16, ink: true }), _jsx(Text, { fontSize: "$2", color: "$ink", flex: 1, children: m.id }), m.owned_by ? _jsx(Text, { fontSize: "$1", color: "$soft", children: m.owned_by }) : null] }, m.id))) }), _jsx(XStack, { children: _jsx(Box, { render: _jsx("a", { href: site('/models') }), ...tap, children: _jsxs(XStack, { items: "center", gap: "$2", py: "$2", children: [_jsx(Text, { fontSize: "$3", color: "$ink", children: "Browse the model catalog" }), _jsx(ArrowUpRight, { size: 13, "aria-hidden": true })] }) }) })] }));
|
|
97
|
+
}
|
|
98
|
+
/** Who is signed in, and where they can act. */
|
|
99
|
+
function Account() {
|
|
100
|
+
const identity = useIamIdentity();
|
|
101
|
+
const { isAuthenticated } = useIam();
|
|
102
|
+
const logout = useSignOut();
|
|
103
|
+
const { organizations, currentOrgId, currentRole, switchOrg } = useOrganizations();
|
|
104
|
+
if (!isAuthenticated || !identity) {
|
|
105
|
+
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see your account." });
|
|
106
|
+
}
|
|
107
|
+
return (_jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsx(YStack, { width: 44, height: 44, rounded: 22, bg: "$borderColor", items: "center", justify: "center", children: _jsx(Text, { fontSize: "$3", color: "$ink", children: identity.initials }) }), _jsxs(YStack, { gap: "$1", flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$4", color: "$ink", children: identity.name }), identity.email ? _jsx(Text, { fontSize: "$2", color: "$soft", children: identity.email }) : null] })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$2", children: [_jsx(Text, { fontSize: "$2", color: "$soft", children: "Organizations" }), organizations.length === 0 ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "You are not a member of an organization." })) : (organizations.map((o) => {
|
|
108
|
+
const here = o.name === currentOrgId;
|
|
109
|
+
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));
|
|
110
|
+
}))] }), _jsx(Separator, {}), _jsx(XStack, { children: _jsx(Button, { size: "sm", onClick: () => logout(), children: "Sign out" }) })] }));
|
|
111
|
+
}
|
|
112
|
+
/** Where the plan's and billing's reads go: this browser's bearer, in its organization. */
|
|
113
|
+
function useTarget() {
|
|
114
|
+
const scoped = org();
|
|
115
|
+
return useMemo(() => ({ api: api(), token: bearer, org: scoped }), [scoped]);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The plan and its term, then the credits — @hanzo/build's `Credits`, the one
|
|
119
|
+
* view every Billing page draws: the balance, grants, auto-reload, this month's
|
|
120
|
+
* spend and its limit, said to be separate from the plan's usage. Money moves on
|
|
121
|
+
* the payment host, so adding funds and a card both open it.
|
|
122
|
+
*/
|
|
123
|
+
function Billing() {
|
|
124
|
+
const { isAuthenticated } = useIam();
|
|
125
|
+
const t = useTarget();
|
|
126
|
+
const { tier } = useTier(isAuthenticated, org());
|
|
127
|
+
const sub = useSubscription(isAuthenticated, org());
|
|
128
|
+
if (!isAuthenticated)
|
|
129
|
+
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see billing." });
|
|
130
|
+
// WHAT THE CUSTOMER BOUGHT, by its family: `Max` with `20x` beside it.
|
|
131
|
+
const plan = tier ? label(tier.plan, tier.tier.name) ?? { name: 'Free', tag: '' } : null;
|
|
132
|
+
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 })] }) }) })] })] }));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Usage: @hanzo/build's `Plan`, the one view every Usage page draws — the plan's
|
|
136
|
+
* windows and each class of model's share, as shares; the free allowance on
|
|
137
|
+
* Free; the balance with no plan — and the choice of paying from credits once
|
|
138
|
+
* the allowance runs out. A reader with no paid plan also sees what was spent,
|
|
139
|
+
* over time (@hanzo/usage), because money is their meter.
|
|
140
|
+
*/
|
|
141
|
+
function Usage() {
|
|
142
|
+
const { accessToken, isAuthenticated } = useIam();
|
|
143
|
+
const t = useTarget();
|
|
144
|
+
const read = useLimits(isAuthenticated, org());
|
|
145
|
+
if (!isAuthenticated || !accessToken) {
|
|
146
|
+
return _jsx(Text, { fontSize: "$3", color: "$soft", children: "Sign in to see your usage." });
|
|
147
|
+
}
|
|
148
|
+
const plan = _jsx(Plan, { target: t, onPlans: () => window.location.assign(site('/pricing')), onBilling: () => showSettings('billing') });
|
|
149
|
+
if (!spendShown(read))
|
|
150
|
+
return plan;
|
|
151
|
+
// The address is read HERE rather than at import: the panel sends the bearer
|
|
152
|
+
// with every read, and a credentialed read to an absolute address from a page
|
|
153
|
+
// the gateway does not admit dies in preflight — which drew this panel as an
|
|
154
|
+
// account that had spent nothing.
|
|
155
|
+
return (_jsxs(YStack, { gap: "$6", children: [plan, _jsx(UsagePanel, { baseUrl: api(), token: accessToken, upgrade: payPage(), title: "Spend" })] }));
|
|
156
|
+
}
|
|
157
|
+
const HISTORICAL_PERSONAS = [
|
|
158
|
+
{ id: 'architect', name: 'Software Architect', role: 'Full-stack & systems design' },
|
|
159
|
+
{ id: 'satoshi', name: 'Satoshi Nakamoto', role: 'Distributed cryptography & consensus' },
|
|
160
|
+
{ id: 'turing', name: 'Alan Turing', role: 'Algorithmic logic & formal verification' },
|
|
161
|
+
{ id: 'ada', name: 'Ada Lovelace', role: 'Computational precision & analysis' },
|
|
162
|
+
{ id: 'davinci', name: 'Leonardo da Vinci', role: 'Polymathic engineering & design' },
|
|
163
|
+
];
|
|
164
|
+
function AgentSettings() {
|
|
165
|
+
const [instructions, setInstructions] = useState(() => {
|
|
166
|
+
if (typeof window === 'undefined')
|
|
167
|
+
return '';
|
|
168
|
+
return localStorage.getItem('hanzo.agent.instructions') || 'You are an autonomous AI software engineer at Hanzo. Think step-by-step and write robust code.';
|
|
169
|
+
});
|
|
170
|
+
const [persona, setPersona] = useState(() => {
|
|
171
|
+
if (typeof window === 'undefined')
|
|
172
|
+
return 'architect';
|
|
173
|
+
return localStorage.getItem('hanzo.agent.persona') || 'architect';
|
|
174
|
+
});
|
|
175
|
+
const [autonomy, setAutonomy] = useState(() => {
|
|
176
|
+
if (typeof window === 'undefined')
|
|
177
|
+
return 'autonomous';
|
|
178
|
+
return localStorage.getItem('hanzo.agent.autonomy') || 'autonomous';
|
|
179
|
+
});
|
|
180
|
+
const [saved, setSaved] = useState(false);
|
|
181
|
+
const save = () => {
|
|
182
|
+
localStorage.setItem('hanzo.agent.instructions', instructions);
|
|
183
|
+
localStorage.setItem('hanzo.agent.persona', persona);
|
|
184
|
+
localStorage.setItem('hanzo.agent.autonomy', autonomy);
|
|
185
|
+
setSaved(true);
|
|
186
|
+
setTimeout(() => setSaved(false), 2000);
|
|
187
|
+
};
|
|
188
|
+
return (_jsxs(YStack, { gap: "$4", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Nothing is bound to this surface yet \u2014 when you bind a personal agent, its persona, tools and memory live here rather than in a shared team." }), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Persona & Historical Character" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Pick an intellectual archetype for agent reasoning and tone." }), _jsx(XStack, { flexWrap: "wrap", gap: "$2", pt: "$1", children: HISTORICAL_PERSONAS.map((p) => {
|
|
189
|
+
const active = persona === p.id;
|
|
190
|
+
return (_jsx(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setPersona(p.id), ...CHOICE, bg: active ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: active ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', py: 6, px: 12, children: _jsx(Text, { fontSize: "$2", fontWeight: active ? '600' : '400', color: active ? '$ink' : '$soft', children: p.name }) }, p.id));
|
|
191
|
+
}) })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "System Prompt Instructions" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Custom system directions prepended to all coding and research runs." }), _jsx(Text, { render: _jsx("textarea", { value: instructions, onChange: (e) => setInstructions(e.target.value), rows: 4 }), width: "100%", bg: "var(--panel, color-mix(in srgb, var(--pure-black) 30%, transparent))", color: "inherit", borderWidth: 1, borderColor: "var(--borderColor, var(--white-15))", rounded: 8, p: 10, fontSize: "$2", fontFamily: "$mono" })] }), _jsx(Separator, {}), _jsxs(YStack, { gap: "$1.5", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Autonomy & Permissions" }), _jsxs(XStack, { gap: "$2", children: [_jsxs(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setAutonomy('autonomous'), ...CHOICE, flex: 1, p: 10, bg: autonomy === 'autonomous' ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: autonomy === 'autonomous' ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Autonomous" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Executes tools & writes code without interrupting." })] }), _jsxs(Box, { render: _jsx("button", { type: "button" }), ...tap, onClick: () => setAutonomy('supervised'), ...CHOICE, flex: 1, p: 10, bg: autonomy === 'supervised' ? 'var(--edge, var(--white-14))' : 'transparent', borderColor: autonomy === 'supervised' ? 'var(--borderColor, var(--white-22))' : 'var(--white-08)', children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Supervised" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Requests confirmation before file writes and commands." })] })] })] }), _jsx(XStack, { self: "flex-start", pt: "$2", children: _jsx(Button, { size: "sm", onClick: save, children: saved ? 'Saved' : 'Save agent preferences' }) })] }));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* WHAT A RUN MAY REACH FOR — the four capabilities, described and not measured.
|
|
195
|
+
*
|
|
196
|
+
* Each row used to carry a badge reading "Enabled", and the MCP row read
|
|
197
|
+
* "Active (:8080/mcp)". Nothing asked. No call stands behind any of the four,
|
|
198
|
+
* so the badges said "on" for a reader whose run had none of it, and the port
|
|
199
|
+
* was one developer's machine printed on everybody's settings pane. A green
|
|
200
|
+
* light nobody wired is worse than no light: it is read as a check that passed.
|
|
201
|
+
*
|
|
202
|
+
* These are facts about the PRODUCT — what a run is allowed to do — which is
|
|
203
|
+
* what this pane is for. Whether a given run got them is a fact about that run,
|
|
204
|
+
* and the run is where it belongs.
|
|
205
|
+
*/
|
|
206
|
+
function Capabilities() {
|
|
207
|
+
return (_jsxs(YStack, { gap: "$3", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "What models and agent swarms are granted access to during tasks." }), _jsx(YStack, { gap: "$2", pt: "$1", children: [
|
|
208
|
+
{ title: 'Filesystem (Read / Write)', desc: 'Create, modify, and delete workspace files in the target repository.' },
|
|
209
|
+
{ title: 'Shell & Terminal Exec', desc: 'Execute build, test, and shell commands in isolated sandbox VMs.' },
|
|
210
|
+
{ title: 'Live Web & Search', desc: 'Query internet search APIs and fetch web documentation.' },
|
|
211
|
+
{ title: 'MCP Protocol Server', desc: 'Expose local and remote tools via Model Context Protocol at /mcp.' },
|
|
212
|
+
].map((cap) => (_jsxs(YStack, { gap: "$0.5", p: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: cap.title }), _jsx(Text, { fontSize: "$2", color: "$soft", children: cap.desc })] }, cap.title))) })] }));
|
|
213
|
+
}
|
|
214
|
+
function MemorySettings() {
|
|
215
|
+
const [cleared, setCleared] = useState(false);
|
|
216
|
+
const clearMemory = () => {
|
|
217
|
+
localStorage.removeItem('hanzo.chat.history');
|
|
218
|
+
setCleared(true);
|
|
219
|
+
setTimeout(() => setCleared(false), 2000);
|
|
220
|
+
};
|
|
221
|
+
return (_jsxs(YStack, { gap: "$3", maxW: 480, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Semantic memory and conversation recall across sessions." }), _jsxs(YStack, { gap: "$0.5", p: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Semantic Vector Index" }), _jsx(Text, { fontSize: "$2", color: "$soft", children: "Automatically indexes past workspace sessions into vector memory." })] }), _jsx(XStack, { self: "flex-start", pt: "$2", children: _jsx(Button, { size: "sm", variant: "outline", onClick: clearMemory, children: cleared ? 'Memory cleared' : 'Clear local memory cache' }) })] }));
|
|
222
|
+
}
|
package/dist/Shell.js
CHANGED
|
@@ -13,7 +13,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
13
13
|
// holds arrangement and words and owns no pixels of its own. That is the whole
|
|
14
14
|
// architecture: a stranger with `@hanzo/ui`, `@hanzo/ai` and `@hanzo/iam` can
|
|
15
15
|
// assemble this room, because nothing in it is private to hanzo.ai.
|
|
16
|
-
import { createContext, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
16
|
+
import { createContext, lazy, Suspense, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
17
17
|
import { createPortal } from "react-dom";
|
|
18
18
|
import { MessageSquare, Code2, Inbox, Bot, ListChecks, HardDrive, Settings2, UserPlus, Building2, Search, Hash, AtSign, Lock, Workflow, Plus, ArrowLeft, ArrowRight, Send, Minus, Blocks, Star, FolderOpen, Activity, BookOpen, SquareTerminal, Share2, Sparkles, PanelLeft, PanelRight, Users, Globe, Cpu, Boxes, Wallet, Video, Calendar, LayoutGrid, ArrowUpLeft, } from "lucide-react";
|
|
19
19
|
import { AiProvider, useAgents, useInbox, useModels, usePeople, } from "@hanzo/ai/react";
|
|
@@ -70,7 +70,7 @@ import { roomName } from "./network.js";
|
|
|
70
70
|
import { useTeamRooms, teamKey, roomHref } from "./rooms.js";
|
|
71
71
|
import { apex, app, dev, ENTRY, room as moves, WORKSPACE } from "./lib/host.js";
|
|
72
72
|
import { netKey } from "./conversations.js";
|
|
73
|
-
import { FIRST, SettingsList,
|
|
73
|
+
import { FIRST, SettingsList, } from "./Settings.js";
|
|
74
74
|
import { Profile } from "./Profile.js";
|
|
75
75
|
import { SupportPicker } from "./Support.js";
|
|
76
76
|
import { Find } from "./Find.js";
|
|
@@ -116,6 +116,8 @@ import { getStoredPeople, getStoredAgents, getCustomThreads, createDirectChat, }
|
|
|
116
116
|
import { bare } from './lib/bare.js';
|
|
117
117
|
import { BAD } from './lib/mix.js';
|
|
118
118
|
import { brand, site, where, wordmark } from './where.js';
|
|
119
|
+
/** The room while a settings row is open, loaded when one is: none of it is a room's first screen. */
|
|
120
|
+
const SettingsPane = lazy(() => import('./SettingsPane.js').then((m) => ({ default: m.SettingsPane })));
|
|
119
121
|
/**
|
|
120
122
|
* The destinations, in rail order.
|
|
121
123
|
*
|
|
@@ -1235,5 +1237,5 @@ function Frame({ mode, children, navigate, back, forward, }) {
|
|
|
1235
1237
|
}, position: "relative", width: "100%", height: "100dvh", overflow: "hidden", bg: "$background", backgroundImage: "var(--pane-ground)", children: [_jsx(Rail, { mode: mode, places: places, open: asked, team: onApex ? (signedIn ? ENTRY : WORKSPACE) : undefined, navigate: navigate, onSettings: () => showSettings(FIRST), onMore: () => setFinding(true) }), _jsxs(XStack, { render: "header", "data-slot": "bar", gridArea: "bar", ...sheet(0), height: 44, shrink: 0, items: "center", px: "$3", gap: "$3", borderBottomWidth: 1, borderColor: "var(--edge, var(--white-06))", children: [_jsxs(XStack, { items: "center", gap: "$1", shrink: 0, children: [_jsx(SidebarIconButton, { ...TAP, label: shown ? "Close sidebar" : "Open sidebar", onPress: toggle, onMouseEnter: () => setPeeking(true), children: _jsx(PanelLeft, { size: 16, "aria-hidden": true }) }), back || forward ? (_jsxs(XStack, { display: "none", $md: { display: "flex" }, items: "center", gap: "$1", shrink: 0, children: [_jsx(SidebarIconButton, { ...TAP, label: "Back", onPress: () => back?.(), children: _jsx(ArrowLeft, { size: 16, "aria-hidden": true }) }), _jsx(SidebarIconButton, { ...TAP, label: "Forward", onPress: () => forward?.(), children: _jsx(ArrowRight, { size: 16, "aria-hidden": true }) })] })) : null] }), _jsx(XStack, { flex: 1, justify: "center", children: _jsx(YStack, { width: 440, maxW: "100%", position: "relative", children: _jsxs(Box, { render: "button", onClick: () => setFinding(true), display: "flex", flexDirection: "row", "data-slot": "find", items: "center", "$max-md": { justify: "center" }, gap: "$2", width: "100%", height: CONTROL, px: "$2.5", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(Search, { size: 13, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsxs(Text, { fontSize: "$1", color: "$soft", "$max-md": { display: "none" }, children: ["Search ", workspace] }), _jsx(XStack, { ml: "auto", items: "center", justify: "center", height: 18, px: "$1", rounded: "$1", bg: "$edge", "$max-md": { display: "none" }, children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "$soft", children: "\u2318K" }) })] }) }) }), _jsxs(XStack, { shrink: 0, minW: 0, justify: "flex-end", items: "center", gap: "$2", children: [settled && !signedIn ? (_jsx(Box, { ...TAP, render: "button", onClick: () => enter(LOGIN), display: "flex", items: "center", justify: "center", cursor: "pointer", children: _jsx(XStack, { items: "center", justify: "center", height: CONTROL, px: "$2.5", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "$ink", children: "Sign in" }) }) })) : null, _jsxs(XStack, { display: "none", $lg: { display: "flex" }, items: "center", gap: "$1", children: [_jsx(SidebarIconButton, { ...TAP, label: aside === "roster" ? "Hide directory & roster" : "Show directory & roster", onPress: () => showAside(aside === "roster" ? null : "roster"), children: _jsx(Users, { size: 16, "aria-hidden": true }) }), _jsx(SidebarIconButton, { ...TAP, label: aside === "tasks" ? "Close side panel" : "Open side panel", onPress: () => showAside(aside === "tasks" ? null : "tasks"), children: _jsx(PanelRight, { size: 16, "aria-hidden": true }) })] })] })] }), shown ? (_jsx(Box, { render: "button", "aria-label": "Close sidebar", onClick: dismiss, ...scrim, "data-slot": "scrim", position: "absolute", t: 44, r: 0, b: TABS_HEIGHT, l: 0, z: "var(--z-scrim)", $md: { display: "none" } })) : null, shown ? (_jsx(XStack, { "data-slot": "list", gridArea: "list", position: asked ? "relative" : "absolute", ...(asked ? {} : { top: 44, bottom: 0, left: 64 }), "$max-md": { position: "fixed", t: 44, r: 0, b: TABS_HEIGHT, l: 0, width: "100%" }, z: "var(--z-drawer)", bg: "$background", onMouseEnter: () => setPeeking(true), onMouseLeave: () => {
|
|
1236
1238
|
if (!asked)
|
|
1237
1239
|
setPeeking(false);
|
|
1238
|
-
}, children: list })) : null, _jsx(Paned, { swap: settingsPane ? _jsx(SettingsPane, { id: settingsPane }) : undefined, children: children }), _jsx(Profile, { open: profiling, onClose: () => setProfiling(false) }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null, finding ? (_jsx(Find, { doors: doors, onClose: () => setFinding(false), navigate: navigate })) : null] }));
|
|
1240
|
+
}, children: list })) : null, _jsx(Paned, { swap: settingsPane ? _jsx(Suspense, { fallback: null, children: _jsx(SettingsPane, { id: settingsPane }) }) : undefined, children: children }), _jsx(Profile, { open: profiling, onClose: () => setProfiling(false) }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null, finding ? (_jsx(Find, { doors: doors, onClose: () => setFinding(false), navigate: navigate })) : null] }));
|
|
1239
1241
|
}
|
package/dist/Standing.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
// The plan at the head of the account menu (Account.tsx): @hanzo/build's
|
|
4
|
+
// `Meter` over the organization's standing, read when the menu opens. Its own
|
|
5
|
+
// module so the menu's foot row costs a room's first screen nothing: it loads
|
|
6
|
+
// with the menu.
|
|
7
|
+
import { useMemo } from 'react';
|
|
8
|
+
import { Meter, useStanding } from '@hanzo/build';
|
|
9
|
+
import { api } from './lib/api.js';
|
|
10
|
+
import { bearer, org } from './lib/session.js';
|
|
11
|
+
export function Standing({ onPlans, onBilling }) {
|
|
12
|
+
const scoped = org();
|
|
13
|
+
const target = useMemo(() => ({ api: api(), token: bearer, org: scoped }), [scoped]);
|
|
14
|
+
const plan = useStanding(target, true);
|
|
15
|
+
return _jsx(Meter, { read: plan, onPlans: onPlans, onBilling: onBilling });
|
|
16
|
+
}
|
package/dist/lib/host.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ export declare const DEV = "/dev";
|
|
|
51
51
|
* hanzo.ai/studio, whose page draws the app for a signed-in reader.
|
|
52
52
|
*/
|
|
53
53
|
export declare const STUDIO = "/studio";
|
|
54
|
+
/** Studio at an address of its own (@hanzo/studio `route`): '' is New, /studio. */
|
|
55
|
+
export declare const studio: (at: string) => string;
|
|
54
56
|
/** Chat on a conversation, or /chat itself for a new one. */
|
|
55
57
|
export declare const talk: (id?: string | null) => string;
|
|
56
58
|
/** Dev at a builder address (`route` in @hanzo/build); '' is New, /dev. What is not an address is New. */
|
|
@@ -62,6 +64,9 @@ export type Place = {
|
|
|
62
64
|
} | {
|
|
63
65
|
mode: 'dev';
|
|
64
66
|
at: string;
|
|
67
|
+
} | {
|
|
68
|
+
mode: 'studio';
|
|
69
|
+
at: string;
|
|
65
70
|
};
|
|
66
71
|
/** The place a path names, or null when it names none of the app's. */
|
|
67
72
|
export declare function place(pathname: string): Place | null;
|
package/dist/lib/host.js
CHANGED
|
@@ -54,6 +54,8 @@ export const DEV = '/dev';
|
|
|
54
54
|
* hanzo.ai/studio, whose page draws the app for a signed-in reader.
|
|
55
55
|
*/
|
|
56
56
|
export const STUDIO = '/studio';
|
|
57
|
+
/** Studio at an address of its own (@hanzo/studio `route`): '' is New, /studio. */
|
|
58
|
+
export const studio = (at) => (at ? `${STUDIO}/${at.replace(/^\/+/, '')}` : STUDIO);
|
|
57
59
|
/** Chat on a conversation, or /chat itself for a new one. */
|
|
58
60
|
export const talk = (id) => (id ? `${CHAT}/${encodeURIComponent(id)}` : CHAT);
|
|
59
61
|
/** Dev at a builder address (`route` in @hanzo/build); '' is New, /dev. What is not an address is New. */
|
|
@@ -67,6 +69,8 @@ export function place(pathname) {
|
|
|
67
69
|
return { mode: 'chat', thread: null };
|
|
68
70
|
if (p === DEV || p.startsWith(`${DEV}/`))
|
|
69
71
|
return { mode: 'dev', at: path(parse(p.slice(DEV.length))) };
|
|
72
|
+
if (p === STUDIO || p.startsWith(`${STUDIO}/`))
|
|
73
|
+
return { mode: 'studio', at: p.slice(STUDIO.length + 1) };
|
|
70
74
|
if (!p.startsWith(`${CHAT}/`))
|
|
71
75
|
return null;
|
|
72
76
|
const id = p.slice(CHAT.length + 1);
|
|
@@ -136,7 +140,8 @@ export function remember(mode) {
|
|
|
136
140
|
/** Where `/` takes a member: the mode they were last in, Chat when none is kept. */
|
|
137
141
|
export function resume() {
|
|
138
142
|
try {
|
|
139
|
-
|
|
143
|
+
const kept = window.localStorage.getItem(MODE);
|
|
144
|
+
return kept === 'dev' ? DEV : kept === 'studio' ? STUDIO : CHAT;
|
|
140
145
|
}
|
|
141
146
|
catch {
|
|
142
147
|
return CHAT;
|
package/dist/where.d.ts
CHANGED
|
@@ -68,6 +68,6 @@ export declare const where: () => Addresses;
|
|
|
68
68
|
/** The product's name, as the host states it. Read at call time, like every address. */
|
|
69
69
|
export declare const brand: () => string;
|
|
70
70
|
/** The product's name in each mode of the app: the host's brand in Chat (Hanzo AI, Hanzo Team), Hanzo Dev in Dev. */
|
|
71
|
-
export declare const wordmark: (mode: 'chat' | 'dev') => string;
|
|
71
|
+
export declare const wordmark: (mode: 'chat' | 'dev' | 'studio') => string;
|
|
72
72
|
/** A page of the app's own, from wherever the rooms are mounted. */
|
|
73
73
|
export declare const site: (path: string) => string;
|
package/dist/where.js
CHANGED
|
@@ -12,6 +12,6 @@ export const where = () => addresses;
|
|
|
12
12
|
/** The product's name, as the host states it. Read at call time, like every address. */
|
|
13
13
|
export const brand = () => addresses.brand ?? 'Hanzo AI';
|
|
14
14
|
/** The product's name in each mode of the app: the host's brand in Chat (Hanzo AI, Hanzo Team), Hanzo Dev in Dev. */
|
|
15
|
-
export const wordmark = (mode) => (mode === 'dev' ? 'Hanzo Dev' : brand());
|
|
15
|
+
export const wordmark = (mode) => (mode === 'dev' ? 'Hanzo Dev' : mode === 'studio' ? 'Hanzo Studio' : brand());
|
|
16
16
|
/** A page of the app's own, from wherever the rooms are mounted. */
|
|
17
17
|
export const site = (path) => `${addresses.site}${path}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.69",
|
|
4
4
|
"description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"@hanzo/gui": "^8.3.5",
|
|
56
56
|
"@hanzo/iam": ">=0.21.28",
|
|
57
57
|
"@hanzo/logo": ">=1.0.26",
|
|
58
|
+
"@hanzo/studio": ">=0.1.0",
|
|
58
59
|
"@hanzo/ui": ">=8.27.67",
|
|
59
60
|
"@hanzo/usage": ">=0.1.18",
|
|
60
61
|
"@hanzo/voice": ">=0.1.16",
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
"@hanzo/gui": "8.3.5",
|
|
74
75
|
"@hanzo/iam": "0.21.28",
|
|
75
76
|
"@hanzo/logo": "1.0.26",
|
|
77
|
+
"@hanzo/studio": "0.1.0",
|
|
76
78
|
"@hanzo/ui": "8.27.67",
|
|
77
79
|
"@hanzo/usage": "0.1.18",
|
|
78
80
|
"@hanzo/voice": "0.1.16",
|