@hanzo/rooms 0.1.58 → 0.1.61
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/Chat.js +41 -5
- package/dist/Me.d.ts +3 -1
- package/dist/Me.js +2 -2
- package/dist/Rail.js +1 -1
- package/dist/Recents.js +10 -7
- package/dist/RightPane.d.ts +8 -0
- package/dist/RightPane.js +25 -2
- package/dist/lib/invite.d.ts +3 -0
- package/dist/lib/invite.js +4 -1
- package/package.json +2 -2
package/dist/Chat.js
CHANGED
|
@@ -45,6 +45,7 @@ import { useMedia } from "@hanzo/gui";
|
|
|
45
45
|
import { useIam, useIamToken } from "@hanzo/iam/react";
|
|
46
46
|
import { bearer, hasSession, org } from "./lib/session.js";
|
|
47
47
|
import { api } from "./lib/api.js";
|
|
48
|
+
import { dev } from "./lib/host.js";
|
|
48
49
|
import { chats, house, paidPlan, paused, setCreditsAfterAllowance, useLimits } from "./lib/limits.js";
|
|
49
50
|
import { LimitedBanner } from "@hanzo/ui/product/LimitedBanner";
|
|
50
51
|
import { WEB } from "./lib/web.js";
|
|
@@ -56,10 +57,10 @@ import { FileText, Image as ImageMark, Paperclip, X } from "lucide-react";
|
|
|
56
57
|
import { FREE } from "./lib/ai.js";
|
|
57
58
|
import { openThread, useOpen } from "./open.js";
|
|
58
59
|
import { Beside, Framed } from "./Shell.js";
|
|
59
|
-
import { SEED, Summary, look } from "./RightPane.js";
|
|
60
|
+
import { SEED, Summary, look, useSites } from "./RightPane.js";
|
|
60
61
|
import { Share } from "./Share.js";
|
|
61
62
|
import { ConversationMenu, ThreadMore, useThreadList } from "./thread.js";
|
|
62
|
-
import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, useDeck, useKey, useMind, usePanel, } from "@hanzo/build";
|
|
63
|
+
import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, useDeck, useKey, useMind, usePanel, frames, name as tabName, } from "@hanzo/build";
|
|
63
64
|
import { checkoutUrl } from './lib/pay.js';
|
|
64
65
|
import { Upgrade, useFree } from './Upgrade.js';
|
|
65
66
|
import { enter, LOGIN } from './lib/destination.js';
|
|
@@ -238,7 +239,9 @@ export function Ask({ placeholder, idle, }) {
|
|
|
238
239
|
* character (`Message ${persona.name}`). `label` stays "Ask anything", so the
|
|
239
240
|
* accessible name does not move when the placeholder does.
|
|
240
241
|
*/
|
|
241
|
-
const message = () => `
|
|
242
|
+
const message = () => `Ask ${brand()}`;
|
|
243
|
+
/** The line every answer is sent under, above the composer, as the reference sets it. */
|
|
244
|
+
const caution = () => `${brand()} can make mistakes. Check important info.`;
|
|
242
245
|
/** The panel offers no kinds of its own in Chat: its tabs are pages. */
|
|
243
246
|
const NONE = [];
|
|
244
247
|
/** The id the room's system turn is held under in the thread. */
|
|
@@ -504,9 +507,12 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
504
507
|
// THE SIDE PANEL (@hanzo/build `Panel`), the one Dev's runs open too: open or
|
|
505
508
|
// shut as Chat was left on a laptop, a sheet asked for on a phone, its tabs
|
|
506
509
|
// kept per conversation. The header's Panel button and ⌘. move it.
|
|
507
|
-
const
|
|
510
|
+
const media = useMedia();
|
|
511
|
+
const wide = media.lg;
|
|
508
512
|
const [panel, setPanel] = usePanel("chat", false, wide);
|
|
509
513
|
const deck = useDeck(`chat:${at}`, SEED);
|
|
514
|
+
// The org's sites, which an empty tab suggests.
|
|
515
|
+
const sites = useSites();
|
|
510
516
|
useKey(() => setPanel(!panel));
|
|
511
517
|
// On a laptop the frame's column is the panel's; a close from the frame (its
|
|
512
518
|
// edge pulled shut, the shell's own control) comes back into `panel`.
|
|
@@ -1210,7 +1216,34 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1210
1216
|
}, onCancel: () => {
|
|
1211
1217
|
setAsking(null);
|
|
1212
1218
|
} }));
|
|
1219
|
+
/** The tabs' browser, as this conversation offers it: the org's sites to start from, and a page asked about, captured or opened in Dev. */
|
|
1220
|
+
const browsing = {
|
|
1221
|
+
suggested: sites,
|
|
1222
|
+
onAsk: (page) => {
|
|
1223
|
+
const quote = page.selection ? `\n> ${page.selection.trim().split("\n").join("\n> ")}` : "";
|
|
1224
|
+
setDraft((was) => `${was ? `${was}\n\n` : ""}About ${page.title} (${page.url}):${quote}\n`);
|
|
1225
|
+
},
|
|
1226
|
+
onCapture: (file) => void hold(at, [file], via),
|
|
1227
|
+
onDev: (page) => {
|
|
1228
|
+
const slug = new URL(page.url).hostname.match(/^([a-z0-9-]+)\.hanzo\.app$/)?.[1] ?? null;
|
|
1229
|
+
window.location.assign(dev(slug));
|
|
1230
|
+
},
|
|
1231
|
+
};
|
|
1213
1232
|
return (_jsxs(YStack, { flex: 1, minH: 0, width: "100%", position: "relative", "data-slot": "chat-room",
|
|
1233
|
+
// A LINK IN AN ANSWER OPENS IN A TAB beside the conversation when the tab
|
|
1234
|
+
// can show it — a site or preview we host — and in a browser tab of its
|
|
1235
|
+
// own otherwise, as a modified click always does.
|
|
1236
|
+
onClickCapture: (press) => {
|
|
1237
|
+
const e = press;
|
|
1238
|
+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
|
|
1239
|
+
return;
|
|
1240
|
+
const a = e.target?.closest?.("a[href]");
|
|
1241
|
+
if (!a || !/^https?:/.test(a.href) || !frames(a.href))
|
|
1242
|
+
return;
|
|
1243
|
+
e.preventDefault();
|
|
1244
|
+
deck.open({ kind: PAGE, title: tabName(a.href), url: a.href });
|
|
1245
|
+
setPanel(true);
|
|
1246
|
+
},
|
|
1214
1247
|
// Entered and left once for every child crossed, so the veil follows a
|
|
1215
1248
|
// count rather than the last event.
|
|
1216
1249
|
onDragEnter: (e) => {
|
|
@@ -1235,7 +1268,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1235
1268
|
e.preventDefault();
|
|
1236
1269
|
// A dropped folder arrives as entries; each file under it goes up with its path.
|
|
1237
1270
|
void dropped(e.dataTransfer).then((list) => hold(at, list, via));
|
|
1238
|
-
}, children: [dragging ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: 10, items: "center", justify: "center", bg: "var(--surface-scrim)", borderWidth: 2, borderStyle: "dashed", borderColor: "$borderColor", rounded: "var(--pane-round)", pointerEvents: "none", children: _jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$2.5", rounded: "$10", bg: "$background", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Paperclip, { size: 15, "aria-hidden": true }), _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop files to attach" })] }) })) : null, _jsxs(XStack, { "data-slot": "conversation-head", height: 52, items: "center", justify: "space-between", gap: "$3", pl: "$4", pr: "$2", shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, flex: 1, children: [mine ? (_jsx(Text, { fontSize: 15, lineHeight: 20, color: "$ink", numberOfLines: 1, shrink: 1, minW: 0, children: mine.title || "Untitled" })) : null, room.length ? (_jsx(XStack, { role: "list", "aria-label": "Agents in this chat", items: "center", gap: "$1.5", minW: 0, overflowX: "auto", "$platform-web": { scrollbarWidth: "none" }, children: room.map((name) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$1", pr: "$0.5", py: "$0.5", rounded: 999, borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Face, { src: faces[name]?.avatar ?? (name === withWhom ? portrait : undefined), emoji: faces[name]?.emoji ?? (name === withWhom ? persona?.emoji : undefined), name: name, size: 22 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, display: "none", $sm: { display: "flex" }, children: name }), _jsx(Box, { render: "button", onClick: () => openAgent(name), "aria-label": `Remove ${name} from this chat`, title: `Remove ${name}`, width: 24, height: 24, rounded: 999, items: "center", justify: "center", hoverStyle: { bg: "$hover" }, children: _jsx(X, { size: 13, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) })] }, name))) })) : null] }), _jsxs(XStack, { "data-slot": "conversation-controls", items: "center", gap: 2, shrink: 0, children: [open ? _jsx(Share, { thread: open }) : null, mine ? (_jsx(ConversationMenu, { threads: alone, active: open, children: _jsx(XStack, { children: _jsx(ThreadMore, { t: mine }) }) })) : null, _jsx(Summary, { at: at, said: said, held: files, deck: deck, onOpen: () => setPanel(true), onCreate: () => window.location.assign(site("/dev")) }), _jsx(PanelToggle, { open: panel, onToggle: () => setPanel(!panel) })] })] }), wide && aside !== null ? (_jsx(Beside, { children: _jsx(Panel, { side: deck, kinds: NONE, onHide: () => setPanel(false), label: "Tabs" }) })) : null, !wide && panel ? _jsx(Panel, { side: deck, kinds: NONE, sheet: true, onHide: () => setPanel(false), label: "Tabs" }) : null, isFree(model) ? (_jsxs(Text, { fontSize: "$2", color: "$soft", text: "center", maxW: MEASURE, mx: "auto", px: "$4", py: "$2", children: [freeCopy.notice, " ", _jsx(Anchor, { href: site("/legal/privacy"), children: freeCopy.noticeCta })] })) : null, _jsx(ChatSurface, { ...chat, messages: shown, send: guarded,
|
|
1271
|
+
}, children: [dragging ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: 10, items: "center", justify: "center", bg: "var(--surface-scrim)", borderWidth: 2, borderStyle: "dashed", borderColor: "$borderColor", rounded: "var(--pane-round)", pointerEvents: "none", children: _jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$2.5", rounded: "$10", bg: "$background", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Paperclip, { size: 15, "aria-hidden": true }), _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop files to attach" })] }) })) : null, _jsxs(XStack, { "data-slot": "conversation-head", height: 52, items: "center", justify: "space-between", gap: "$3", pl: "$4", pr: "$2", shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, flex: 1, children: [mine ? (_jsx(Text, { fontSize: 15, lineHeight: 20, color: "$ink", numberOfLines: 1, shrink: 1, minW: 0, children: mine.title || "Untitled" })) : null, room.length ? (_jsx(XStack, { role: "list", "aria-label": "Agents in this chat", items: "center", gap: "$1.5", minW: 0, overflowX: "auto", "$platform-web": { scrollbarWidth: "none" }, children: room.map((name) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$1", pr: "$0.5", py: "$0.5", rounded: 999, borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Face, { src: faces[name]?.avatar ?? (name === withWhom ? portrait : undefined), emoji: faces[name]?.emoji ?? (name === withWhom ? persona?.emoji : undefined), name: name, size: 22 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, display: "none", $sm: { display: "flex" }, children: name }), _jsx(Box, { render: "button", onClick: () => openAgent(name), "aria-label": `Remove ${name} from this chat`, title: `Remove ${name}`, width: 24, height: 24, rounded: 999, items: "center", justify: "center", hoverStyle: { bg: "$hover" }, children: _jsx(X, { size: 13, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) })] }, name))) })) : null] }), _jsxs(XStack, { "data-slot": "conversation-controls", items: "center", gap: 2, shrink: 0, children: [open ? _jsx(Share, { thread: open }) : null, mine ? (_jsx(ConversationMenu, { threads: alone, active: open, children: _jsx(XStack, { children: _jsx(ThreadMore, { t: mine }) }) })) : null, _jsx(Summary, { at: at, said: said, held: files, deck: deck, onOpen: () => setPanel(true), onCreate: () => window.location.assign(site("/dev")) }), _jsx(PanelToggle, { open: panel, onToggle: () => setPanel(!panel) })] })] }), wide && aside !== null ? (_jsx(Beside, { children: _jsx(Panel, { side: deck, kinds: NONE, onHide: () => setPanel(false), label: "Tabs", ...browsing }) })) : null, !wide && panel ? _jsx(Panel, { side: deck, kinds: NONE, sheet: true, onHide: () => setPanel(false), label: "Tabs", ...browsing }) : null, isFree(model) ? (_jsxs(Text, { fontSize: "$2", color: "$soft", text: "center", maxW: MEASURE, mx: "auto", px: "$4", py: "$2", children: [freeCopy.notice, " ", _jsx(Anchor, { href: site("/legal/privacy"), children: freeCopy.noticeCta })] })) : null, _jsx(ChatSurface, { ...chat, messages: shown, send: guarded,
|
|
1239
1272
|
// AN EMPTY ROOM CENTRES ITS COMPOSER. While nothing has been said the
|
|
1240
1273
|
// opening (the heading and the starters) and the composer sit as one
|
|
1241
1274
|
// group in the middle of the height, with or without the plan bars in
|
|
@@ -1313,6 +1346,9 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1313
1346
|
// `label` keeps the accessible name constant while it does.
|
|
1314
1347
|
placeholder: room.length ? `Message ${join(room)}` : placeholder,
|
|
1315
1348
|
label: ASK,
|
|
1349
|
+
note: caution(),
|
|
1350
|
+
// A phone's pill is the tall one: the field across the top, the controls under it.
|
|
1351
|
+
narrow: !media.sm,
|
|
1316
1352
|
// In the app's frame the composer floats on the pane as a pane of its own.
|
|
1317
1353
|
framed,
|
|
1318
1354
|
// A file or an image pasted into the field is held like a picked one;
|
package/dist/Me.d.ts
CHANGED
|
@@ -19,10 +19,12 @@ export declare function Face({ host, size }: {
|
|
|
19
19
|
* plan, Free included, is one to move up from.
|
|
20
20
|
*/
|
|
21
21
|
export declare const upgrade: (family: string | null | undefined) => string;
|
|
22
|
-
export declare function Me({ host, narrow, onOrg, }: {
|
|
22
|
+
export declare function Me({ host, narrow, above, onOrg, }: {
|
|
23
23
|
host: Host;
|
|
24
24
|
/** The rail's: the face alone, the menu opening beside it. */
|
|
25
25
|
narrow?: boolean;
|
|
26
|
+
/** The menu opens above the face, not beside it: the rail laid along a phone drawer's foot. */
|
|
27
|
+
above?: boolean;
|
|
26
28
|
/** Work in another organization: picked, then the app opened in it. */
|
|
27
29
|
onOrg: (org: string) => void;
|
|
28
30
|
}): import("react").JSX.Element;
|
package/dist/Me.js
CHANGED
|
@@ -162,7 +162,7 @@ const mac = () => typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(nav
|
|
|
162
162
|
const Keys = ({ children }) => (_jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: children }));
|
|
163
163
|
/** A row that opens a page of the menu. */
|
|
164
164
|
const More = () => _jsx(ChevronRight, { size: 14, "aria-hidden": true, opacity: 0.6 });
|
|
165
|
-
export function Me({ host, narrow = false, onOrg, }) {
|
|
165
|
+
export function Me({ host, narrow = false, above = false, onOrg, }) {
|
|
166
166
|
const menu = useWho();
|
|
167
167
|
const [page, setPage] = useState('main');
|
|
168
168
|
const [creating, setCreating] = useState(false);
|
|
@@ -211,7 +211,7 @@ export function Me({ host, narrow = false, onOrg, }) {
|
|
|
211
211
|
if (open)
|
|
212
212
|
setPage('main');
|
|
213
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) => {
|
|
214
|
+
}, placement: narrow && !above ? 'right-end' : above ? 'top-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 8, children: [_jsx(Popover.Anchor, { width: narrow ? undefined : '100%', children: _jsxs(XStack, { ref: (el) => {
|
|
215
215
|
row.current = el instanceof HTMLElement ? el : null;
|
|
216
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: () => {
|
|
217
217
|
menu.onOpenChange(false);
|
package/dist/Rail.js
CHANGED
|
@@ -81,5 +81,5 @@ export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev
|
|
|
81
81
|
{ key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) },
|
|
82
82
|
];
|
|
83
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 })] }));
|
|
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, above: flat, onOrg: onOrg })] }));
|
|
85
85
|
}
|
package/dist/Recents.js
CHANGED
|
@@ -78,7 +78,7 @@ function List({ mode, rows, words, view, keep, open, reading, error, onOpen, thr
|
|
|
78
78
|
return;
|
|
79
79
|
e.preventDefault?.();
|
|
80
80
|
onOpen(r);
|
|
81
|
-
}, onPress: () => onOpen(r), children: _jsx(SizableText, { size: "$3", color: open(r) ? '$ink' : '$quiet', numberOfLines: 1, children: r.title }) }));
|
|
81
|
+
}, onPress: () => onOpen(r), children: _jsx(SizableText, { size: "$3", color: open(r) ? '$ink' : '$quiet', numberOfLines: 1, display: "block", children: r.title }) }));
|
|
82
82
|
if (t)
|
|
83
83
|
return (_jsx(ThreadRow, { t: t, active: open(r), children: item }, r.id));
|
|
84
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));
|
|
@@ -90,10 +90,13 @@ function List({ mode, rows, words, view, keep, open, reading, error, onOpen, thr
|
|
|
90
90
|
const loose = groups.filter((g) => g.title !== PINNED);
|
|
91
91
|
const pinned = groups.find((g) => g.title === PINNED);
|
|
92
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
// Each list of conversations holds its own menu (thread.tsx): the view menu
|
|
94
|
+
// in the Recents heading between them is a menu of its own, never inside one.
|
|
95
|
+
const menus = (rows) => threads ? (_jsx(ConversationMenu, { threads: threads, active: held, onGone: onNew, children: _jsx(YStack, { children: rows }) })) : (rows);
|
|
96
|
+
const rest = loose.length
|
|
97
|
+
? 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")))
|
|
98
|
+
: pinned
|
|
99
|
+
? null
|
|
100
|
+
: note;
|
|
101
|
+
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, _jsxs(YStack, { role: "list", "aria-labelledby": label, gap: 1, children: [pinned ? (_jsxs(YStack, { role: "listitem", gap: 1, children: [heading(PINNED), menus(_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 ? menus(rest) : rest] })] })] }) }));
|
|
99
102
|
}
|
package/dist/RightPane.d.ts
CHANGED
|
@@ -53,3 +53,11 @@ export declare function Summary({ at, said, held, deck, onOpen, onCreate }: {
|
|
|
53
53
|
onOpen: () => void;
|
|
54
54
|
onCreate: () => void;
|
|
55
55
|
}): import("react").JSX.Element;
|
|
56
|
+
/**
|
|
57
|
+
* The organization's sites, as an empty tab suggests them: `GET /v1/projects`,
|
|
58
|
+
* every row with a live address. Read once a conversation; nothing on a refusal.
|
|
59
|
+
*/
|
|
60
|
+
export declare function useSites(): {
|
|
61
|
+
title: string;
|
|
62
|
+
url: string;
|
|
63
|
+
}[];
|
package/dist/RightPane.js
CHANGED
|
@@ -16,7 +16,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
16
16
|
// The panel's tabs, their order and the open one are kept per conversation by
|
|
17
17
|
// @hanzo/build (`useDeck`, scope `chat:<channel>`); the frame owns the column
|
|
18
18
|
// it stands in (`Beside` in `Shell.tsx`).
|
|
19
|
-
import { useMemo, useState } from 'react';
|
|
19
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
20
20
|
import { FileCode, Globe, Image as ImageMark, ListTodo, Paperclip, Plus, Search } from 'lucide-react';
|
|
21
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';
|
|
@@ -24,7 +24,9 @@ import { artifacts, name as titleOf, PAGE } from '@hanzo/build';
|
|
|
24
24
|
import { Card, Quiet, Row } from './card.js';
|
|
25
25
|
import { said as told } from './lib/attach.js';
|
|
26
26
|
import { download, fileOf } from './lib/files.js';
|
|
27
|
-
import { org } from './lib/session.js';
|
|
27
|
+
import { org, scope } from './lib/session.js';
|
|
28
|
+
import { api } from './lib/api.js';
|
|
29
|
+
import { list } from './lib/list.js';
|
|
28
30
|
import { localOf, take } from './pane.js';
|
|
29
31
|
import { useProviders, useSkills } from './tools.js';
|
|
30
32
|
import { activate, startDevice } from './connect.js';
|
|
@@ -251,3 +253,24 @@ export function Summary({ at, said, held, deck, onOpen, onCreate }) {
|
|
|
251
253
|
return (_jsx(Line, { icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), onPress: () => show(m), children: m.name }, m.id));
|
|
252
254
|
}), !done.length && !took.length ? _jsx(Line, { quiet: true, children: "Nothing yet" }) : null] }) })] }));
|
|
253
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* The organization's sites, as an empty tab suggests them: `GET /v1/projects`,
|
|
258
|
+
* every row with a live address. Read once a conversation; nothing on a refusal.
|
|
259
|
+
*/
|
|
260
|
+
export function useSites() {
|
|
261
|
+
const [rows, setRows] = useState([]);
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
const stop = new AbortController();
|
|
264
|
+
fetch(`${api()}/v1/projects`, { headers: scope(), signal: stop.signal })
|
|
265
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
266
|
+
.then((body) => {
|
|
267
|
+
if (stop.signal.aborted)
|
|
268
|
+
return;
|
|
269
|
+
const all = list(Array.isArray(body) ? body : body?.data);
|
|
270
|
+
setRows(all.filter((p) => typeof p.liveUrl === 'string' && /^https:\/\//.test(p.liveUrl)).map((p) => ({ title: p.name || p.slug || 'Site', url: p.liveUrl })));
|
|
271
|
+
})
|
|
272
|
+
.catch(() => { });
|
|
273
|
+
return () => stop.abort();
|
|
274
|
+
}, []);
|
|
275
|
+
return rows;
|
|
276
|
+
}
|
package/dist/lib/invite.d.ts
CHANGED
|
@@ -55,6 +55,9 @@ export declare function createInvitation(org: string, email?: string, quota?: nu
|
|
|
55
55
|
*
|
|
56
56
|
* The organization is named as `?owner=`, the one input IAM's list reads. A
|
|
57
57
|
* read that names none is authorized against no organization and refused.
|
|
58
|
+
*
|
|
59
|
+
* A listed invitation carries no code: IAM shows a code once, in the answer to
|
|
60
|
+
* `createInvitation`. A row is an invitation by its name.
|
|
58
61
|
*/
|
|
59
62
|
export declare function listInvitations(org: string): Promise<Invitation[]>;
|
|
60
63
|
/**
|
package/dist/lib/invite.js
CHANGED
|
@@ -87,10 +87,13 @@ export async function createInvitation(org, email, quota = 1) {
|
|
|
87
87
|
*
|
|
88
88
|
* The organization is named as `?owner=`, the one input IAM's list reads. A
|
|
89
89
|
* read that names none is authorized against no organization and refused.
|
|
90
|
+
*
|
|
91
|
+
* A listed invitation carries no code: IAM shows a code once, in the answer to
|
|
92
|
+
* `createInvitation`. A row is an invitation by its name.
|
|
90
93
|
*/
|
|
91
94
|
export async function listInvitations(org) {
|
|
92
95
|
const body = (await call(`/v1/iam/invitations?owner=${encodeURIComponent(org)}`));
|
|
93
|
-
return (body.invitations ?? []).filter((r) => r && r.
|
|
96
|
+
return (body.invitations ?? []).filter((r) => r && r.name);
|
|
94
97
|
}
|
|
95
98
|
/**
|
|
96
99
|
* Emails the join link to the address an invitation is pinned to, and returns
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
4
4
|
"description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@hanzo/ai": "0.6.23",
|
|
68
68
|
"@hanzo/appearance": "0.2.8",
|
|
69
|
-
"@hanzo/build": "0.2.
|
|
69
|
+
"@hanzo/build": "0.2.56",
|
|
70
70
|
"@hanzo/composer": "0.2.0",
|
|
71
71
|
"@hanzo/design": "0.5.34",
|
|
72
72
|
"@hanzo/event": "0.3.56",
|