@hanzo/rooms 0.1.57 → 0.1.58
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/App.d.ts +13 -1
- package/dist/App.js +117 -36
- package/dist/Chat.js +20 -11
- package/dist/Column.d.ts +10 -6
- package/dist/Column.js +77 -54
- package/dist/Home.js +7 -7
- package/dist/Inbox.js +1 -1
- package/dist/Me.d.ts +7 -1
- package/dist/Me.js +35 -8
- package/dist/Meet.js +1 -1
- package/dist/Prose.js +16 -9
- package/dist/Rail.d.ts +39 -0
- package/dist/Rail.js +85 -0
- package/dist/Recents.js +21 -12
- package/dist/RightPane.d.ts +15 -0
- package/dist/RightPane.js +38 -9
- package/dist/Share.js +2 -2
- package/dist/Shell.js +7 -8
- package/dist/Talk.js +3 -3
- package/dist/recent.js +1 -1
- package/dist/rooms.js +2 -2
- package/dist/thread.d.ts +8 -0
- package/dist/thread.js +15 -0
- package/dist/where.d.ts +2 -0
- package/dist/where.js +2 -0
- package/package.json +1 -1
- package/dist/ModeMark.d.ts +0 -8
- package/dist/ModeMark.js +0 -19
package/dist/Inbox.js
CHANGED
package/dist/Me.d.ts
CHANGED
|
@@ -13,9 +13,15 @@ export declare function Face({ host, size }: {
|
|
|
13
13
|
host: Host;
|
|
14
14
|
size?: number;
|
|
15
15
|
}): import("react").JSX.Element;
|
|
16
|
+
/**
|
|
17
|
+
* The plans row, named by the plan the organization is on: Enterprise and
|
|
18
|
+
* Agency are plans a person stays on, so the row names theirs; every other
|
|
19
|
+
* plan, Free included, is one to move up from.
|
|
20
|
+
*/
|
|
21
|
+
export declare const upgrade: (family: string | null | undefined) => string;
|
|
16
22
|
export declare function Me({ host, narrow, onOrg, }: {
|
|
17
23
|
host: Host;
|
|
18
|
-
/** The
|
|
24
|
+
/** The rail's: the face alone, the menu opening beside it. */
|
|
19
25
|
narrow?: boolean;
|
|
20
26
|
/** Work in another organization: picked, then the app opened in it. */
|
|
21
27
|
onOrg: (org: string) => void;
|
package/dist/Me.js
CHANGED
|
@@ -25,7 +25,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
25
25
|
// Orgs.tsx); the next load mints a token that carries it.
|
|
26
26
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
27
27
|
import { Popover, SizableText, usePopperContextSlow, View, XStack, YStack } from '@hanzo/gui';
|
|
28
|
-
import { Building2, ChevronsUpDown, CreditCard, ExternalLink, Gauge, KeyRound, LifeBuoy, LogOut, Plus, Settings, Sparkles, Users, X } from 'lucide-react';
|
|
28
|
+
import { Building2, ChevronLeft, ChevronRight, ChevronsUpDown, CreditCard, ExternalLink, Gauge, KeyRound, LifeBuoy, LogOut, Mail, Plus, Settings, Sparkles, Users, X } from 'lucide-react';
|
|
29
29
|
import { label, Meter, path, said as spoken, useStanding, useWho } from '@hanzo/build';
|
|
30
30
|
import { useOrganizations } from '@hanzo/iam/react';
|
|
31
31
|
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
@@ -111,7 +111,7 @@ export function Face({ host, size = 28 }) {
|
|
|
111
111
|
/** One row of the menu: a real button, so Enter, Space and focus are the browser's. */
|
|
112
112
|
function Item({ icon, label, sub, checked, onPress, trail, }) {
|
|
113
113
|
const radio = checked !== undefined;
|
|
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:
|
|
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] }));
|
|
115
115
|
}
|
|
116
116
|
const Rule = () => _jsx(XStack, { height: 1, bg: "$borderColor", my: "$1", mx: "$1" });
|
|
117
117
|
const Label = ({ children }) => (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", pt: "$1.5", pb: "$1", children: children }));
|
|
@@ -150,8 +150,21 @@ function Refit({ children }) {
|
|
|
150
150
|
}, [update]);
|
|
151
151
|
return (_jsx(View, { render: _jsx("div", { ref: box }), self: "stretch", minW: 0, children: children }));
|
|
152
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* The plans row, named by the plan the organization is on: Enterprise and
|
|
155
|
+
* Agency are plans a person stays on, so the row names theirs; every other
|
|
156
|
+
* plan, Free included, is one to move up from.
|
|
157
|
+
*/
|
|
158
|
+
export const upgrade = (family) => family === 'Enterprise' ? 'Enterprise plan' : family === 'Agency' ? 'Agency plan' : 'Upgrade plan';
|
|
159
|
+
/** The keys a row prints, in this platform's spelling. */
|
|
160
|
+
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 });
|
|
153
165
|
export function Me({ host, narrow = false, onOrg, }) {
|
|
154
166
|
const menu = useWho();
|
|
167
|
+
const [page, setPage] = useState('main');
|
|
155
168
|
const [creating, setCreating] = useState(false);
|
|
156
169
|
const [supporting, setSupporting] = useState(false);
|
|
157
170
|
const row = useRef(null);
|
|
@@ -165,15 +178,24 @@ export function Me({ host, narrow = false, onOrg, }) {
|
|
|
165
178
|
const other = usePlan(personal && personal.name !== current ? personal.name : null, menu.open);
|
|
166
179
|
const planFor = (o) => (o.name === current ? spoken(read.value?.label ?? null) : o.personal ? other : '');
|
|
167
180
|
const who = host.person?.name || host.person?.email || '';
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
//
|
|
181
|
+
const plan = spoken(read.value?.label ?? null);
|
|
182
|
+
const apple = mac();
|
|
183
|
+
// Each page opens on its first row, once it is drawn — after the popover has
|
|
184
|
+
// placed its own focus — so the keys reach the rows at once.
|
|
171
185
|
const land = (node) => {
|
|
172
186
|
content.current = node;
|
|
173
187
|
if (!node)
|
|
174
188
|
return;
|
|
175
189
|
requestAnimationFrame(() => requestAnimationFrame(() => (node.querySelector('[data-item][aria-checked="true"]') ?? node.querySelector('[data-item]'))?.focus()));
|
|
176
190
|
};
|
|
191
|
+
const turn = (to) => {
|
|
192
|
+
setPage(to);
|
|
193
|
+
requestAnimationFrame(() => land(content.current));
|
|
194
|
+
};
|
|
195
|
+
const toggle = () => {
|
|
196
|
+
setPage('main');
|
|
197
|
+
menu.toggle();
|
|
198
|
+
};
|
|
177
199
|
const close = () => {
|
|
178
200
|
menu.onOpenChange(false);
|
|
179
201
|
row.current?.focus();
|
|
@@ -182,15 +204,20 @@ export function Me({ host, narrow = false, onOrg, }) {
|
|
|
182
204
|
menu.onOpenChange(false);
|
|
183
205
|
host.go(p);
|
|
184
206
|
};
|
|
207
|
+
const back = (title) => _jsx(Item, { icon: _jsx(ChevronLeft, { size: 15, "aria-hidden": true }), label: title, onPress: () => turn('main') });
|
|
185
208
|
if (!host.person)
|
|
186
209
|
return null;
|
|
187
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Popover, { open: menu.open, onOpenChange:
|
|
210
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Popover, { open: menu.open, onOpenChange: (open) => {
|
|
211
|
+
if (open)
|
|
212
|
+
setPage('main');
|
|
213
|
+
menu.onOpenChange(open);
|
|
214
|
+
}, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 8, children: [_jsx(Popover.Anchor, { width: narrow ? undefined : '100%', children: _jsxs(XStack, { ref: (el) => {
|
|
188
215
|
row.current = el instanceof HTMLElement ? el : null;
|
|
189
|
-
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress:
|
|
216
|
+
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, ...{ title: who }, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: toggle, 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 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$5", p: "$1", width: 240, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(Refit, { children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: page === 'main' ? (_jsxs(YStack, { gap: 1, children: [_jsxs(XStack, { items: "center", gap: "$2.5", px: "$2", pt: "$1.5", pb: "$2", 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 || '' })] })] }), _jsx(Rule, {}), _jsx(Item, { icon: _jsx(Gauge, { size: 15, "aria-hidden": true }), label: "Usage remaining", trail: _jsx(More, {}), onPress: () => turn('usage') }), _jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: here?.display ?? current ?? 'Organization', sub: "Organization", trail: _jsx(More, {}), onPress: () => turn('orgs') }), _jsx(Item, { icon: _jsx(KeyRound, { size: 15, "aria-hidden": true }), label: "API keys", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(KEYS)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Settings", trail: _jsx(Keys, { children: apple ? '⌘,' : 'Ctrl+,' }), onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(Rule, {}), _jsx(Item, { icon: _jsx(LifeBuoy, { size: 15, "aria-hidden": true }), label: "Help", trail: _jsx(More, {}), onPress: () => turn('help') }), host.signOut ? _jsx(Item, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), label: "Log out", onPress: () => (menu.onOpenChange(false), host.signOut?.()) }) : null] })) : page === 'usage' ? (_jsxs(YStack, { gap: 1, children: [back('Usage remaining'), _jsx(Rule, {}), _jsx(Meter, { read: read, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Rule, {}), _jsx(Item, { icon: _jsx(Gauge, { size: 15, "aria-hidden": true }), label: "Usage", onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(Item, { icon: _jsx(CreditCard, { size: 15, "aria-hidden": true }), label: "Billing", onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: upgrade(read.value?.label?.name), onPress: () => go(path({ kind: 'screen', screen: 'plans' })) })] })) : page === 'orgs' ? (_jsxs(YStack, { gap: 1, children: [back('Organization'), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
|
|
190
217
|
menu.onOpenChange(false);
|
|
191
218
|
if (o.name !== current)
|
|
192
219
|
onOrg(o.name);
|
|
193
|
-
} }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(site(ORG_SETTINGS))) }), where().team ? null : here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null
|
|
220
|
+
} }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(site(ORG_SETTINGS))) }), where().team ? null : here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null] })) : (_jsxs(YStack, { gap: 1, children: [back('Help'), _jsx(Rule, {}), _jsx(Item, { icon: _jsx(LifeBuoy, { size: 15, "aria-hidden": true }), label: "Get help", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.open(DOCS, '_blank', 'noopener,noreferrer')) }), _jsx(Item, { icon: _jsx(Mail, { size: 15, "aria-hidden": true }), label: "Contact us", onPress: () => (menu.onOpenChange(false), window.location.assign(site('/contact'))) })] })) }) }) })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null] }));
|
|
194
221
|
}
|
|
195
222
|
/**
|
|
196
223
|
* The sheet that creates an organization: a name, then `POST /v1/account/orgs`,
|
package/dist/Meet.js
CHANGED
|
@@ -273,7 +273,7 @@ export function Meet() {
|
|
|
273
273
|
return (_jsxs(YStack, { width: "100%", height: "100%", bg: "$background", overflowX: "auto", overflowY: "auto", children: [_jsxs(XStack, { minH: 56, px: 20, py: 8, gap: 10, flexWrap: "wrap", bg: "$panel", borderBottomWidth: 1, borderBottomColor: "$borderColor", items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: 12, shrink: 1, minW: "auto", children: [_jsx(XStack, { width: 32, height: 32, rounded: 8, bg: "$raised", items: "center", justify: "center", shrink: 1, minW: "auto", children: _jsx(Video, { size: 18, color: "var(--foreground)" }) }), _jsxs(YStack, { shrink: 1, minW: "auto", children: [_jsxs(XStack, { items: "center", gap: 8, children: [_jsx(Text, { fontSize: "$4", fontWeight: "700", color: "$ink", children: "Hanzo Meet" }), _jsx(Text, { fontSize: FLOOR, fontWeight: "700", bg: "$hover", color: "$soft", borderWidth: 1, borderColor: "$borderColor", px: 6, py: 1, rounded: 4, children: "LIVE \u00B7 meet.hanzo.ai" })] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Native WebRTC video conferencing with AI meeting bot transcription & voice" })] })] }), activeMeetingRoom ? (_jsxs(XStack, { items: "center", gap: 8, shrink: 1, minW: "auto", flexWrap: "wrap", children: [_jsx(XStack, { bg: "$hover", rounded: 8, p: 2, gap: 2, borderWidth: 1, borderColor: "$borderColor", shrink: 1, minW: "auto", children: [
|
|
274
274
|
['iframe', 'SFU Stage', Tv],
|
|
275
275
|
['grid', 'Agent Grid', LayoutGrid],
|
|
276
|
-
].map(([view, label, Icon]) => (_jsxs(Button, { size: "sm", variant: meetingView === view ? 'primary' : 'ghost', ...(meetingView === view ? null : { color: '$soft' }), "aria-pressed": meetingView === view, onClick: () => setMeetingView(view), children: [_jsx(Icon, { size: 12 }), label] }, view))) }), botAssisting ? (_jsx(View, { display: "none", "$max-md": { display: 'flex' }, children: _jsxs(Button, { size: "sm", variant: copilot ? 'secondary' : 'default', onClick: () => setCopilot((v) => !v), "aria-label": "Co-pilot", "aria-expanded": copilot, "aria-controls": "meet-copilot", children: [_jsx(Bot, { size: 13 }), "Co-pilot"] }) })) : null, _jsxs(Button, { size: "sm", onClick: () => handleCopyLink(activeMeetingRoom), children: [copied ? _jsx(Check, { size: 13 }) : _jsx(Copy, { size: 13 }), copied ? 'Link Copied' : 'Copy
|
|
276
|
+
].map(([view, label, Icon]) => (_jsxs(Button, { size: "sm", variant: meetingView === view ? 'primary' : 'ghost', ...(meetingView === view ? null : { color: '$soft' }), "aria-pressed": meetingView === view, onClick: () => setMeetingView(view), children: [_jsx(Icon, { size: 12 }), label] }, view))) }), botAssisting ? (_jsx(View, { display: "none", "$max-md": { display: 'flex' }, children: _jsxs(Button, { size: "sm", variant: copilot ? 'secondary' : 'default', onClick: () => setCopilot((v) => !v), "aria-label": "Co-pilot", "aria-expanded": copilot, "aria-controls": "meet-copilot", children: [_jsx(Bot, { size: 13 }), "Co-pilot"] }) })) : null, _jsxs(Button, { size: "sm", onClick: () => handleCopyLink(activeMeetingRoom), children: [copied ? _jsx(Check, { size: 13 }) : _jsx(Copy, { size: 13 }), copied ? 'Link Copied' : 'Copy meeting link'] }), _jsxs(Button, { size: "sm", variant: "destructive", onClick: () => {
|
|
277
277
|
stopAgentSpeech();
|
|
278
278
|
// Leaving the call hangs up the hands-free conversation and
|
|
279
279
|
// settles the transcript with it.
|
package/dist/Prose.js
CHANGED
|
@@ -7,8 +7,15 @@ const INK = 'var(--foreground)';
|
|
|
7
7
|
const QUIET = 'var(--muted-foreground)';
|
|
8
8
|
const EDGE = 'var(--border)';
|
|
9
9
|
const GROUND = 'var(--secondary)';
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The reading type, px: a 16px body on a 24px line, and a heading's size and
|
|
12
|
+
* line by depth, as the reference conversation sets them — the body a reader
|
|
13
|
+
* reads at arm's length, not a sidebar's 14.
|
|
14
|
+
*/
|
|
15
|
+
const BODY = 16;
|
|
16
|
+
const LINE = 24;
|
|
17
|
+
const RUNG = [28, 24, 20, 17, 16, 16];
|
|
18
|
+
const LEAD = [36, 32, 28, 24, 24, 24];
|
|
12
19
|
/** The character references a model writes, as the characters they name. An
|
|
13
20
|
* HTML renderer let the browser decode `&`; a string React sets is shown
|
|
14
21
|
* exactly as written. */
|
|
@@ -21,7 +28,7 @@ const decode = (s) => s.replace(/&(?:#x([0-9a-f]{1,6})|#(\d{1,7})|([a-z]+));/gi,
|
|
|
21
28
|
return NAMED[name.toLowerCase()] ?? all;
|
|
22
29
|
});
|
|
23
30
|
/** One run of inline tokens, in `tone`, at the block's `rung`. */
|
|
24
|
-
function inline(tokens, tone, rung =
|
|
31
|
+
function inline(tokens, tone, rung = BODY) {
|
|
25
32
|
return (tokens ?? []).map((t, i) => {
|
|
26
33
|
switch (t.type) {
|
|
27
34
|
case 'strong':
|
|
@@ -31,7 +38,7 @@ function inline(tokens, tone, rung = '$2') {
|
|
|
31
38
|
case 'del':
|
|
32
39
|
return (_jsx(Text, { render: "del", color: tone, textDecorationLine: "line-through", opacity: 0.7, children: inline(t.tokens, tone, rung) }, i));
|
|
33
40
|
case 'codespan':
|
|
34
|
-
return (_jsx(Text, { render: "code", color: tone, fontFamily: "$mono", fontSize:
|
|
41
|
+
return (_jsx(Text, { render: "code", color: tone, fontFamily: "$mono", fontSize: Math.round(rung * 0.875), px: "$1", rounded: 4, bg: GROUND, children: decode(t.text) }, i));
|
|
35
42
|
case 'br':
|
|
36
43
|
return _jsx("br", {}, i);
|
|
37
44
|
case 'link': {
|
|
@@ -41,7 +48,7 @@ function inline(tokens, tone, rung = '$2') {
|
|
|
41
48
|
// AWAY IS A NEW TAB, HOME IS NOT. Someone else's page opened from a
|
|
42
49
|
// message never gets a handle on this one and never inherits the
|
|
43
50
|
// referrer; a link back into this app is a link.
|
|
44
|
-
return (_jsx(Anchor, { href: to.href, ...(to.away ? { target: '_blank', rel: 'noopener noreferrer nofollow' } : {}), ...(t.title ? { title: t.title } : {}),
|
|
51
|
+
return (_jsx(Anchor, { href: to.href, ...(to.away ? { target: '_blank', rel: 'noopener noreferrer nofollow' } : {}), ...(t.title ? { title: t.title } : {}), fontSize: rung, color: INK, textDecorationLine: "underline", children: inline(t.tokens, INK, rung) }, i));
|
|
45
52
|
}
|
|
46
53
|
// An image is a fetch to wherever the message says, which is a reader's
|
|
47
54
|
// address leaking to whoever wrote it. The alt text is the message.
|
|
@@ -59,11 +66,11 @@ function inline(tokens, tone, rung = '$2') {
|
|
|
59
66
|
}
|
|
60
67
|
/** Running text: a paragraph, or a list item's own words. */
|
|
61
68
|
function Line({ tokens, tone }) {
|
|
62
|
-
return (_jsx(Text, { render: "p", color: tone, fontSize:
|
|
69
|
+
return (_jsx(Text, { render: "p", color: tone, fontSize: BODY, lineHeight: LINE, children: inline(tokens, tone) }));
|
|
63
70
|
}
|
|
64
71
|
function List({ list, tone }) {
|
|
65
72
|
const start = typeof list.start === 'number' ? list.start : 1;
|
|
66
|
-
return (_jsx(YStack, { render: list.ordered ? 'ol' : 'ul', m: 0, p: 0, gap: "$1", children: list.items.map((item, i) => (_jsxs(XStack, { render: "li", gap: "$1", children: [_jsx(Text, { "aria-hidden": true, color: tone, fontSize:
|
|
73
|
+
return (_jsx(YStack, { render: list.ordered ? 'ol' : 'ul', m: 0, p: 0, gap: "$1", children: list.items.map((item, i) => (_jsxs(XStack, { render: "li", gap: "$1", children: [_jsx(Text, { "aria-hidden": true, color: tone, fontSize: BODY, lineHeight: LINE, minW: 16, text: "right", children: item.task ? (item.checked ? '☑' : '☐') : list.ordered ? `${start + i}.` : '•' }), _jsx(YStack, { flex: 1, minW: 0, gap: "$1", children: blocks(item.tokens, tone) })] }, i))) }));
|
|
67
74
|
}
|
|
68
75
|
/** A table is a grid: one track per column, as wide as what the column holds,
|
|
69
76
|
* in a box that scrolls sideways rather than widening the message. Cells
|
|
@@ -71,7 +78,7 @@ function List({ list, tone }) {
|
|
|
71
78
|
* the top and the first column the left. */
|
|
72
79
|
function Table({ table, tone }) {
|
|
73
80
|
const align = (c) => table.align[c] ?? 'left';
|
|
74
|
-
return (_jsx(View, { role: "table", display: "grid", gridTemplateColumns: `repeat(${table.header.length}, max-content)`, self: "flex-start", maxW: "100%", overflowX: "auto", children: [table.header, ...table.rows].map((row, r) => (_jsx(View, { role: "row", display: "contents", children: row.map((cell, c) => (_jsx(Text, { role: r ? 'cell' : 'columnheader', color: tone, fontSize:
|
|
81
|
+
return (_jsx(View, { role: "table", display: "grid", gridTemplateColumns: `repeat(${table.header.length}, max-content)`, self: "flex-start", maxW: "100%", overflowX: "auto", children: [table.header, ...table.rows].map((row, r) => (_jsx(View, { role: "row", display: "contents", children: row.map((cell, c) => (_jsx(Text, { role: r ? 'cell' : 'columnheader', color: tone, fontSize: 14, lineHeight: 20, fontWeight: r ? '400' : '600', text: align(c), px: "$2", py: "$1", bg: r ? undefined : GROUND, borderColor: EDGE, borderStyle: "solid", borderRightWidth: 1, borderBottomWidth: 1, borderTopWidth: r ? 0 : 1, borderLeftWidth: c ? 0 : 1, children: inline(cell.tokens, tone, 14) }, c))) }, r))) }));
|
|
75
82
|
}
|
|
76
83
|
/** A run of block tokens. A tight list item's words arrive as a `text` block. */
|
|
77
84
|
function blocks(tokens, tone) {
|
|
@@ -82,7 +89,7 @@ function blocks(tokens, tone) {
|
|
|
82
89
|
case 'text':
|
|
83
90
|
return _jsx(Line, { tokens: 'tokens' in t && t.tokens ? t.tokens : [t], tone: tone }, i);
|
|
84
91
|
case 'heading':
|
|
85
|
-
return (_jsx(Text, { render: `h${t.depth}`, m: 0, color: tone, fontSize: RUNG[t.depth - 1], lineHeight:
|
|
92
|
+
return (_jsx(Text, { render: `h${t.depth}`, m: 0, mt: t.depth < 3 ? 8 : 4, color: tone, fontSize: RUNG[t.depth - 1], lineHeight: LEAD[t.depth - 1], fontWeight: "600", children: inline(t.tokens, tone, RUNG[t.depth - 1]) }, i));
|
|
86
93
|
case 'list':
|
|
87
94
|
return _jsx(List, { list: t, tone: tone }, i);
|
|
88
95
|
case 'table':
|
package/dist/Rail.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type Host } from '@hanzo/build';
|
|
3
|
+
import type { Mode } from './recent.js';
|
|
4
|
+
/** The rail's width, px. */
|
|
5
|
+
export declare const RAIL = 48;
|
|
6
|
+
/** One mark: an icon button named for where it goes, its name its tooltip, lit where the reader is. */
|
|
7
|
+
export declare function Mark({ label, on, onPress, children, ...rest }: {
|
|
8
|
+
label: string;
|
|
9
|
+
/** The reader is there. */
|
|
10
|
+
on?: boolean;
|
|
11
|
+
onPress?: () => void;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
[attribute: `data-${string}` | `aria-${string}`]: unknown;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
15
|
+
/** The sidebar's toggle: the same mark wherever a host draws it. */
|
|
16
|
+
export declare function Toggle({ open, onPress, label }: {
|
|
17
|
+
open: boolean;
|
|
18
|
+
onPress: () => void;
|
|
19
|
+
label?: string;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
21
|
+
export declare function Rail({ host, mode, at, open, toggle, flat, onChat, onDev, go, onOrg, }: {
|
|
22
|
+
host: Host;
|
|
23
|
+
mode: Mode;
|
|
24
|
+
/** Dev's builder address, or null in Chat. */
|
|
25
|
+
at: string | null;
|
|
26
|
+
/** The sidebar beside the rail is open. */
|
|
27
|
+
open: boolean;
|
|
28
|
+
/** Lead with the sidebar's toggle; absent where the host's bar holds it. */
|
|
29
|
+
toggle?: () => void;
|
|
30
|
+
/** Laid along the foot of the phone's drawer rather than down the window's edge. */
|
|
31
|
+
flat?: boolean;
|
|
32
|
+
/** To Chat and to Dev, each where the reader left it. */
|
|
33
|
+
onChat: () => void;
|
|
34
|
+
onDev: () => void;
|
|
35
|
+
/** Moves the app to an address of this page. */
|
|
36
|
+
go: (address: string) => void;
|
|
37
|
+
/** Work in another organization. */
|
|
38
|
+
onOrg: (org: string) => void;
|
|
39
|
+
}): import("react").JSX.Element;
|
package/dist/Rail.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// The app's rail: its products as marks down the window's left edge, in one
|
|
4
|
+
// order — Chat, Dev, Library, Scheduled tasks, Customize — then More, a menu of
|
|
5
|
+
// the rest, and the account at the foot. It is how the app moves between Chat
|
|
6
|
+
// and Dev; there is no switch anywhere else.
|
|
7
|
+
//
|
|
8
|
+
// Library, Scheduled tasks and Customize are Dev's own places (@hanzo/build's
|
|
9
|
+
// Artifacts, Automations and Customize), raised to the rail; each is lit while
|
|
10
|
+
// the reader stands on it, and Dev is lit for the rest of Dev.
|
|
11
|
+
//
|
|
12
|
+
// It stands on the frame's floor, never cut as a pane (ground.ts): the rail and
|
|
13
|
+
// the sidebar beside it are the window, and the content is the panel inset in
|
|
14
|
+
// it.
|
|
15
|
+
//
|
|
16
|
+
// The sidebar's toggle leads it (`toggle`) where the host has no bar of its own:
|
|
17
|
+
// a browser tab. A host that is a window draws the toggle in its bar beside Back
|
|
18
|
+
// and Forward (App.tsx `bar`), and its rail starts with Chat. On a phone the
|
|
19
|
+
// rail lies along the foot of the drawer (`flat`), the account at its end.
|
|
20
|
+
import { Boxes, Clock, Code2, Ellipsis, FolderGit2, LayoutTemplate, Library, ListTodo, MessageSquare, PanelLeft, Puzzle, Server, SquareTerminal, Users } from 'lucide-react';
|
|
21
|
+
import { XStack, YStack } from '@hanzo/gui';
|
|
22
|
+
import { route } from '@hanzo/build';
|
|
23
|
+
import { DropdownMenu } from '@hanzo/ui';
|
|
24
|
+
import { Me } from './Me.js';
|
|
25
|
+
import { app, TABS, WORKSPACE } from './lib/host.js';
|
|
26
|
+
/** The rail's width, px. */
|
|
27
|
+
export const RAIL = 48;
|
|
28
|
+
/** A mark, px: a 36px square on a 44px pitch. */
|
|
29
|
+
const MARK = 36;
|
|
30
|
+
/**
|
|
31
|
+
* Opens an address of another product in a tab of its own, through an anchor,
|
|
32
|
+
* so a host that sends links out of its window (the desktop's system browser)
|
|
33
|
+
* hears it as the link it is.
|
|
34
|
+
*/
|
|
35
|
+
function out(href) {
|
|
36
|
+
const a = document.createElement('a');
|
|
37
|
+
a.href = href;
|
|
38
|
+
a.target = '_blank';
|
|
39
|
+
a.rel = 'noopener noreferrer';
|
|
40
|
+
document.body.appendChild(a);
|
|
41
|
+
a.click();
|
|
42
|
+
a.remove();
|
|
43
|
+
}
|
|
44
|
+
/** The places the rail raises out of Dev, by the builder address each opens. */
|
|
45
|
+
const RAISED = { library: '-/artifacts', scheduled: '-/automations', customize: '-/customize' };
|
|
46
|
+
/** Which raised place a builder address stands on, if any. */
|
|
47
|
+
function raised(at) {
|
|
48
|
+
if (at === null)
|
|
49
|
+
return null;
|
|
50
|
+
const r = route(at);
|
|
51
|
+
if (r.kind === 'screen' && r.screen === 'artifacts')
|
|
52
|
+
return 'library';
|
|
53
|
+
if ((r.kind === 'screen' && r.screen === 'automations') || r.kind === 'automation')
|
|
54
|
+
return 'scheduled';
|
|
55
|
+
if (r.kind === 'customize')
|
|
56
|
+
return 'customize';
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/** One mark: an icon button named for where it goes, its name its tooltip, lit where the reader is. */
|
|
60
|
+
export function Mark({ label, on = false, onPress, children, ...rest }) {
|
|
61
|
+
return (_jsx(XStack, { render: "button", "aria-label": label, ...{ title: label }, "aria-current": on ? 'page' : undefined, onPress: onPress, width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", shrink: 0, cursor: "pointer", opacity: on ? 1 : 0.62, bg: on ? '$edge' : 'transparent', hoverStyle: { bg: on ? '$edge' : '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' },
|
|
62
|
+
// A thumb's 44px on a touch screen.
|
|
63
|
+
$touchable: { width: 44, height: 44 }, ...rest, children: children }));
|
|
64
|
+
}
|
|
65
|
+
/** The sidebar's toggle: the same mark wherever a host draws it. */
|
|
66
|
+
export function Toggle({ open, onPress, label }) {
|
|
67
|
+
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 }) }));
|
|
68
|
+
}
|
|
69
|
+
export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev, go, onOrg, }) {
|
|
70
|
+
const here = mode === 'dev' ? raised(at) : null;
|
|
71
|
+
const to = (p) => () => go(app(p));
|
|
72
|
+
// Everything else the app reaches, behind More: Dev's other places, then the products on hosts of their own.
|
|
73
|
+
const rest = [
|
|
74
|
+
{ key: 'projects', label: 'Projects', icon: _jsx(FolderGit2, { size: 16, "aria-hidden": true }), onSelect: to('-/projects') },
|
|
75
|
+
{ key: 'templates', label: 'Templates', icon: _jsx(LayoutTemplate, { size: 16, "aria-hidden": true }), onSelect: to('-/templates') },
|
|
76
|
+
{ key: 'issues', label: 'Issues', icon: _jsx(ListTodo, { size: 16, "aria-hidden": true }), onSelect: to('-/issues') },
|
|
77
|
+
{ key: 'machines', label: 'Machines', icon: _jsx(Server, { size: 16, "aria-hidden": true }), onSelect: to('-/settings/machines') },
|
|
78
|
+
{ key: 'environments', label: 'Environments', icon: _jsx(Boxes, { size: 16, "aria-hidden": true }), onSelect: to('-/settings/environments') },
|
|
79
|
+
{ type: 'separator', key: 'away' },
|
|
80
|
+
{ key: 'tabs', label: 'Tabs', icon: _jsx(SquareTerminal, { size: 16, "aria-hidden": true }), onSelect: () => out(TABS) },
|
|
81
|
+
{ key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) },
|
|
82
|
+
];
|
|
83
|
+
const Stack = flat ? XStack : YStack;
|
|
84
|
+
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: "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, onOrg: onOrg })] }));
|
|
85
|
+
}
|
package/dist/Recents.js
CHANGED
|
@@ -9,12 +9,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
// and Chat's Status shows the archived ones apart.
|
|
10
10
|
import { useEffect, useId, useMemo, useRef } from 'react';
|
|
11
11
|
import { ScrollView, SizableText, XStack, YStack } from '@hanzo/gui';
|
|
12
|
-
import { ListFilter
|
|
12
|
+
import { ListFilter } from 'lucide-react';
|
|
13
13
|
import { DOTS, route, useSessions } from '@hanzo/build';
|
|
14
14
|
import { DropdownMenu } from '@hanzo/ui';
|
|
15
15
|
import { SidebarItem, StatusDot } from '@hanzo/ui/chat';
|
|
16
16
|
import { useKept } from './kept.js';
|
|
17
|
-
import {
|
|
17
|
+
import { arrange, chats, MENUS, PINNED, PLAIN, runs, SINCE, viewOf } from './recent.js';
|
|
18
18
|
import { ConversationMenu, ThreadRow, useThreadList } from './thread.js';
|
|
19
19
|
/** The menu's items: each section a heading and its options, the one in force checked. */
|
|
20
20
|
function items(mode, view, set) {
|
|
@@ -68,23 +68,32 @@ function List({ mode, rows, words, view, keep, open, reading, error, onOpen, thr
|
|
|
68
68
|
const label = useId();
|
|
69
69
|
const groups = arrange(rows, view, words);
|
|
70
70
|
const narrowed = view.status !== PLAIN.status || view.since !== PLAIN.since;
|
|
71
|
-
const now = new Date();
|
|
72
71
|
const running = mode === 'dev' ? rows.filter((r) => r.status === 'running').length : 0;
|
|
72
|
+
// A row is its title alone, as the reference draws it: a conversation's ⋯
|
|
73
|
+
// shows on hover and focus, a run's state is a dot at its end.
|
|
73
74
|
const row = (r) => {
|
|
74
|
-
const when = ago(r.at, now);
|
|
75
75
|
const t = threads?.get(r.id);
|
|
76
|
-
const item = (_jsx(SidebarItem, { "data-kind": r.kind, active: open(r), rounded: "$4", minH:
|
|
76
|
+
const item = (_jsx(SidebarItem, { "data-kind": r.kind, active: open(r), rounded: "$4", minH: 31, px: "$2", pr: t || r.kind === 'dev' ? '$7' : '$2', ...(t ? { $touchable: { pr: 52 } } : null), onKeyDown: (e) => {
|
|
77
77
|
if (e.key !== 'Enter' && e.key !== ' ')
|
|
78
78
|
return;
|
|
79
79
|
e.preventDefault?.();
|
|
80
80
|
onOpen(r);
|
|
81
|
-
},
|
|
82
|
-
// How long ago, at the row's end; the full time is its title.
|
|
83
|
-
const age = when ? (_jsx(SizableText, { size: "$1", color: "$soft", pointerEvents: "none", display: "flex", items: "center", fontVariant: ['tabular-nums'], children: _jsx("time", { dateTime: r.at, title: new Date(r.at).toLocaleString(), children: when }) })) : null;
|
|
81
|
+
}, onPress: () => onOpen(r), children: _jsx(SizableText, { size: "$3", color: open(r) ? '$ink' : '$quiet', numberOfLines: 1, children: r.title }) }));
|
|
84
82
|
if (t)
|
|
85
|
-
return (_jsx(ThreadRow, { t: t, active: open(r),
|
|
86
|
-
return (_jsxs(YStack, { role: "listitem", position: "relative", children: [item,
|
|
83
|
+
return (_jsx(ThreadRow, { t: t, active: open(r), children: item }, r.id));
|
|
84
|
+
return (_jsxs(YStack, { role: "listitem", position: "relative", children: [item, r.kind === 'dev' ? (_jsx(XStack, { position: "absolute", r: "$2.5", t: 0, b: 0, items: "center", pointerEvents: "none", children: _jsx(StatusDot, { status: DOTS[r.status] ?? 'idle' }) })) : null] }, r.id));
|
|
87
85
|
};
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
// The view menu stands at the end of the Recents heading: the target's own, quiet until the pointer is near.
|
|
87
|
+
const menu = (_jsx(DropdownMenu, { minWidth: 200, trigger: _jsx(XStack, { render: "button", "aria-label": "Recents view", width: 26, height: 26, rounded: "$2", items: "center", justify: "center", cursor: "pointer", opacity: narrowed ? 1 : 0, "$group-hover": { opacity: 1 }, focusStyle: { opacity: 1 }, $touchable: { opacity: 1 }, hoverStyle: { bg: '$hover' }, bg: narrowed ? '$hover' : 'transparent', children: _jsx(ListFilter, { size: 14, "aria-hidden": true }) }), items: items(mode, view, keep) }));
|
|
88
|
+
/** A section's heading: the reference's quiet 14px label, the Recents one carrying the count and the menu. */
|
|
89
|
+
const heading = (title, id, lead = false) => (_jsxs(XStack, { group: true, items: "center", justify: "space-between", height: 31, pl: "$2", pr: "$1", mt: "$3", shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { id: id, size: "$3", color: "$soft", children: title }), lead && running ? (_jsx(XStack, { "aria-label": `${running} running`, minW: 18, height: 18, px: "$1.5", rounded: 999, items: "center", justify: "center", bg: "$edge", borderWidth: 1, borderColor: "$borderColor", children: _jsx(SizableText, { size: "$1", fontWeight: "600", color: "$ink", "aria-hidden": true, children: String(running) }) })) : null] }), lead ? menu : null] }));
|
|
90
|
+
const loose = groups.filter((g) => g.title !== PINNED);
|
|
91
|
+
const pinned = groups.find((g) => g.title === PINNED);
|
|
92
|
+
const note = reading ? (_jsx(Note, { children: mode === 'chat' ? 'Reading your chats…' : 'Reading your runs…' })) : error ? null : words.trim() ? (_jsx(Note, { children: `Nothing matches “${words.trim()}”.` })) : view.status === 'archived' ? (_jsx(Note, { children: "No archived chats." })) : narrowed ? (_jsx(Note, { children: "Nothing in this view." })) : (_jsx(Note, { children: mode === 'chat' ? 'No chats yet.' : 'No runs yet.' }));
|
|
93
|
+
const body = (_jsxs(YStack, { role: "list", "aria-labelledby": label, gap: 1, children: [pinned ? (_jsxs(YStack, { role: "listitem", gap: 1, children: [heading(PINNED), _jsx(YStack, { role: "list", "aria-label": PINNED, gap: 1, children: pinned.rows.map(row) })] })) : null, _jsxs(YStack, { role: "listitem", gap: 1, children: [heading('Recents', label, true), loose.length
|
|
94
|
+
? loose.map((g) => g.title ? (_jsxs(YStack, { gap: 1, pt: "$1", children: [_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", pb: "$1", children: g.title }), _jsx(YStack, { role: "list", "aria-label": g.title, gap: 1, children: g.rows.map(row) })] }, g.title)) : (_jsx(YStack, { role: "list", "aria-label": "All", gap: 1, children: g.rows.map(row) }, "all")))
|
|
95
|
+
: pinned
|
|
96
|
+
? null
|
|
97
|
+
: note] })] }));
|
|
98
|
+
return (_jsx(YStack, { "data-slot": "recents", flex: 1, minH: 0, children: _jsxs(ScrollView, { flex: 1, showsVerticalScrollIndicator: false, children: [error ? _jsx(Note, { children: error.message }) : null, threads ? (_jsx(ConversationMenu, { threads: threads, active: held, onGone: onNew, children: _jsx(YStack, { children: body }) })) : (body)] }) }));
|
|
90
99
|
}
|
package/dist/RightPane.d.ts
CHANGED
|
@@ -38,3 +38,18 @@ export declare function useKinds({ at, said, held, deck }: {
|
|
|
38
38
|
held: Held[];
|
|
39
39
|
deck: Deck;
|
|
40
40
|
}): Kind[];
|
|
41
|
+
/**
|
|
42
|
+
* The conversation's summary, from the outline mark in its header: what it
|
|
43
|
+
* produced (Outputs, led by Create file or site, which starts a Dev run) and
|
|
44
|
+
* what went into it (Sources, with the `+` that brings more). Each row opens in
|
|
45
|
+
* a tab beside the conversation. A section with nothing in it keeps its heading
|
|
46
|
+
* and one quiet line.
|
|
47
|
+
*/
|
|
48
|
+
export declare function Summary({ at, said, held, deck, onOpen, onCreate }: {
|
|
49
|
+
at: string;
|
|
50
|
+
said: ChatMessage[];
|
|
51
|
+
held: Held[];
|
|
52
|
+
deck: Deck;
|
|
53
|
+
onOpen: () => void;
|
|
54
|
+
onCreate: () => void;
|
|
55
|
+
}): import("react").JSX.Element;
|
package/dist/RightPane.js
CHANGED
|
@@ -17,8 +17,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
17
17
|
// @hanzo/build (`useDeck`, scope `chat:<channel>`); the frame owns the column
|
|
18
18
|
// it stands in (`Beside` in `Shell.tsx`).
|
|
19
19
|
import { useMemo, useState } from 'react';
|
|
20
|
-
import { FileCode, Image as ImageMark, Paperclip, Plus, Search } from 'lucide-react';
|
|
21
|
-
import { Box, DropdownMenu, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Text, YStack, } from '@hanzo/ui';
|
|
20
|
+
import { FileCode, Globe, Image as ImageMark, ListTodo, Paperclip, Plus, Search } from 'lucide-react';
|
|
21
|
+
import { Box, DropdownMenu, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Popover, PopoverContent, PopoverTrigger, Text, XStack, YStack, } from '@hanzo/ui';
|
|
22
22
|
import { useAi } from '@hanzo/ai/react';
|
|
23
23
|
import { artifacts, name as titleOf, PAGE } from '@hanzo/build';
|
|
24
24
|
import { Card, Quiet, Row } from './card.js';
|
|
@@ -29,13 +29,7 @@ import { localOf, take } from './pane.js';
|
|
|
29
29
|
import { useProviders, useSkills } from './tools.js';
|
|
30
30
|
import { activate, startDevice } from './connect.js';
|
|
31
31
|
/** A conversation's panel before anyone has arranged it. */
|
|
32
|
-
export const SEED = {
|
|
33
|
-
tabs: [
|
|
34
|
-
{ id: 'artifacts', kind: 'artifacts', title: 'Artifacts' },
|
|
35
|
-
{ id: 'sources', kind: 'sources', title: 'Sources' },
|
|
36
|
-
],
|
|
37
|
-
at: 'artifacts',
|
|
38
|
-
};
|
|
32
|
+
export const SEED = { tabs: [], at: null };
|
|
39
33
|
/** The text of a turn, whichever shape the wire used. */
|
|
40
34
|
const words = (m) => typeof m.content === 'string'
|
|
41
35
|
? m.content
|
|
@@ -222,3 +216,38 @@ function Services() {
|
|
|
222
216
|
.catch((e) => setStep(String(e?.message ?? 'Could not start')));
|
|
223
217
|
}, children: _jsx(Word, { children: p.name }) }, p.id)))] }));
|
|
224
218
|
}
|
|
219
|
+
/** A row of the summary: its mark and its name, one press from a tab. */
|
|
220
|
+
function Line({ icon, children, onPress, quiet = false }) {
|
|
221
|
+
return (_jsxs(XStack, { render: onPress ? 'button' : undefined, role: onPress ? 'button' : undefined, onPress: onPress, items: "center", gap: "$2.5", px: "$2", minH: 30, width: "100%", rounded: "$3", cursor: onPress ? 'pointer' : undefined, hoverStyle: onPress ? { bg: '$hover' } : undefined, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [icon ? (_jsx(XStack, { width: 16, justify: "center", shrink: 0, opacity: 0.75, children: icon })) : null, _jsx(Text, { flex: 1, minW: 0, fontSize: "$2", color: quiet ? '$soft' : '$ink', numberOfLines: 1, children: children })] }));
|
|
222
|
+
}
|
|
223
|
+
/** A heading of the summary, with what it adds at its end. */
|
|
224
|
+
const Head = ({ children, action }) => (_jsxs(XStack, { items: "center", justify: "space-between", px: "$2", pt: "$1.5", pb: "$1", minH: 28, children: [_jsx(Text, { fontSize: "$2", color: "$soft", children: children }), action] }));
|
|
225
|
+
/**
|
|
226
|
+
* The conversation's summary, from the outline mark in its header: what it
|
|
227
|
+
* produced (Outputs, led by Create file or site, which starts a Dev run) and
|
|
228
|
+
* what went into it (Sources, with the `+` that brings more). Each row opens in
|
|
229
|
+
* a tab beside the conversation. A section with nothing in it keeps its heading
|
|
230
|
+
* and one quiet line.
|
|
231
|
+
*/
|
|
232
|
+
export function Summary({ at, said, held, deck, onOpen, onCreate }) {
|
|
233
|
+
const ai = useAi();
|
|
234
|
+
const [open, setOpen] = useState(false);
|
|
235
|
+
const made = useMemo(() => outputs(said), [said]);
|
|
236
|
+
const took = useMemo(() => sources(said, held), [said, held]);
|
|
237
|
+
const done = useMemo(() => lookups(said), [said]);
|
|
238
|
+
const show = (m) => {
|
|
239
|
+
setOpen(false);
|
|
240
|
+
void look(deck, m, ai);
|
|
241
|
+
onOpen();
|
|
242
|
+
};
|
|
243
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, allowFlip: true, children: [_jsx(PopoverTrigger, { children: _jsx(XStack, { render: "button", "data-slot": "summary-toggle", "aria-label": "Summary", "aria-haspopup": "dialog", "aria-expanded": open, ...{ title: 'Summary' }, width: 32, height: 32, rounded: "$3", items: "center", justify: "center", cursor: "pointer", bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: _jsx(ListTodo, { size: 16, "aria-hidden": true }) }) }), _jsx(PopoverContent, { solid: true, align: "end", p: 6, width: 300, children: _jsxs(YStack, { "data-slot": "summary", role: "dialog", "aria-label": "Summary", gap: 1, self: "stretch", width: "100%", maxH: "min(70dvh, 520px)", overflowY: "auto", overflowX: "hidden", children: [_jsx(Head, { children: "Outputs" }), _jsx(Line, { quiet: true, onPress: () => {
|
|
244
|
+
setOpen(false);
|
|
245
|
+
onCreate();
|
|
246
|
+
}, children: "Create file or site" }), made.map((m) => {
|
|
247
|
+
const Glyph = GLYPH[m.kind];
|
|
248
|
+
return (_jsx(Line, { icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), onPress: () => show(m), children: m.name }, m.id));
|
|
249
|
+
}), _jsx(XStack, { height: 1, bg: "$borderColor", my: "$1.5", mx: "$1" }), _jsx(Head, { action: _jsx(Add, { at: at }), children: "Sources" }), done.map((call) => (_jsx(Line, { icon: _jsx(Globe, { size: 15, "aria-hidden": true }), onPress: () => show({ id: call.id, name: call.name, kind: 'file', body: [call.asked, call.got ?? ''].filter(Boolean).join('\n\n'), mime: 'text/plain' }), children: call.name === 'web_search' || call.name === 'search' ? 'Web search' : call.name }, call.id))), took.map((m) => {
|
|
250
|
+
const Glyph = GLYPH[m.kind];
|
|
251
|
+
return (_jsx(Line, { icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), onPress: () => show(m), children: m.name }, m.id));
|
|
252
|
+
}), !done.length && !took.length ? _jsx(Line, { quiet: true, children: "Nothing yet" }) : null] }) })] }));
|
|
253
|
+
}
|
package/dist/Share.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
10
10
|
// them, and revokes a link, which ends it for all of them. Recipients read and
|
|
11
11
|
// do not reply: a conversation belongs to the member who started it.
|
|
12
12
|
import { useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
13
|
-
import { Check, Link2,
|
|
13
|
+
import { Check, Link2, Upload } from 'lucide-react';
|
|
14
14
|
import { Box, Button, Dialog, DialogContent, DialogTitle, Text, XStack, YStack } from '@hanzo/ui';
|
|
15
15
|
import { hold, heldToken, link, Refused, shares } from './lib/share.js';
|
|
16
16
|
import { BAD } from './lib/mix.js';
|
|
@@ -29,7 +29,7 @@ export function Share({ thread }) {
|
|
|
29
29
|
setOpen(false);
|
|
30
30
|
button.current?.focus();
|
|
31
31
|
}, []);
|
|
32
|
-
return (_jsxs(Box, { position: "relative", children: [
|
|
32
|
+
return (_jsxs(Box, { position: "relative", children: [_jsxs(Box, { ref: button, render: "button", onClick: () => setOpen((o) => !o), "aria-haspopup": "dialog", "aria-expanded": open, "aria-label": "Share this conversation", "data-slot": "share", display: "flex", flexDirection: "row", gap: "$1.5", height: 32, px: "$2.5", items: "center", justify: "center", borderWidth: 0, bg: open ? '$hover' : 'transparent', rounded: "$3", cursor: "pointer", hoverStyle: { bg: '$hover' }, children: [_jsx(Upload, { size: 16, "aria-hidden": true }), _jsx(Text, { fontSize: "$3", color: "$ink", children: "Share" })] }), open ? _jsx(Panel, { thread: thread, onClose: close }) : null] }));
|
|
33
33
|
}
|
|
34
34
|
/** The header's panel: hung under its button, shut by Escape or a press outside, focus starting inside. */
|
|
35
35
|
function Panel({ thread, onClose }) {
|