@hanzo/rooms 0.1.72 → 0.1.74
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/assets/personas/ada.md +187 -0
- package/assets/personas/adi_shamir.md +155 -0
- package/assets/personas/al_khwarizmi.md +168 -0
- package/assets/personas/al_kindi.md +172 -0
- package/assets/personas/alan_kay.md +154 -0
- package/assets/personas/analyst.md +138 -0
- package/assets/personas/andrew_yao.md +155 -0
- package/assets/personas/auguste_kerckhoffs.md +168 -0
- package/assets/personas/barbara_liskov.md +154 -0
- package/assets/personas/butler_lampson.md +156 -0
- package/assets/personas/charles_babbage.md +175 -0
- package/assets/personas/creative.md +141 -0
- package/assets/personas/curry.md +169 -0
- package/assets/personas/cynthia_dwork.md +158 -0
- package/assets/personas/david_chaum.md +157 -0
- package/assets/personas/david_parnas.md +153 -0
- package/assets/personas/dennis_ritchie.md +153 -0
- package/assets/personas/dijkstra.md +187 -0
- package/assets/personas/donald_knuth.md +156 -0
- package/assets/personas/douglas_engelbart.md +175 -0
- package/assets/personas/edgar_codd.md +151 -0
- package/assets/personas/engineer.md +140 -0
- package/assets/personas/feynman.md +178 -0
- package/assets/personas/finney.md +187 -0
- package/assets/personas/fred_brooks.md +152 -0
- package/assets/personas/geoffrey_hinton.md +194 -0
- package/assets/personas/herbert_simon.md +190 -0
- package/assets/personas/hickey.md +169 -0
- package/assets/personas/hopper.md +191 -0
- package/assets/personas/hubert_dreyfus.md +184 -0
- package/assets/personas/humberto_maturana.md +156 -0
- package/assets/personas/index.json +1 -0
- package/assets/personas/jcr_licklider.md +178 -0
- package/assets/personas/jobs.md +177 -0
- package/assets/personas/joe_armstrong.md +155 -0
- package/assets/personas/john_backus.md +179 -0
- package/assets/personas/john_mccarthy.md +177 -0
- package/assets/personas/jon_postel.md +149 -0
- package/assets/personas/joseph_weizenbaum.md +185 -0
- package/assets/personas/judea_pearl.md +192 -0
- package/assets/personas/karen_sparck_jones.md +154 -0
- package/assets/personas/konrad_zuse.md +175 -0
- package/assets/personas/kunihiko_fukushima.md +152 -0
- package/assets/personas/leslie_lamport.md +156 -0
- package/assets/personas/markov.md +171 -0
- package/assets/personas/mentor.md +141 -0
- package/assets/personas/nancy_lynch.md +154 -0
- package/assets/personas/niklaus_wirth.md +154 -0
- package/assets/personas/norbert_wiener.md +179 -0
- package/assets/personas/north-star.md +138 -0
- package/assets/personas/peter_naur.md +151 -0
- package/assets/personas/pike.md +165 -0
- package/assets/personas/radia_perlman.md +156 -0
- package/assets/personas/raj_reddy.md +154 -0
- package/assets/personas/ralph_merkle.md +152 -0
- package/assets/personas/richard_sutton.md +156 -0
- package/assets/personas/robin_milner.md +149 -0
- package/assets/personas/shafi_goldwasser.md +156 -0
- package/assets/personas/shannon.md +184 -0
- package/assets/personas/stafford_beer.md +157 -0
- package/assets/personas/taher_elgamal.md +180 -0
- package/assets/personas/tesla.md +181 -0
- package/assets/personas/tony_hoare.md +153 -0
- package/assets/personas/torvalds.md +188 -0
- package/assets/personas/tpope.md +163 -0
- package/assets/personas/turing.md +188 -0
- package/assets/personas/whitfield_diffie.md +159 -0
- package/assets/personas/zeekay.md +151 -0
- package/dist/App.js +26 -22
- package/dist/Chat.js +51 -38
- package/dist/Column.d.ts +3 -3
- package/dist/Column.js +5 -27
- package/dist/Me.d.ts +8 -3
- package/dist/Me.js +79 -108
- package/dist/Palette.d.ts +18 -0
- package/dist/Palette.js +131 -0
- package/dist/Rail.js +3 -15
- package/dist/Recents.d.ts +1 -2
- package/dist/Recents.js +9 -9
- package/dist/Shell.d.ts +7 -0
- package/dist/Shell.js +65 -46
- package/dist/Top.d.ts +17 -0
- package/dist/Top.js +58 -0
- package/dist/agents.d.ts +64 -0
- package/dist/agents.js +62 -0
- package/dist/crew.d.ts +26 -5
- package/dist/crew.js +208 -83
- package/dist/help.d.ts +43 -0
- package/dist/help.js +58 -0
- package/dist/lib/session.d.ts +5 -10
- package/dist/lib/session.js +6 -10
- package/dist/lib/web.d.ts +6 -0
- package/dist/lib/web.js +39 -7
- package/dist/recent.d.ts +2 -2
- package/dist/recent.js +3 -5
- package/dist/seek.d.ts +37 -0
- package/dist/seek.js +55 -0
- package/dist/team.js +3 -11
- package/dist/thread.js +7 -2
- package/dist/view.d.ts +44 -0
- package/dist/view.js +98 -0
- package/package.json +7 -7
- package/dist/Account.d.ts +0 -12
- package/dist/Account.js +0 -65
- package/dist/Find.d.ts +0 -7
- package/dist/Find.js +0 -82
- package/dist/Profile.d.ts +0 -4
- package/dist/Profile.js +0 -67
- package/dist/Standing.d.ts +0 -4
- package/dist/Standing.js +0 -16
package/dist/Column.js
CHANGED
|
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
//
|
|
11
11
|
// The bell is the org's run activity (@hanzo/build `useSessions`): the runs
|
|
12
12
|
// that moved last, each one press from its page, and Notifications in Settings.
|
|
13
|
-
// The search opens the
|
|
13
|
+
// The search opens the palette (Palette.tsx; ⌘K / Ctrl+K from anywhere).
|
|
14
14
|
//
|
|
15
15
|
// Dev's places are @hanzo/build's `nav`, the one list every rail draws, each a
|
|
16
16
|
// row under a quiet group label.
|
|
@@ -19,11 +19,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
19
19
|
// double-clicked back (@hanzo/build's `Grip`), kept per browser. Pulled in past
|
|
20
20
|
// its floor it shuts (`onShut`), as the sidebar's toggle shuts it.
|
|
21
21
|
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
22
|
-
import { Bell, Search, Settings2, SquarePen
|
|
22
|
+
import { Bell, Search, Settings2, SquarePen } from 'lucide-react';
|
|
23
23
|
import { DOTS, Grip, nav, NEW, path as written, useSessions } from '@hanzo/build';
|
|
24
24
|
import { DropdownMenu } from '@hanzo/ui';
|
|
25
25
|
import { Sidebar, SidebarIconButton, SidebarItem, StatusDot } from '@hanzo/ui/chat';
|
|
26
|
-
import {
|
|
26
|
+
import { useId } from 'react';
|
|
27
27
|
import { Made } from './Me.js';
|
|
28
28
|
import { Recents } from './Recents.js';
|
|
29
29
|
import { Setup } from './Setup.js';
|
|
@@ -89,11 +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,
|
|
93
|
-
const [words, setWords] = useState('');
|
|
94
|
-
// The search field shows once asked for, and while it holds words.
|
|
95
|
-
const [seeking, setSeeking] = useState(false);
|
|
96
|
-
const field = useRef(null);
|
|
92
|
+
export function Column({ host, mode, thread, go, onShut, onSeek, drawer = false, foot, places, }) {
|
|
97
93
|
// The desktop sidebar and a phone's drawer can both be in the page: each names its own group labels.
|
|
98
94
|
const uid = useId();
|
|
99
95
|
const span = useSpan(SPAN, WIDTH, FLOOR, CEIL);
|
|
@@ -105,27 +101,9 @@ export function Column({ host, mode, thread, go, onShut, seek = 0, drawer = fals
|
|
|
105
101
|
empty();
|
|
106
102
|
go(talk());
|
|
107
103
|
};
|
|
108
|
-
const ask = () => {
|
|
109
|
-
setSeeking(true);
|
|
110
|
-
requestAnimationFrame(() => field.current?.focus());
|
|
111
|
-
};
|
|
112
|
-
const done = () => {
|
|
113
|
-
setWords('');
|
|
114
|
-
setSeeking(false);
|
|
115
|
-
};
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
if (seek)
|
|
118
|
-
ask();
|
|
119
|
-
}, [seek]);
|
|
120
104
|
return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
|
|
121
105
|
// 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 }),
|
|
123
|
-
if (e.key === 'Escape')
|
|
124
|
-
done();
|
|
125
|
-
}, onBlur: () => {
|
|
126
|
-
if (!words)
|
|
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))) })), 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",
|
|
106
|
+
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: "Search", onPress: onSeek, "aria-haspopup": "dialog", ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) })] }), 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, 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
107
|
// In the gutter between the sidebar and the panel, not over the sidebar's own rows.
|
|
130
108
|
r: GUTTER_END, width: "var(--pane-gap)" }))] }));
|
|
131
109
|
}
|
package/dist/Me.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Host } from '@hanzo/build';
|
|
2
|
+
/** What the menu reads of its host: who is signed in, in which organization, and how they leave. */
|
|
3
|
+
export type Who = Pick<Host, 'api' | 'token' | 'org' | 'person' | 'signOut'>;
|
|
2
4
|
/** One organization as the menu draws it. */
|
|
3
5
|
export interface Org {
|
|
4
6
|
name: string;
|
|
@@ -10,7 +12,7 @@ export interface Org {
|
|
|
10
12
|
export declare const initials: (name: string) => string;
|
|
11
13
|
/** The person's picture, or their initials. */
|
|
12
14
|
export declare function Face({ host, size }: {
|
|
13
|
-
host:
|
|
15
|
+
host: Who;
|
|
14
16
|
size?: number;
|
|
15
17
|
}): import("react").JSX.Element;
|
|
16
18
|
/**
|
|
@@ -20,10 +22,13 @@ export declare function Face({ host, size }: {
|
|
|
20
22
|
*/
|
|
21
23
|
export declare const upgrade: (family: string | null | undefined) => string;
|
|
22
24
|
export declare function Me({ host, narrow, above, onOrg, }: {
|
|
23
|
-
host:
|
|
25
|
+
host: Who;
|
|
24
26
|
/** The rail's: the face alone, the menu opening beside it. */
|
|
25
27
|
narrow?: boolean;
|
|
26
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The menu opens above the face, not beside it: the rail laid along a phone
|
|
30
|
+
* drawer's foot. That copy leaves ⌘/ to the rail's own, so one press opens one dialog.
|
|
31
|
+
*/
|
|
27
32
|
above?: boolean;
|
|
28
33
|
/** Work in another organization: picked, then the app opened in it. */
|
|
29
34
|
onOrg: (org: string) => void;
|
package/dist/Me.js
CHANGED
|
@@ -1,51 +1,62 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
// The account row
|
|
3
|
+
// The account row and the menu it opens: the one account menu. The app's rail
|
|
4
|
+
// draws it for Chat, Dev and Studio (Rail.tsx) and the shell's sidebar for every
|
|
5
|
+
// other app (Shell.tsx).
|
|
4
6
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// Top to bottom: who, with their plan, a row whose › opens the account itself;
|
|
8
|
+
// then Upgrade plan, Personalization, Profile and Settings (⌘, / Ctrl+,); then
|
|
9
|
+
// Help ›, the site's own pages and the keyboard shortcuts (⌘/ / Ctrl+/ from
|
|
10
|
+
// anywhere); and Log out. Every row opens a real destination: Settings'
|
|
11
|
+
// sections through `showSettings` (open.ts), which the app answers with the
|
|
12
|
+
// builder's Settings (dev.tsx) and the shell with its own pane; the plans on the
|
|
13
|
+
// pay site (`leave`); the site's pages through `site()`, so hanzo.team names
|
|
14
|
+
// hanzo.ai (help.ts).
|
|
9
15
|
//
|
|
10
|
-
// THE
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// plan
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// personal one. Then Settings, Usage, Billing, API keys, the plans, help and Log
|
|
19
|
-
// out. A SuperAdmin also gets All organizations (support mode).
|
|
16
|
+
// THE ACCOUNT'S › LEADS WITH THE PLAN: @hanzo/build's `Meter`, the plan by its
|
|
17
|
+
// family (`Max` with `20x` beside it) and, on a plan, its windows as shares; the
|
|
18
|
+
// free allowance left today on Free; the balance only for an account with no
|
|
19
|
+
// plan, whose meter it is. Then every organization the token names — the
|
|
20
|
+
// personal one first, with its plan — switched with `onOrg`; Create
|
|
21
|
+
// organization, Organization settings, and Hanzo Team (opened in the same
|
|
22
|
+
// organization for a team, offered as "Create a team" for a personal one); and
|
|
23
|
+
// All organizations for a SuperAdmin (support mode).
|
|
20
24
|
//
|
|
21
25
|
// IAM is the source of every fact here: names and roles from the token's `orgs`
|
|
22
26
|
// claim, display names and which org is personal from `GET /v1/iam/organizations`,
|
|
23
27
|
// the plan from billing and the limits. An organization is created by the same
|
|
24
|
-
// `POST /v1/account/orgs` the no-organization gate uses (
|
|
25
|
-
//
|
|
28
|
+
// `POST /v1/account/orgs` the no-organization gate uses (Orgs.tsx); the next load
|
|
29
|
+
// mints a token that carries it.
|
|
26
30
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { label, Meter,
|
|
31
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
32
|
+
import { ArrowUpRight, Building2, Check, ChevronsUpDown, CircleHelp, CircleUserRound, Download, FileText, Info, Keyboard, LifeBuoy, LogOut, Palette, Plus, ScrollText, Settings, ShieldCheck, Sparkles, Users, X, } from 'lucide-react';
|
|
33
|
+
import { label, Meter, said as spoken, useStanding } from '@hanzo/build';
|
|
30
34
|
import { useOrganizations } from '@hanzo/iam/react';
|
|
31
|
-
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
32
|
-
import { glass } from '@hanzo/ui/glass';
|
|
35
|
+
import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Input, Kbd, } from '@hanzo/ui';
|
|
33
36
|
import { api } from './lib/api.js';
|
|
34
37
|
import { createOrg, say } from './lib/org.js';
|
|
35
38
|
import { list } from './lib/list.js';
|
|
36
39
|
import { org as current, orgs, pick, renew, scope, superAdmin } from './lib/session.js';
|
|
37
40
|
import { ENTRY } from './lib/host.js';
|
|
41
|
+
import { leave } from './dev.js';
|
|
42
|
+
import { asked, KEYS, legal, out, pages } from './help.js';
|
|
43
|
+
import { showSettings } from './open.js';
|
|
38
44
|
import { site, where } from './where.js';
|
|
39
45
|
import { SupportPicker } from './Support.js';
|
|
40
46
|
import { BAD } from './lib/mix.js';
|
|
41
|
-
/** The documentation Get help opens. */
|
|
42
|
-
const DOCS = 'https://docs.hanzo.ai/docs/dev';
|
|
43
47
|
/** Where an organization's own settings live: a page of the site (`site()`), not of the app. */
|
|
44
48
|
const ORG_SETTINGS = '/settings/organization';
|
|
45
|
-
/** The organization's API keys: one home, the platform's keys page, never a second copy in an app. */
|
|
46
|
-
const KEYS = 'https://platform.hanzo.ai/api-keys';
|
|
47
49
|
/** An organization this tab created, until the token carries it. */
|
|
48
50
|
const MADE = 'hanzo:org:made';
|
|
51
|
+
/** Each Help page's icon, by its key in help.ts. */
|
|
52
|
+
const ICONS = {
|
|
53
|
+
help: _jsx(CircleHelp, { size: 16, "aria-hidden": true }),
|
|
54
|
+
center: _jsx(ShieldCheck, { size: 16, "aria-hidden": true }),
|
|
55
|
+
releases: _jsx(ScrollText, { size: 16, "aria-hidden": true }),
|
|
56
|
+
download: _jsx(Download, { size: 16, "aria-hidden": true }),
|
|
57
|
+
terms: _jsx(FileText, { size: 16, "aria-hidden": true }),
|
|
58
|
+
privacy: _jsx(Info, { size: 16, "aria-hidden": true }),
|
|
59
|
+
};
|
|
49
60
|
/** A role as a person reads it: `owner` → Owner. */
|
|
50
61
|
const said = (role) => (role ? role.charAt(0).toUpperCase() + role.slice(1) : '');
|
|
51
62
|
/** The first letters of a name, at most two: Antje Worring → AW. */
|
|
@@ -108,48 +119,12 @@ export function Face({ host, size = 28 }) {
|
|
|
108
119
|
const p = host.person;
|
|
109
120
|
return (_jsx(XStack, { width: size, height: size, rounded: 9999, items: "center", justify: "center", bg: "$raised", overflow: "hidden", shrink: 0, borderWidth: 1, borderColor: "$borderColor", children: p?.avatar ? (_jsx("img", { src: p.avatar, alt: "", width: size, height: size })) : (_jsx(SizableText, { size: "$1", fontWeight: "600", color: "$ink", children: initials(p?.name || p?.email || '') })) }));
|
|
110
121
|
}
|
|
111
|
-
/**
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
return (_jsxs(XStack, { render: "button", ...{ role: radio ? 'menuitemradio' : 'menuitem' }, "aria-checked": radio ? checked : undefined, "data-item": "", onPress: onPress, items: "center", gap: "$2.5", px: "$2", minH: 30, py: 4, rounded: "$3", bg: checked ? '$edge' : 'transparent', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { bg: '$hover', outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", width: "100%", children: [icon ? (_jsx(XStack, { width: 20, justify: "center", shrink: 0, opacity: 0.8, children: icon })) : null, _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, text: "left", children: label }), sub ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: sub })) : null] }), trail] }));
|
|
122
|
+
/** A row's face: its icon in a 16px well, its words, and what trails them. */
|
|
123
|
+
function Line({ icon, children, sub, trail }) {
|
|
124
|
+
return (_jsxs(_Fragment, { children: [_jsx(XStack, { width: 16, height: 16, items: "center", justify: "center", shrink: 0, children: icon }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: children }), sub ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: sub })) : null] }), trail] }));
|
|
115
125
|
}
|
|
116
|
-
const Rule = () => _jsx(XStack, { height: 1, bg: "$borderColor", my: "$1", mx: "$1" });
|
|
117
|
-
const Label = ({ children }) => (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", pt: "$1.5", pb: "$1", children: children }));
|
|
118
126
|
/** An organization's mark in the list: its initials on a rounded square. */
|
|
119
|
-
const Mark = ({ org }) => (_jsx(XStack, { width:
|
|
120
|
-
/** Arrows, Home and End move between the menu's rows; Escape closes it. */
|
|
121
|
-
function rove(e, close) {
|
|
122
|
-
const rows = [...e.currentTarget.querySelectorAll('[data-item]')];
|
|
123
|
-
const at = rows.indexOf(document.activeElement);
|
|
124
|
-
const to = e.key === 'ArrowDown' ? (at + 1) % rows.length : e.key === 'ArrowUp' ? (at - 1 + rows.length) % rows.length : e.key === 'Home' ? 0 : e.key === 'End' ? rows.length - 1 : -2;
|
|
125
|
-
if (e.key === 'Escape') {
|
|
126
|
-
e.preventDefault();
|
|
127
|
-
close();
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
if (to === -2 || !rows.length)
|
|
131
|
-
return;
|
|
132
|
-
e.preventDefault();
|
|
133
|
-
rows[to]?.focus();
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* The menu grows once the plan is read (`Meter`), after it was placed, and the
|
|
137
|
-
* popover follows its anchor, not its own size. So it is placed again whenever
|
|
138
|
-
* its content changes size, and never hangs over the row it opens from.
|
|
139
|
-
*/
|
|
140
|
-
function Refit({ children }) {
|
|
141
|
-
const { update } = usePopperContextSlow();
|
|
142
|
-
const box = useRef(null);
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
const node = box.current;
|
|
145
|
-
if (!node || typeof ResizeObserver === 'undefined')
|
|
146
|
-
return;
|
|
147
|
-
const watch = new ResizeObserver(() => update());
|
|
148
|
-
watch.observe(node);
|
|
149
|
-
return () => watch.disconnect();
|
|
150
|
-
}, [update]);
|
|
151
|
-
return (_jsx(View, { render: _jsx("div", { ref: box }), self: "stretch", minW: 0, children: children }));
|
|
152
|
-
}
|
|
127
|
+
const Mark = ({ org }) => (_jsx(XStack, { width: 16, height: 16, rounded: "$2", items: "center", justify: "center", bg: "$raised", shrink: 0, children: org.personal ? (_jsx(SizableText, { size: "$1", fontWeight: "600", color: "$ink", children: initials(org.display).charAt(0) })) : (_jsx(Building2, { size: 12, "aria-hidden": true })) }));
|
|
153
128
|
/**
|
|
154
129
|
* The plans row, named by the plan the organization is on: Enterprise and
|
|
155
130
|
* Agency are plans a person stays on, so the row names theirs; every other
|
|
@@ -158,66 +133,62 @@ function Refit({ children }) {
|
|
|
158
133
|
export const upgrade = (family) => family === 'Enterprise' ? 'Enterprise plan' : family === 'Agency' ? 'Agency plan' : 'Upgrade plan';
|
|
159
134
|
/** The keys a row prints, in this platform's spelling. */
|
|
160
135
|
const mac = () => typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
|
|
161
|
-
/** A row's keys at its end, quiet. */
|
|
162
|
-
const Keys = ({ children }) => (_jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: children }));
|
|
163
|
-
/** A row that opens a page of the menu. */
|
|
164
|
-
const More = () => _jsx(ChevronRight, { size: 14, "aria-hidden": true, opacity: 0.6 });
|
|
165
136
|
export function Me({ host, narrow = false, above = false, onOrg, }) {
|
|
166
|
-
const
|
|
167
|
-
const [
|
|
137
|
+
const [open, setOpen] = useState(false);
|
|
138
|
+
const [account, setAccount] = useState(false);
|
|
139
|
+
const [help, setHelp] = useState(false);
|
|
168
140
|
const [creating, setCreating] = useState(false);
|
|
169
141
|
const [supporting, setSupporting] = useState(false);
|
|
142
|
+
const [keys, setKeys] = useState(false);
|
|
170
143
|
const row = useRef(null);
|
|
171
|
-
const content = useRef(null);
|
|
172
144
|
const current = host.org;
|
|
173
145
|
const all = useOrgs(current);
|
|
174
146
|
const here = all.find((o) => o.name === current) ?? null;
|
|
175
147
|
const personal = all.find((o) => o.personal) ?? null;
|
|
176
148
|
const target = useMemo(() => ({ api: host.api, token: host.token, org: host.org }), [host.api, host.token, host.org]);
|
|
177
|
-
const read = useStanding(target,
|
|
178
|
-
const other = usePlan(personal && personal.name !== current ? personal.name : null,
|
|
149
|
+
const read = useStanding(target, open);
|
|
150
|
+
const other = usePlan(personal && personal.name !== current ? personal.name : null, open);
|
|
179
151
|
const planFor = (o) => (o.name === current ? spoken(read.value?.label ?? null) : o.personal ? other : '');
|
|
180
152
|
const who = host.person?.name || host.person?.email || '';
|
|
181
153
|
const plan = spoken(read.value?.label ?? null);
|
|
182
154
|
const apple = mac();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const land = (node) => {
|
|
186
|
-
content.current = node;
|
|
187
|
-
if (!node)
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (above)
|
|
188
157
|
return;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
158
|
+
const press = (e) => {
|
|
159
|
+
if (!asked(e))
|
|
160
|
+
return;
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
setOpen(false);
|
|
163
|
+
setKeys(true);
|
|
164
|
+
};
|
|
165
|
+
window.addEventListener('keydown', press);
|
|
166
|
+
return () => window.removeEventListener('keydown', press);
|
|
167
|
+
}, [above]);
|
|
168
|
+
// Focus goes back to the row only when nothing the menu opened (a dialog) took it.
|
|
169
|
+
const back = (e) => {
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
requestAnimationFrame(() => {
|
|
172
|
+
const now = document.activeElement;
|
|
173
|
+
if (!now || now === document.body)
|
|
174
|
+
row.current?.focus();
|
|
175
|
+
});
|
|
206
176
|
};
|
|
207
|
-
const back = (title) => _jsx(Item, { icon: _jsx(ChevronLeft, { size: 15, "aria-hidden": true }), label: title, onPress: () => turn('main') });
|
|
208
177
|
if (!host.person)
|
|
209
178
|
return null;
|
|
210
|
-
return (_jsxs(_Fragment, { children: [_jsxs(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
179
|
+
return (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu, { open: open, onOpenChange: (next) => {
|
|
180
|
+
setOpen(next);
|
|
181
|
+
if (next)
|
|
182
|
+
return;
|
|
183
|
+
setAccount(false);
|
|
184
|
+
setHelp(false);
|
|
185
|
+
}, placement: narrow && !above ? 'right-end' : above ? 'top-end' : 'top-start', children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(XStack, { ref: (el) => {
|
|
215
186
|
row.current = el instanceof HTMLElement ? el : null;
|
|
216
|
-
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, ...{ title: who },
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
187
|
+
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, ...{ title: who }, width: narrow ? 36 : '100%', minH: narrow ? 36 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 26 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), plan ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: plan })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsxs(DropdownMenuContent, { minW: 260, maxW: "calc(100vw - 16px)", "aria-labelledby": undefined, "aria-label": "Account", onCloseAutoFocus: back, children: [_jsxs(DropdownMenuSub, { open: account, onOpenChange: setAccount, children: [_jsxs(DropdownMenuSubTrigger, { minH: 48, gap: "$2.5", children: [_jsx(Face, { host: host, size: 28 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", fontWeight: "600", color: "$ink", numberOfLines: 1, children: who }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: plan || host.person.email || '' })] })] }), account ? (_jsxs(DropdownMenuSubContent, { minW: 260, maxW: "calc(100vw - 16px)", children: [_jsx(Meter, { read: read, onPlans: () => leave('here'), onBilling: () => (setOpen(false), showSettings('billing')) }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: "Organizations" }), all.map((o) => (_jsx(DropdownMenuItem, { onSelect: () => o.name !== current && onOrg(o.name), children: _jsx(Line, { icon: _jsx(Mark, { org: o }), sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), trail: o.name === current ? _jsx(Check, { size: 16, "aria-label": "Current" }) : null, children: o.display }) }, o.name))), _jsx(DropdownMenuItem, { onSelect: () => setCreating(true), children: _jsx(Line, { icon: _jsx(Plus, { size: 16, "aria-hidden": true }), children: "Create organization" }) }), _jsx(DropdownMenuItem, { onSelect: () => window.location.assign(site(ORG_SETTINGS)), children: _jsx(Line, { icon: _jsx(Settings, { size: 16, "aria-hidden": true }), children: "Organization settings" }) }), where().team ? null : here && !here.personal ? (_jsx(DropdownMenuItem, { onSelect: () => out(`${ENTRY}&org=${encodeURIComponent(here.name)}`), children: _jsx(Line, { icon: _jsx(Users, { size: 16, "aria-hidden": true }), trail: _jsx(ArrowUpRight, { size: 14, "aria-hidden": true, opacity: 0.6 }), children: "Open in Hanzo Team" }) })) : (_jsx(DropdownMenuItem, { onSelect: () => setCreating(true), children: _jsx(Line, { icon: _jsx(Users, { size: 16, "aria-hidden": true }), children: "Create a team" }) })), superAdmin() ? (_jsx(DropdownMenuItem, { onSelect: () => setSupporting(true), children: _jsx(Line, { icon: _jsx(Building2, { size: 16, "aria-hidden": true }), children: "All organizations" }) })) : null] })) : null] }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { onSelect: () => leave('here'), children: _jsx(Line, { icon: _jsx(Sparkles, { size: 16, "aria-hidden": true }), children: upgrade(read.value?.label?.name) }) }), _jsx(DropdownMenuItem, { onSelect: () => showSettings('general'), children: _jsx(Line, { icon: _jsx(Palette, { size: 16, "aria-hidden": true }), children: "Personalization" }) }), _jsx(DropdownMenuItem, { onSelect: () => showSettings('account'), children: _jsx(Line, { icon: _jsx(CircleUserRound, { size: 16, "aria-hidden": true }), children: "Profile" }) }), _jsx(DropdownMenuItem, { "aria-keyshortcuts": "Control+, Meta+,", onSelect: () => showSettings('general'), children: _jsx(Line, { icon: _jsx(Settings, { size: 16, "aria-hidden": true }), trail: _jsx(DropdownMenuShortcut, { "aria-hidden": true, children: apple ? '⌘,' : 'Ctrl+,' }), children: "Settings" }) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuSub, { open: help, onOpenChange: setHelp, children: [_jsx(DropdownMenuSubTrigger, { children: _jsx(Line, { icon: _jsx(LifeBuoy, { size: 16, "aria-hidden": true }), children: "Help" }) }), help ? (_jsxs(DropdownMenuSubContent, { minW: 240, maxW: "calc(100vw - 16px)", children: [pages().map((p) => (_jsx(DropdownMenuItem, { onSelect: () => out(p.href), children: _jsx(Line, { icon: ICONS[p.key], trail: p.key === 'help' ? _jsx(ArrowUpRight, { size: 14, "aria-hidden": true, opacity: 0.6 }) : null, children: p.label }) }, p.key))), _jsx(DropdownMenuItem, { "aria-keyshortcuts": "Control+/ Meta+/", onSelect: () => setKeys(true), children: _jsx(Line, { icon: _jsx(Keyboard, { size: 16, "aria-hidden": true }), trail: _jsx(DropdownMenuShortcut, { "aria-hidden": true, children: apple ? '⌘/' : 'Ctrl+/' }), children: "Keyboard shortcuts" }) }), _jsx(DropdownMenuSeparator, {}), legal().map((p) => (_jsx(DropdownMenuItem, { onSelect: () => out(p.href), children: _jsx(Line, { icon: ICONS[p.key], children: p.label }) }, p.key)))] })) : null] }), host.signOut ? (_jsx(DropdownMenuItem, { onSelect: () => host.signOut?.(), children: _jsx(Line, { icon: _jsx(LogOut, { size: 16, "aria-hidden": true }), children: "Log out" }) })) : null] })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), _jsx(Shortcuts, { open: keys, onOpenChange: setKeys, apple: apple }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null] }));
|
|
188
|
+
}
|
|
189
|
+
/** The app's keys (help.ts `KEYS`), one row each, spelled for this platform. */
|
|
190
|
+
function Shortcuts({ open, onOpenChange, apple }) {
|
|
191
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 440, rounded: "$6", gap: "$3", children: [_jsx(DialogTitle, { children: "Keyboard shortcuts" }), _jsx(YStack, { gap: "$1", children: KEYS.map((k) => (_jsxs(XStack, { items: "center", justify: "space-between", gap: "$3", minH: 32, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: k.does }), _jsx(Kbd, { children: apple ? k.mac : k.pc })] }, k.does))) })] }) }));
|
|
221
192
|
}
|
|
222
193
|
/**
|
|
223
194
|
* The sheet that creates an organization: a name, then `POST /v1/account/orgs`,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ShortcutKeys } from '@hanzo/ui';
|
|
3
|
+
import { type Hit } from './seek.js';
|
|
4
|
+
/** A row: what it is called, its glyph, and what pressing it does. */
|
|
5
|
+
export interface Action extends Hit {
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
shortcut?: ShortcutKeys;
|
|
8
|
+
run: () => void;
|
|
9
|
+
/** Pressing it keeps the palette open. */
|
|
10
|
+
stay?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function Palette({ onClose, go, actions, }: {
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
/** Moves the app to one of its addresses (lib/host `talk`, `app`). */
|
|
15
|
+
go: (address: string) => void;
|
|
16
|
+
/** The frame's own actions, after the palette's; one with a palette action's id stands in for it. */
|
|
17
|
+
actions?: Action[];
|
|
18
|
+
}): import("react").JSX.Element;
|
package/dist/Palette.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// The one search. ⌘K / Ctrl+K from anywhere and the sidebar's search open it:
|
|
4
|
+
// a dialog with the field on top, listing Recent chats while the field is empty
|
|
5
|
+
// and, for a query, Chats, Projects, Agents, Skills and Actions, each cut to its
|
|
6
|
+
// best five (seek.ts `find`). A row opens where it lives: a chat at its
|
|
7
|
+
// address, a project in Dev, an agent in a new chat with it, a skill in Dev's
|
|
8
|
+
// Customize, an action runs. Arrows move, Enter opens, Escape closes
|
|
9
|
+
// (@hanzo/ui's CommandMenu); Keyboard shortcuts lists the app's keys, and
|
|
10
|
+
// Backspace on an empty field comes back.
|
|
11
|
+
//
|
|
12
|
+
// MOUNTED ONLY WHILE OPEN, and every list is read once as it opens, then
|
|
13
|
+
// searched here as the reader types: the conversations store (hanzoai/agent)
|
|
14
|
+
// answers its whole list and searches nothing, titles or words, so there is no
|
|
15
|
+
// read per key to wait for or to cancel. The chats are the sidebar's last read
|
|
16
|
+
// (thread.tsx `useThreadList`), drawn at once and read again behind it; the
|
|
17
|
+
// projects (`/v1/projects`), the active skills (`/v1/tool/skills?activated=true`)
|
|
18
|
+
// and the agents (crew.tsx) arrive while the reader types, and a read the
|
|
19
|
+
// palette closes on is aborted.
|
|
20
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
21
|
+
import { BookOpen, Bot, Code2, FolderGit2, Keyboard, LogOut, MessageCircle, Puzzle, Settings, SquarePen } from 'lucide-react';
|
|
22
|
+
import { useAi } from '@hanzo/ai/react';
|
|
23
|
+
import { path as written } from '@hanzo/build';
|
|
24
|
+
import { Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
|
|
25
|
+
import { CommandMenu } from '@hanzo/ui/product/CommandMenu';
|
|
26
|
+
import { ordered } from './agents.js';
|
|
27
|
+
import { useCrew } from './crew.js';
|
|
28
|
+
import { called } from './team.js';
|
|
29
|
+
import { empty, openThread, withAgents } from './open.js';
|
|
30
|
+
import { useThreadList } from './thread.js';
|
|
31
|
+
import { useSignOut } from './lib/signout.js';
|
|
32
|
+
import { app, talk } from './lib/host.js';
|
|
33
|
+
import { find } from './seek.js';
|
|
34
|
+
const ICON = { size: 16, 'aria-hidden': true };
|
|
35
|
+
/** The app's keys, as Keyboard shortcuts lists them. */
|
|
36
|
+
const KEYS = [
|
|
37
|
+
{ id: 'search', label: 'Search', shortcut: ['Mod', 'K'] },
|
|
38
|
+
{ id: 'settings', label: 'Settings', shortcut: ['Mod', ','] },
|
|
39
|
+
{ id: 'panel', label: 'Side panel', shortcut: ['Mod', '.'] },
|
|
40
|
+
{ id: 'rename', label: 'Rename chat', shortcut: ['Ctrl', 'Alt', 'R'] },
|
|
41
|
+
{ id: 'pin', label: 'Pin or unpin chat', shortcut: ['Ctrl', 'Alt', 'P'] },
|
|
42
|
+
{ id: 'back', label: 'Back, in the desktop app', shortcut: ['Alt', 'ArrowLeft'] },
|
|
43
|
+
{ id: 'forward', label: 'Forward, in the desktop app', shortcut: ['Alt', 'ArrowRight'] },
|
|
44
|
+
];
|
|
45
|
+
const SKILLS = written({ kind: 'customize', tab: 'skills' });
|
|
46
|
+
/** A list read once as the palette opens; a refusal leaves it empty and the rest still answer. */
|
|
47
|
+
function useRead(path, shape) {
|
|
48
|
+
const ai = useAi();
|
|
49
|
+
const [rows, setRows] = useState([]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const stop = new AbortController();
|
|
52
|
+
ai.http.json({ path, signal: stop.signal }).then((body) => setRows(shape(body)), () => { });
|
|
53
|
+
return () => stop.abort();
|
|
54
|
+
// `shape` is a module constant at each call.
|
|
55
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
56
|
+
}, [ai, path]);
|
|
57
|
+
return rows;
|
|
58
|
+
}
|
|
59
|
+
const obj = (v) => (v && typeof v === 'object' ? v : {});
|
|
60
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
61
|
+
const rows = (v) => (Array.isArray(v) ? v : []);
|
|
62
|
+
/** `/v1/projects`: a project by its slug, found by its name, slug and repository. */
|
|
63
|
+
const projects = (body) => (Array.isArray(body) ? body : rows(obj(body).data))
|
|
64
|
+
.map(obj)
|
|
65
|
+
.filter((p) => str(p.slug))
|
|
66
|
+
.map((p) => ({ id: str(p.slug), label: str(p.name) || str(p.slug), keywords: [str(p.slug), str(obj(p.repo).url)] }));
|
|
67
|
+
/** `/v1/tool/skills?activated=true`: a skill by its name, its tool's less `skill_`. */
|
|
68
|
+
const skills = (body) => rows(obj(body).tools)
|
|
69
|
+
.map(obj)
|
|
70
|
+
.filter((t) => str(t.name))
|
|
71
|
+
.map((t) => ({ id: str(t.name), label: str(t.name).replace(/^skill_/, ''), keywords: [str(t.description)] }));
|
|
72
|
+
export function Palette({ onClose, go, actions = [], }) {
|
|
73
|
+
const [query, setQuery] = useState('');
|
|
74
|
+
const [keys, setKeys] = useState(false);
|
|
75
|
+
const { threads } = useThreadList();
|
|
76
|
+
const { agents } = useCrew();
|
|
77
|
+
const made = useRead('/v1/projects', projects);
|
|
78
|
+
const known = useRead('/v1/tool/skills?activated=true', skills);
|
|
79
|
+
const signOut = useSignOut();
|
|
80
|
+
const groups = useMemo(() => {
|
|
81
|
+
const own = [
|
|
82
|
+
{ id: 'new-chat', label: 'New chat', icon: _jsx(SquarePen, { ...ICON }), run: () => (empty(), go(talk())) },
|
|
83
|
+
{ id: 'new-task', label: 'New Dev task', icon: _jsx(Code2, { ...ICON }), run: () => go(app('')) },
|
|
84
|
+
{ id: 'settings', label: 'Settings', icon: _jsx(Settings, { ...ICON }), shortcut: ['Mod', ','], run: () => go(app(written({ kind: 'settings', section: 'general' }))) },
|
|
85
|
+
{ id: 'customize', label: 'Customize', icon: _jsx(Puzzle, { ...ICON }), run: () => go(app(SKILLS)) },
|
|
86
|
+
{ id: 'keys', label: 'Keyboard shortcuts', icon: _jsx(Keyboard, { ...ICON }), stay: true, run: () => (setQuery(''), setKeys(true)) },
|
|
87
|
+
{ id: 'logout', label: 'Log out', icon: _jsx(LogOut, { ...ICON }), run: () => void signOut() },
|
|
88
|
+
];
|
|
89
|
+
const doing = [...own.map((a) => actions.find((b) => b.id === a.id) ?? a), ...actions.filter((b) => !own.some((a) => a.id === b.id))];
|
|
90
|
+
const chats = threads.map((t) => ({
|
|
91
|
+
id: `chat-${t.id}`,
|
|
92
|
+
label: t.title || 'Untitled',
|
|
93
|
+
at: t.updatedAt,
|
|
94
|
+
icon: _jsx(MessageCircle, { ...ICON }),
|
|
95
|
+
run: () => (openThread(t.id), go(talk(t.id))),
|
|
96
|
+
}));
|
|
97
|
+
const shown = find(query, chats, [
|
|
98
|
+
{ id: 'projects', label: 'Projects', items: made.map((p) => ({ ...p, id: `project-${p.id}`, icon: _jsx(FolderGit2, { ...ICON }), run: () => go(app(p.id)) })) },
|
|
99
|
+
{
|
|
100
|
+
id: 'agents',
|
|
101
|
+
label: 'Agents',
|
|
102
|
+
items: ordered(agents).map((one) => {
|
|
103
|
+
const name = called(one.name);
|
|
104
|
+
return { id: `agent-${one.id || one.name}`, label: name, icon: _jsx(Bot, { ...ICON }), run: () => (withAgents([name]), go(talk())) };
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
{ id: 'skills', label: 'Skills', items: known.map((s) => ({ ...s, id: `skill-${s.id}`, icon: _jsx(BookOpen, { ...ICON }), run: () => go(app(SKILLS)) })) },
|
|
108
|
+
{ id: 'actions', label: 'Actions', items: doing },
|
|
109
|
+
]);
|
|
110
|
+
return shown.map((g) => ({
|
|
111
|
+
...g,
|
|
112
|
+
items: g.items.map((a) => ({
|
|
113
|
+
id: a.id,
|
|
114
|
+
label: a.label,
|
|
115
|
+
icon: a.icon,
|
|
116
|
+
shortcut: a.shortcut,
|
|
117
|
+
// The palette ranked these against the query; the menu's own filter passes what it kept.
|
|
118
|
+
keywords: [query],
|
|
119
|
+
onSelect: () => {
|
|
120
|
+
if (!a.stay)
|
|
121
|
+
onClose();
|
|
122
|
+
a.run();
|
|
123
|
+
},
|
|
124
|
+
})),
|
|
125
|
+
}));
|
|
126
|
+
}, [query, threads, agents, made, known, actions, go, onClose, signOut]);
|
|
127
|
+
const listed = keys
|
|
128
|
+
? [{ id: 'keys', label: 'Keyboard shortcuts', items: KEYS.map((k) => ({ ...k, id: `key-${k.id}`, onSelect: () => setKeys(false) })) }]
|
|
129
|
+
: groups;
|
|
130
|
+
return (_jsx(Dialog, { open: true, onOpenChange: (open) => (open ? null : onClose()), children: _jsxs(DialogContent, { "data-slot": "palette", p: 0, gap: 0, width: "100%", maxW: 680, height: "min(520px, calc(100dvh - 32px))", rounded: "$6", overflow: "hidden", "$max-md": { width: '100vw', maxW: '100vw', height: '100dvh', rounded: 0, borderWidth: 0 }, children: [_jsx(DialogTitle, { position: "absolute", width: 1, height: 1, overflow: "hidden", opacity: 0, children: "Search" }), _jsx(CommandMenu, { groups: listed, query: query, onQueryChange: setQuery, placeholder: "Search\u2026", label: "Search", empty: keys ? 'No such key.' : query.trim() ? `Nothing matches “${query.trim()}”.` : 'No chats yet.', onBack: keys ? () => setKeys(false) : undefined, onClose: onClose })] }) }));
|
|
131
|
+
}
|
package/dist/Rail.js
CHANGED
|
@@ -3,7 +3,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
// The app's rail: its products as marks down the window's left edge, in one
|
|
4
4
|
// order — Chat, Dev, Studio, Library, Scheduled tasks, Customize — then More, a
|
|
5
5
|
// menu of the rest, and the account at the foot. It is how the app moves
|
|
6
|
-
// between its products;
|
|
6
|
+
// between its products; a phone, which has no rail, switches Chat and Dev in
|
|
7
|
+
// its bar (Top.tsx).
|
|
7
8
|
//
|
|
8
9
|
// Studio is the app's third mode (@hanzo/studio at /studio), each of the three
|
|
9
10
|
// opened where the reader left it.
|
|
@@ -26,26 +27,13 @@ import { Boxes, Clock, Code2, Ellipsis, FolderGit2, LayoutTemplate, Library, Lis
|
|
|
26
27
|
import { XStack, YStack } from '@hanzo/gui';
|
|
27
28
|
import { route } from '@hanzo/build';
|
|
28
29
|
import { DropdownMenu } from '@hanzo/ui';
|
|
30
|
+
import { out } from './help.js';
|
|
29
31
|
import { Me } from './Me.js';
|
|
30
32
|
import { app, TABS, WORKSPACE } from './lib/host.js';
|
|
31
33
|
/** The rail's width, px. */
|
|
32
34
|
export const RAIL = 48;
|
|
33
35
|
/** A mark, px: a 36px square on a 44px pitch. */
|
|
34
36
|
const MARK = 36;
|
|
35
|
-
/**
|
|
36
|
-
* Opens an address of another product in a tab of its own, through an anchor,
|
|
37
|
-
* so a host that sends links out of its window (the desktop's system browser)
|
|
38
|
-
* hears it as the link it is.
|
|
39
|
-
*/
|
|
40
|
-
function out(href) {
|
|
41
|
-
const a = document.createElement('a');
|
|
42
|
-
a.href = href;
|
|
43
|
-
a.target = '_blank';
|
|
44
|
-
a.rel = 'noopener noreferrer';
|
|
45
|
-
document.body.appendChild(a);
|
|
46
|
-
a.click();
|
|
47
|
-
a.remove();
|
|
48
|
-
}
|
|
49
37
|
/** The places the rail raises out of Dev, by the builder address each opens. */
|
|
50
38
|
const RAISED = { library: '-/artifacts', scheduled: '-/automations', customize: '-/customize' };
|
|
51
39
|
/** Which raised place a builder address stands on, if any. */
|
package/dist/Recents.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { type Host } from '@hanzo/build';
|
|
2
2
|
import { type Mode, type Recent } from './recent.js';
|
|
3
|
-
export declare function Recents({ host, mode, thread,
|
|
3
|
+
export declare function Recents({ host, mode, thread, onOpen, onNew, }: {
|
|
4
4
|
host: Host;
|
|
5
5
|
mode: Mode;
|
|
6
6
|
/** The Chat conversation the pane holds, or null. */
|
|
7
7
|
thread: string | null;
|
|
8
|
-
words: string;
|
|
9
8
|
onOpen: (r: Recent) => void;
|
|
10
9
|
/** Moves the pane to a new conversation: where it goes when the one it holds is deleted. */
|
|
11
10
|
onNew: () => void;
|