@hanzo/rooms 0.1.63 → 0.1.65

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.js CHANGED
@@ -26,7 +26,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
26
26
  // THE ORGANIZATION COMES FIRST (Orgs.tsx), and the app mounts it itself, so no
27
27
  // host draws the app without it: a reader with none makes one and a reader
28
28
  // with several picks one. No plan is asked for here: that is the workspace's,
29
- // behind its own front door.
29
+ // on its own host (hanzo.team) and behind its front door.
30
30
  //
31
31
  // THE SIDEBAR'S TOGGLE IS AT THE WINDOW'S TOP-LEFT. In a browser tab it leads
32
32
  // the rail. A host that is a window of its own (`bar`) has no browser around
package/dist/Chat.js CHANGED
@@ -58,10 +58,10 @@ import { FileText, Image as ImageMark, Paperclip, X } from "lucide-react";
58
58
  import { FREE } from "./lib/ai.js";
59
59
  import { openThread, useOpen } from "./open.js";
60
60
  import { Beside, Framed } from "./Shell.js";
61
- import { SEED, Summary, look, useSites } from "./RightPane.js";
61
+ import { SEED, look, useSections, useSites } from "./RightPane.js";
62
62
  import { Share } from "./Share.js";
63
63
  import { ConversationMenu, ThreadMore, useThreadList } from "./thread.js";
64
- import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, useDeck, useKey, useMind, usePanel, frames, name as tabName, } from "@hanzo/build";
64
+ import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, Summary, useDeck, useKey, useMind, usePanel, frames, name as tabName, } from "@hanzo/build";
65
65
  import { checkoutUrl } from './lib/pay.js';
66
66
  import { Upgrade, useFree } from './Upgrade.js';
67
67
  import { enter, LOGIN } from './lib/destination.js';
@@ -1222,6 +1222,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1222
1222
  // Dev place goes from a host without it (hanzo.team).
1223
1223
  const pathed = useRooms().path !== null;
1224
1224
  const create = () => (pathed ? go(app("")) : window.location.assign(dev()));
1225
+ const sections = useSections({ at, said, held: files, deck, onCreate: create });
1225
1226
  /** 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. */
1226
1227
  const browsing = {
1227
1228
  suggested: sites,
@@ -1274,7 +1275,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1274
1275
  e.preventDefault();
1275
1276
  // A dropped folder arrives as entries; each file under it goes up with its path.
1276
1277
  void dropped(e.dataTransfer).then((list) => hold(at, list, via));
1277
- }, 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: create }), _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,
1278
+ }, 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, { sections: sections, onOpen: () => setPanel(true) }), _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,
1278
1279
  // AN EMPTY ROOM CENTRES ITS COMPOSER. While nothing has been said the
1279
1280
  // opening (the heading and the starters) and the composer sit as one
1280
1281
  // group in the middle of the height, with or without the plan bars in
package/dist/Orgs.js CHANGED
@@ -8,13 +8,14 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
8
8
  // request under it then carries that organization (lib/auth/session.ts
9
9
  // `scope`, hooks/useAi.ts).
10
10
  //
11
- // NO PLAN STANDS IN FRONT OF CHAT OR DEV. Off the front door every
11
+ // NO PLAN STANDS IN FRONT OF CHAT OR DEV. Off the workspace every
12
12
  // organization opens, whatever its plan: what it may spend is the API's to
13
13
  // meter, per plan, on every call. The per-seat Team plan is the workspace's to
14
- // ask for, behind its front door, and nowhere else. It used to be asked of
15
- // every organization that was not personal and not on a live plan, on every
16
- // host, which put each member of a team on Free, and every account filed in the
17
- // shared sign-up organization, in front of a wall at hanzo.ai/chat. Plans are
14
+ // ask for, on its host (hanzo.team) and behind its front door, and nowhere
15
+ // else. It used to be asked of every organization that was not personal and
16
+ // not on a live plan, on every host, which put each member of a team on Free,
17
+ // and every account filed in the shared sign-up organization, in front of a
18
+ // wall at hanzo.ai/chat. Plans are
18
19
  // the catalog's, read live and seeded from @hanzo/plans for first paint.
19
20
  // Checkout is hanzo.ai/pay in this tab; it returns here, and the tier is read
20
21
  // again on return.
@@ -80,8 +81,11 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
80
81
  // The team being set up in this page. Once its name is taken the setup owns
81
82
  // the screen until checkout, whatever `Orgs` reads of the new organization.
