@hanzo/rooms 0.1.58 → 0.1.59
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 +37 -4
- 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/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. */
|
|
@@ -507,6 +510,8 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
507
510
|
const wide = useMedia().lg;
|
|
508
511
|
const [panel, setPanel] = usePanel("chat", false, wide);
|
|
509
512
|
const deck = useDeck(`chat:${at}`, SEED);
|
|
513
|
+
// The org's sites, which an empty tab suggests.
|
|
514
|
+
const sites = useSites();
|
|
510
515
|
useKey(() => setPanel(!panel));
|
|
511
516
|
// On a laptop the frame's column is the panel's; a close from the frame (its
|
|
512
517
|
// edge pulled shut, the shell's own control) comes back into `panel`.
|
|
@@ -1210,7 +1215,34 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1210
1215
|
}, onCancel: () => {
|
|
1211
1216
|
setAsking(null);
|
|
1212
1217
|
} }));
|
|
1218
|
+
/** 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. */
|
|
1219
|
+
const browsing = {
|
|
1220
|
+
suggested: sites,
|
|
1221
|
+
onAsk: (page) => {
|
|
1222
|
+
const quote = page.selection ? `\n> ${page.selection.trim().split("\n").join("\n> ")}` : "";
|
|
1223
|
+
setDraft((was) => `${was ? `${was}\n\n` : ""}About ${page.title} (${page.url}):${quote}\n`);
|
|
1224
|
+
},
|
|
1225
|
+
onCapture: (file) => void hold(at, [file], via),
|
|
1226
|
+
onDev: (page) => {
|
|
1227
|
+
const slug = new URL(page.url).hostname.match(/^([a-z0-9-]+)\.hanzo\.app$/)?.[1] ?? null;
|
|
1228
|
+
window.location.assign(dev(slug));
|
|
1229
|
+
},
|
|
1230
|
+
};
|
|
1213
1231
|
return (_jsxs(YStack, { flex: 1, minH: 0, width: "100%", position: "relative", "data-slot": "chat-room",
|
|
1232
|
+
// A LINK IN AN ANSWER OPENS IN A TAB beside the conversation when the tab
|
|
1233
|
+
// can show it — a site or preview we host — and in a browser tab of its
|
|
1234
|
+
// own otherwise, as a modified click always does.
|
|
1235
|
+
onClickCapture: (press) => {
|
|
1236
|
+
const e = press;
|
|
1237
|
+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
|
|
1238
|
+
return;
|
|
1239
|
+
const a = e.target?.closest?.("a[href]");
|
|
1240
|
+
if (!a || !/^https?:/.test(a.href) || !frames(a.href))
|
|
1241
|
+
return;
|
|
1242
|
+
e.preventDefault();
|
|
1243
|
+
deck.open({ kind: PAGE, title: tabName(a.href), url: a.href });
|
|
1244
|
+
setPanel(true);
|
|
1245
|
+
},
|
|
1214
1246
|
// Entered and left once for every child crossed, so the veil follows a
|
|
1215
1247
|
// count rather than the last event.
|
|
1216
1248
|
onDragEnter: (e) => {
|
|
@@ -1235,7 +1267,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1235
1267
|
e.preventDefault();
|
|
1236
1268
|
// A dropped folder arrives as entries; each file under it goes up with its path.
|
|
1237
1269
|
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,
|
|
1270
|
+
}, 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
1271
|
// AN EMPTY ROOM CENTRES ITS COMPOSER. While nothing has been said the
|
|
1240
1272
|
// opening (the heading and the starters) and the composer sit as one
|
|
1241
1273
|
// group in the middle of the height, with or without the plan bars in
|
|
@@ -1313,6 +1345,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1313
1345
|
// `label` keeps the accessible name constant while it does.
|
|
1314
1346
|
placeholder: room.length ? `Message ${join(room)}` : placeholder,
|
|
1315
1347
|
label: ASK,
|
|
1348
|
+
note: caution(),
|
|
1316
1349
|
// In the app's frame the composer floats on the pane as a pane of its own.
|
|
1317
1350
|
framed,
|
|
1318
1351
|
// 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
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.55",
|
|
70
70
|
"@hanzo/composer": "0.2.0",
|
|
71
71
|
"@hanzo/design": "0.5.34",
|
|
72
72
|
"@hanzo/event": "0.3.56",
|