82
83
  const [founding, setFounding] = useState(null);
83
- // The plan is read behind the front door only: nowhere else does it decide anything.
84
- const { tier, wrong, reload } = useTier(Boolean(door && isAuthenticated && current), current);
84
+ // The workspace asks for the plan, on its host and behind its front door, and
85
+ // only there is it read: nowhere else does it decide anything.
86
+ const onTeam = workspace(useHost());
87
+ const asks = door || onTeam;
88
+ const { tier, wrong, reload } = useTier(Boolean(asks && isAuthenticated && current), current);
85
89
  // A plan is live when billing READ one for this organization and it is not the free rung.
86
90
  const live = Boolean(tier?.tier && tier.tier.name !== FREE_TIER);
87
91
  // Behind the front door every organization without a live plan is read for
@@ -92,7 +96,7 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
92
96
  // plan: support is there to see what the organization has, and what it spends
93
97
  // is billed to the operator's own books, never the organization's.
94
98
  const supporting = Boolean(current) && assumed() === current;
95
- const open = !door || live || supporting;
99
+ const open = !asks || live || supporting;
96
100
  const { client } = useAi();
97
101
  useEffect(() => {
98
102
  try {
@@ -197,7 +201,7 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
197
201
  return _jsx(Pick, { members: members, onPick: choose });
198
202
  if (open)
199
203
  return forward ? _jsx(Forward, { to: forward }) : _jsx(_Fragment, { children: _jsx(Held, { veiled: false, children: children }) });
200
- // BEHIND THE FRONT DOOR NOTHING OPENS WITHOUT A LIVE PLAN. Every branch here
204
+ // IN THE WORKSPACE NOTHING OPENS WITHOUT A LIVE PLAN. Every branch here
201
205
  // used to hand the room over when the answer was anything but a plain
202
206
  // "free": an error of any kind, a read still in flight, a document with no
203
207
  // plan in it. Each was a way in without paying — a refusal, a slow network,
@@ -208,15 +212,20 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
208
212
  if (free && personal === null)
209
213
  return _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
210
214
  const swap = () => setPicked(false);
211
- // A billing OUTAGE is ours; the plan is still the condition.
212
- if (!tier)
213
- return _jsx(Plan, { org: current, members: members, onSwitch: swap, outage: outage(wrong), onRetry: reload });
214
- if (personal) {
215
- if (front && Landing)
216
- return _jsx(Landing, { member: true });
217
- return _jsx(Start, { onFounded: setFounding, first: false, members: members, onSwitch: swap });
215
+ if (door) {
216
+ // A billing OUTAGE is ours; the plan is still the condition.
217
+ if (!tier)
218
+ return _jsx(Plan, { org: current, members: members, onSwitch: swap, outage: outage(wrong), onRetry: reload });
219
+ if (personal) {
220
+ if (front && Landing)
221
+ return _jsx(Landing, { member: true });
222
+ return _jsx(Start, { onFounded: setFounding, first: false, members: members, onSwitch: swap });
223
+ }
224
+ return _jsx(Plan, { org: current, members: members, onSwitch: swap });
218
225
  }
219
- return _jsx(Plan, { org: current, members: members, onSwitch: swap });
226
+ // The workspace's host off its front door: no live plan, or none read, does
227
+ // not open the room.
228
+ return _jsx(Subscribe, { org: current, members: members, onSwitch: swap });
220
229
  }
221
230
  /**
222
231
  * A link that asks to arrive signed in.
@@ -453,6 +462,12 @@ function CreateDialog({ onClose }) {
453
462
  function Pick({ members, onPick }) {
454
463
  return (_jsx(Page, { label: "Choose an organization", children: _jsxs(Column, { children: [_jsx(Brand, {}), _jsx(Heading, { title: "Choose an organization", lede: _jsx(_Fragment, { children: "Everything you open \u2014 chats, agents, credit \u2014 is this one's." }) }), _jsx(Rule, {}), _jsx(View, { display: "grid", gap: 10, children: members.map((o) => (_jsx(View, { render: "button", onClick: () => onPick(o), items: "flex-start", px: 16, py: 14, rounded: 12, borderWidth: 1, borderColor: "var(--border)", bg: "transparent", cursor: "pointer", children: _jsx(Text, { fontSize: "$4", color: "var(--foreground)", text: "left", children: o }) }, o))) })] }) }));
455
464
  }
465
+ /** On the workspace's host, an organization whose plan is not live: it is offered the per-seat Team plan. */
466
+ function Subscribe({ org, members, onSwitch, }) {
467
+ const { plan, price } = usePlan('team');
468
+ const seat = plan?.pricePerUser ? ' / seat' : '';
469
+ return (_jsx(Page, { label: "Subscribe", children: _jsxs(Column, { children: [_jsx(Brand, {}), _jsx(Heading, { title: org, lead: 22.5, lede: plan ? `This organization runs on the ${plan.name} plan at ${price}${seat} / month, and nothing in it works until that plan is live.` : 'Its plan is not live yet.' }), _jsx(Rule, {}), _jsxs(XStack, { gap: 10, flexWrap: "wrap", children: [plan ? (_jsx(Button, { asChild: true, variant: "primary", size: "lg", children: _jsx("a", { href: checkout(plan), children: `Subscribe — ${price}${seat} / month` }) })) : null, members.length > 1 ? (_jsx(Button, { size: "lg", onClick: onSwitch, children: "Switch organization" })) : null] })] }) }));
470
+ }
456
471
  // ── the front door's setup ─────────────────────────────────────────────────────
457
472
  /**
458
473
  * CREATE YOUR TEAM: its name, then who else is in it, then its plan.
package/dist/Rail.js CHANGED
@@ -1,9 +1,12 @@
1
1
  'use client';
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
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.
4
+ // order — Chat, Dev, Studio, Library, Scheduled tasks, Customize — then More, a
5
+ // menu of the rest, and the account at the foot. It is how the app moves
6
+ // between its products; there is no switch anywhere else.
7
+ //
8
+ // Studio is its own page, hanzo.ai/studio, opened in this tab: the app draws it
9
+ // there for a signed-in reader.
7
10
  //
8
11
  // Library, Scheduled tasks and Customize are Dev's own places (@hanzo/build's
9
12
  // Artifacts, Automations and Customize), raised to the rail; each is lit while
@@ -19,12 +22,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
19
22
  // rail lies along the foot of the drawer (`flat`), the account at its end; the
20
23
  // drawer is too narrow for every mark, so Library, Scheduled tasks and
21
24
  // Customize lead More there.
22
- import { Boxes, Clock, Code2, Ellipsis, FolderGit2, LayoutTemplate, Library, ListTodo, MessageSquare, PanelLeft, Puzzle, Server, SquareTerminal, Users } from 'lucide-react';
25
+ import { Boxes, Clock, Code2, Ellipsis, FolderGit2, LayoutTemplate, Library, ListTodo, MessageSquare, PanelLeft, Puzzle, Server, SquareTerminal, Users, WandSparkles } from 'lucide-react';
23
26
  import { XStack, YStack } from '@hanzo/gui';
24
27
  import { route } from '@hanzo/build';
25
28
  import { DropdownMenu } from '@hanzo/ui';
26
29
  import { Me } from './Me.js';
27
- import { app, TABS, WORKSPACE } from './lib/host.js';
30
+ import { app, STUDIO, TABS, WORKSPACE } from './lib/host.js';
31
+ import { site } from './where.js';
28
32
  /** The rail's width, px. */
29
33
  export const RAIL = 48;
30
34
  /** A mark, px: a 36px square on a 44px pitch. */
@@ -91,5 +95,5 @@ export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev
91
95
  { key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) },
92
96
  ];
93
97
  const Stack = flat ? XStack : YStack;
94
- 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 }) }), flat ? null : (_jsxs(_Fragment, { children: [_jsx(Mark, { label: "Library", on: here === 'library', onPress: to(RAISED.library), "data-slot": "rail-library", children: _jsx(Library, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Scheduled tasks", on: here === 'scheduled', onPress: to(RAISED.scheduled), "data-slot": "rail-scheduled", children: _jsx(Clock, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Customize", on: here === 'customize', onPress: to(RAISED.customize), "data-slot": "rail-customize", children: _jsx(Puzzle, { size: 18, "aria-hidden": true }) })] })), _jsx(DropdownMenu, { placement: flat ? 'top-start' : 'right-start', minWidth: 220, trigger: _jsx(XStack, { render: "button", "aria-label": "More", ...{ title: 'More' }, "data-slot": "rail-more", width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", cursor: "pointer", opacity: 0.62, hoverStyle: { bg: '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, $touchable: { width: 44, height: 44 }, children: _jsx(Ellipsis, { size: 18, "aria-hidden": true }) }), items: rest }), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, above: flat, onOrg: onOrg })] }));
98
+ return (_jsxs(Stack, { "data-slot": "rail", render: "nav", "aria-label": "Products", ...(flat ? { pt: '$2', mt: '$2', borderTopWidth: 1, borderColor: '$borderColor', gap: '$1' } : { width: RAIL, flex: 1, pt: '$2', pb: '$2', gap: 8 }), shrink: 0, items: "center", children: [toggle ? (_jsx(YStack, { pb: 8, children: _jsx(Toggle, { open: open, onPress: toggle }) })) : null, _jsx(Mark, { label: "Chat", on: mode === 'chat', onPress: onChat, "data-slot": "rail-chat", children: _jsx(MessageSquare, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Dev", on: mode === 'dev' && !here, onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Studio", onPress: () => window.location.assign(site(STUDIO)), "data-slot": "rail-studio", children: _jsx(WandSparkles, { size: 18, "aria-hidden": true }) }), flat ? null : (_jsxs(_Fragment, { children: [_jsx(Mark, { label: "Library", on: here === 'library', onPress: to(RAISED.library), "data-slot": "rail-library", children: _jsx(Library, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Scheduled tasks", on: here === 'scheduled', onPress: to(RAISED.scheduled), "data-slot": "rail-scheduled", children: _jsx(Clock, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Customize", on: here === 'customize', onPress: to(RAISED.customize), "data-slot": "rail-customize", children: _jsx(Puzzle, { size: 18, "aria-hidden": true }) })] })), _jsx(DropdownMenu, { placement: flat ? 'top-start' : 'right-start', minWidth: 220, trigger: _jsx(XStack, { render: "button", "aria-label": "More", ...{ title: 'More' }, "data-slot": "rail-more", width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", cursor: "pointer", opacity: 0.62, hoverStyle: { bg: '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, $touchable: { width: 44, height: 44 }, children: _jsx(Ellipsis, { size: 18, "aria-hidden": true }) }), items: rest }), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, above: flat, onOrg: onOrg })] }));
95
99
  }
@@ -1,5 +1,5 @@
1
1
  import { type ChatMessage } from '@hanzo/ai/react';
2
- import { type Deck, type Kind, type Tabs } from '@hanzo/build';
2
+ import { type Deck, type Section, type Tabs } from '@hanzo/build';
3
3
  import { type Api } from './lib/files.js';
4
4
  import { type Held } from './pane.js';
5
5
  /** A conversation's panel before anyone has arranged it. */
@@ -31,28 +31,19 @@ export interface Mark {
31
31
  * the bytes this document still holds first (`localOf`).
32
32
  */
33
33
  export declare function look(deck: Deck, m: Mark, api: Api): Promise<void>;
34
- /** Chat's kinds of tab, over the conversation `said` and the files `held` for the next message. */
35
- export declare function useKinds({ at, said, held, deck }: {
36
- at: string;
37
- said: ChatMessage[];
38
- held: Held[];
39
- deck: Deck;
40
- }): Kind[];
41
34
  /**
42
- * The conversation's summary, from the outline mark in its header: what it
35
+ * The conversation's summary as sections (@hanzo/build `Summary`): what it
43
36
  * produced (Outputs, led by Create file or site, which starts a Dev run) and
44
37
  * 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.
38
+ * a tab beside the conversation.
47
39
  */
48
- export declare function Summary({ at, said, held, deck, onOpen, onCreate }: {
40
+ export declare function useSections({ at, said, held, deck, onCreate }: {
49
41
  at: string;
50
42
  said: ChatMessage[];
51
43
  held: Held[];
52
44
  deck: Deck;
53
- onOpen: () => void;
54
45
  onCreate: () => void;
55
- }): import("react").JSX.Element;
46
+ }): Section[];
56
47
  /**
57
48
  * The organization's sites, as an empty tab suggests them: `GET /v1/projects`,
58
49
  * every row with a live address. Read once a conversation; nothing on a refusal.
package/dist/RightPane.js CHANGED
@@ -17,11 +17,10 @@ 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 { useEffect, useMemo, useState } from 'react';
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';
20
+ import { FileCode, Globe, Image as ImageMark, Paperclip, Plus } from 'lucide-react';
21
+ import { Box, DropdownMenu, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Text, } from '@hanzo/ui';
22
22
  import { useAi } from '@hanzo/ai/react';
23
23
  import { artifacts, name as titleOf, PAGE } from '@hanzo/build';
24
- import { Card, Quiet, Row } from './card.js';
25
24
  import { said as told } from './lib/attach.js';
26
25
  import { download, fileOf } from './lib/files.js';
27
26
  import { org, scope } from './lib/session.js';
@@ -116,32 +115,6 @@ export async function look(deck, m, api) {
116
115
  // A file forgotten or a store unreachable opens nothing; the row stays.
117
116
  }
118
117
  }
119
- /** A list of marks, every one of them; pressing one opens it. */
120
- function Marks({ kind, title, icon, marks, deck, action, children }) {
121
- const ai = useAi();
122
- return (_jsxs(Card, { kind: kind, icon: icon, title: title, action: action, children: [marks.length === 0 ? _jsx(Quiet, { children: children }) : null, marks.map((m) => {
123
- const Glyph = GLYPH[m.kind];
124
- return (_jsx(Row, { icon: _jsx(Glyph, { size: 16, "aria-hidden": true }), onPress: () => void look(deck, m, ai), children: m.name }, m.id));
125
- })] }));
126
- }
127
- /** Chat's kinds of tab, over the conversation `said` and the files `held` for the next message. */
128
- export function useKinds({ at, said, held, deck }) {
129
- const made = useMemo(() => outputs(said), [said]);
130
- const took = useMemo(() => sources(said, held), [said, held]);
131
- const done = useMemo(() => lookups(said), [said]);
132
- return [
133
- {
134
- id: 'artifacts',
135
- label: 'Artifacts',
136
- render: () => (_jsx(Marks, { kind: "outputs", title: "Outputs", icon: _jsx(FileCode, { size: 16, "aria-hidden": true }), marks: made, deck: deck, children: "This conversation has produced nothing yet." })),
137
- },
138
- {
139
- id: 'sources',
140
- label: 'Sources',
141
- render: () => (_jsxs(YStack, { gap: "$3", children: [_jsx(Marks, { kind: "sources", title: "Sources", icon: _jsx(Paperclip, { size: 16, "aria-hidden": true }), marks: took, deck: deck, action: _jsx(Add, { at: at }), children: "Nothing has been put into this conversation yet." }), _jsxs(Card, { kind: "lookups", icon: _jsx(Search, { size: 16, "aria-hidden": true }), title: "Looked up", children: [done.length === 0 ? _jsx(Quiet, { children: "Nothing was looked up for this conversation yet." }) : null, done.map((call) => (_jsxs(YStack, { gap: "$1", p: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Text, { fontSize: "$2", color: "$ink", children: call.name }), _jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 2, children: call.asked }), _jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 6, children: call.got ?? 'Running…' })] }, call.id)))] })] })),
142
- },
143
- ];
144
- }
145
118
  /**
146
119
  * A menu row's words.
147
120
  *
@@ -218,40 +191,31 @@ function Services() {
218
191
  .catch((e) => setStep(String(e?.message ?? 'Could not start')));
219
192
  }, children: _jsx(Word, { children: p.name }) }, p.id)))] }));
220
193
  }
221
- /** A row of the summary: its mark and its name, one press from a tab. */
222
- function Line({ icon, children, onPress, quiet = false }) {
223
- 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 })] }));
224
- }
225
- /** A heading of the summary, with what it adds at its end. */
226
- 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] }));
227
194
  /**
228
- * The conversation's summary, from the outline mark in its header: what it
195
+ * The conversation's summary as sections (@hanzo/build `Summary`): what it
229
196
  * produced (Outputs, led by Create file or site, which starts a Dev run) and
230
197
  * what went into it (Sources, with the `+` that brings more). Each row opens in
231
- * a tab beside the conversation. A section with nothing in it keeps its heading
232
- * and one quiet line.
198
+ * a tab beside the conversation.
233
199
  */
234
- export function Summary({ at, said, held, deck, onOpen, onCreate }) {
200
+ export function useSections({ at, said, held, deck, onCreate }) {
235
201
  const ai = useAi();
236
- const [open, setOpen] = useState(false);
237
202
  const made = useMemo(() => outputs(said), [said]);
238
203
  const took = useMemo(() => sources(said, held), [said, held]);
239
204
  const done = useMemo(() => lookups(said), [said]);
240
- const show = (m) => {
241
- setOpen(false);
242
- void look(deck, m, ai);
243
- onOpen();
244
- };
245
- 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: () => {
246
- setOpen(false);
247
- onCreate();
248
- }, children: "Create file or site" }), made.map((m) => {
249
- const Glyph = GLYPH[m.kind];
250
- return (_jsx(Line, { icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), onPress: () => show(m), children: m.name }, m.id));
251
- }), _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) => {
252
- const Glyph = GLYPH[m.kind];
253
- return (_jsx(Line, { icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), onPress: () => show(m), children: m.name }, m.id));
254
- }), !done.length && !took.length ? _jsx(Line, { quiet: true, children: "Nothing yet" }) : null] }) })] }));
205
+ return useMemo(() => {
206
+ const row = (m) => {
207
+ const Glyph = GLYPH[m.kind];
208
+ return { id: m.id, title: m.name, icon: _jsx(Glyph, { size: 15, "aria-hidden": true }), open: () => void look(deck, m, ai) };
209
+ };
210
+ const asked = done.map((call) => {
211
+ const m = { id: call.id, name: call.name, kind: 'file', body: [call.asked, call.got ?? ''].filter(Boolean).join('\n\n'), mime: 'text/plain' };
212
+ return { ...row(m), title: call.name === 'web_search' || call.name === 'search' ? 'Web search' : call.name, icon: _jsx(Globe, { size: 15, "aria-hidden": true }), detail: call.asked };
213
+ });
214
+ return [
215
+ { id: 'outputs', title: 'Outputs', lead: { title: 'Create file or site', run: onCreate }, entries: made.map(row) },
216
+ { id: 'sources', title: 'Sources', action: _jsx(Add, { at: at }), empty: 'Nothing yet', entries: [...asked, ...took.map(row)] },
217
+ ];
218
+ }, [at, made, took, done, deck, ai, onCreate]);
255
219
  }
256
220
  /**
257
221
  * The organization's sites, as an empty tab suggests them: `GET /v1/projects`,
@@ -267,7 +231,7 @@ export function useSites() {
267
231
  if (stop.signal.aborted)
268
232
  return;
269
233
  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 })));
234
+ setRows(all.filter((p) => typeof p.liveUrl === 'string' && p.liveUrl.startsWith('https://')).map((p) => ({ title: p.name || p.slug || 'Site', url: p.liveUrl })));
271
235
  })
272
236
  .catch(() => { });
273
237
  return () => stop.abort();
@@ -46,6 +46,11 @@ export declare const TABS = "https://tabs.hanzo.ai";
46
46
  */
47
47
  export declare const CHAT = "/chat";
48
48
  export declare const DEV = "/dev";
49
+ /**
50
+ * Studio: making images, audio, video and documents, and editing them.
51
+ * hanzo.ai/studio, whose page draws the app for a signed-in reader.
52
+ */
53
+ export declare const STUDIO = "/studio";
49
54
  /** Chat on a conversation, or /chat itself for a new one. */
50
55
  export declare const talk: (id?: string | null) => string;
51
56
  /** Dev at a builder address (`route` in @hanzo/build); '' is New, /dev. What is not an address is New. */
package/dist/lib/host.js CHANGED
@@ -49,6 +49,11 @@ export const TABS = 'https://tabs.hanzo.ai';
49
49
  */
50
50
  export const CHAT = '/chat';
51
51
  export const DEV = '/dev';
52
+ /**
53
+ * Studio: making images, audio, video and documents, and editing them.
54
+ * hanzo.ai/studio, whose page draws the app for a signed-in reader.
55
+ */
56
+ export const STUDIO = '/studio';
52
57
  /** Chat on a conversation, or /chat itself for a new one. */
53
58
  export const talk = (id) => (id ? `${CHAT}/${encodeURIComponent(id)}` : CHAT);
54
59
  /** Dev at a builder address (`route` in @hanzo/build); '' is New, /dev. What is not an address is New. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
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",
@@ -49,7 +49,7 @@
49
49
  "peerDependencies": {
50
50
  "@hanzo/ai": ">=0.6.23",
51
51
  "@hanzo/appearance": ">=0.2.6",
52
- "@hanzo/build": ">=0.2.53",
52
+ "@hanzo/build": ">=0.2.57",
53
53
  "@hanzo/composer": ">=0.2.0",
54
54
  "@hanzo/event": ">=0.3.56",
55
55
  "@hanzo/gui": "^8.3.5",
@@ -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.56",
69
+ "@hanzo/build": "0.2.57",
70
70
  "@hanzo/composer": "0.2.0",
71
71
  "@hanzo/design": "0.5.34",
72
72
  "@hanzo/event": "0.3.56",