@hanzo/rooms 0.1.81 → 0.1.83
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 +1 -1
- package/dist/Chat.js +9 -6
- package/dist/Column.js +2 -1
- package/dist/Palette.d.ts +7 -1
- package/dist/Palette.js +66 -27
- package/dist/Rail.d.ts +1 -5
- package/dist/Rail.js +16 -48
- package/dist/Shell.js +6 -5
- package/dist/lib/title.d.ts +7 -0
- package/dist/lib/title.js +36 -0
- package/dist/seek.d.ts +9 -0
- package/dist/seek.js +11 -5
- package/dist/thread.js +2 -1
- package/package.json +3 -3
package/dist/App.js
CHANGED
|
@@ -241,5 +241,5 @@ function Window({ bar }) {
|
|
|
241
241
|
shut();
|
|
242
242
|
setFinding(true);
|
|
243
243
|
};
|
|
244
|
-
return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsx(PrefsProvider, { children: _jsxs(YStack, { ref: view, "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", "$max-md": { position: 'fixed', t: TOP, l: 0, r: 0, height: HEIGHT }, children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer, onPress: () => setDrawer(true), label: "Open sidebar" }) }), _jsx(Mark, { label: "Back", "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Forward", "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), flex: 1, minW: 0, minH: 0, pr: "var(--pane-gap)", pb: "var(--pane-gap)", pt: bar ? 0 : 'var(--pane-gap)', "$max-md": { p: 0 }, children: [_jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode,
|
|
244
|
+
return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsx(PrefsProvider, { children: _jsxs(YStack, { ref: view, "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", "$max-md": { position: 'fixed', t: TOP, l: 0, r: 0, height: HEIGHT }, children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer, onPress: () => setDrawer(true), label: "Open sidebar" }) }), _jsx(Mark, { label: "Back", "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Forward", "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), flex: 1, minW: 0, minH: 0, pr: "var(--pane-gap)", pb: "var(--pane-gap)", pt: bar ? 0 : 'var(--pane-gap)', "$max-md": { p: 0 }, children: [_jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, onStudio: toStudio, onOrg: org }) }), closed ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), onSeek: seek, places: sidebar }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, ml: "var(--pane-gap)", "$max-md": { ml: 0, pb: FOOT }, children: [bar ? null : _jsx(Top, { host: host, mode: mode, places: sidebar, go: move, onMenu: () => setDrawer(true), onChat: toChat, onDev: toDev }), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : mode === 'studio' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(YStack, { flex: 1, minH: 0, height: "100%", children: _jsx(Suspense, { fallback: null, children: _jsx(Studio, { host: maker, rail: false }) }) }) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer ? (_jsxs(_Fragment, { children: [_jsx(YStack, { render: "button", "aria-label": "Close sidebar", onPress: () => setDrawer(false), bg: DIM, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-overlay)", $md: { display: 'none' } }), _jsx(YStack, { "data-slot": "drawer", position: "absolute", t: INSET.top, b: INSET.bottom, l: INSET.left, width: "min(320px, calc(100% - 2 * var(--pane-gap) - 48px))", z: "var(--z-modal)", $md: { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, onSeek: seek, drawer: true, places: sidebar, foot: _jsx(Rail, { host: host, mode: mode, open: true, flat: true, onChat: toChat, onDev: toDev, onStudio: toStudio, onOrg: org }) }) })] })) : null, finding ? _jsx(Palette, { go: move, onClose: found, mode: mode, host: host }) : null] }) }) }) }));
|
|
245
245
|
}
|
package/dist/Chat.js
CHANGED
|
@@ -42,6 +42,7 @@ import { AgentMaker, HOUSE, useAgentShelf, useCrew } from "./crew.js";
|
|
|
42
42
|
import { called } from "./team.js";
|
|
43
43
|
import { Anchor, Box, Button, Text, View, XStack, YStack } from "@hanzo/ui";
|
|
44
44
|
import { useMedia } from "@hanzo/gui";
|
|
45
|
+
import { tidy } from "./lib/title.js";
|
|
45
46
|
import { useIam, useIamToken } from "@hanzo/iam/react";
|
|
46
47
|
import { bearer, hasSession, org } from "./lib/session.js";
|
|
47
48
|
import { api } from "./lib/api.js";
|
|
@@ -540,12 +541,13 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
540
541
|
const at = channel(opened);
|
|
541
542
|
// The first of them: what a surface that addresses one person reads.
|
|
542
543
|
const withWhom = room[0] ?? null;
|
|
543
|
-
// THE SIDE PANEL (@hanzo/build `Panel`), the one Dev's runs open too:
|
|
544
|
-
//
|
|
545
|
-
// kept per conversation
|
|
544
|
+
// THE SIDE PANEL (@hanzo/build `Panel`), the one Dev's runs open too: shut
|
|
545
|
+
// until the reader opens it or a preview lands (`see`, a link that frames),
|
|
546
|
+
// kept per conversation with its tabs, so a chat opens as it was left and
|
|
547
|
+
// never as the last chat was. The header's Panel button and ⌘. move it.
|
|
546
548
|
const media = useMedia();
|
|
547
549
|
const wide = media.lg;
|
|
548
|
-
const [panel, setPanel] = usePanel(
|
|
550
|
+
const [panel, setPanel] = usePanel(`chat:${at}`, false, wide);
|
|
549
551
|
const deck = useDeck(`chat:${at}`, SEED);
|
|
550
552
|
// The org's sites, which an empty tab suggests.
|
|
551
553
|
const sites = useSites();
|
|
@@ -1392,6 +1394,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1392
1394
|
upload: () => take(at, via),
|
|
1393
1395
|
agents: agentShelf,
|
|
1394
1396
|
skills: carry.skills,
|
|
1397
|
+
design: carry.design,
|
|
1395
1398
|
plugins: carry.plugins,
|
|
1396
1399
|
research: { on: carry.gear.gear.research, set: (on) => carry.gear.set({ research: on }) },
|
|
1397
1400
|
memory: { on: carry.gear.gear.memory, set: (on) => carry.gear.set({ memory: on }) },
|
|
@@ -1463,7 +1466,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1463
1466
|
}, children: [_jsx(Schedule, { open: plan !== null, onOpenChange: (open) => {
|
|
1464
1467
|
if (!open)
|
|
1465
1468
|
setPlan(null);
|
|
1466
|
-
}, prompt: plan ?? "", model: model, models: models.map((m) => ({ id: m.id, name: nameOf(m.id, listed) })) }), 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
|
|
1469
|
+
}, prompt: plan ?? "", model: model, models: models.map((m) => ({ id: m.id, name: nameOf(m.id, listed) })) }), 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: tidy(mine.title) })) : 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, onSchedule: anonymous ? undefined : () => setPlan(lastAsked()), 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, _jsx(ChatSurface, { ...chat, messages: shown, send: guarded,
|
|
1467
1470
|
// AN EMPTY ROOM CENTRES ITS COMPOSER on a laptop. While nothing has been
|
|
1468
1471
|
// said the opening (the heading) and the composer sit as one group in the
|
|
1469
1472
|
// middle of the height; the moment a turn exists the transcript takes the
|
|
@@ -1530,7 +1533,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1530
1533
|
empty: heading === null ? undefined : (_jsxs(YStack, { "data-slot": "opening", items: "center", width: "100%", py: "$6", ...(media.sm ? null : { flex: 1, justify: "center" }), children: [room.length > 1 ? (_jsx(XStack, { items: "center", justify: "center", mb: "$4", children: room.map((name, i) => (_jsx(Box, { ml: i ? -16 : 0, rounded: 40, borderWidth: 2, borderColor: "$background", z: room.length - i, children: _jsx(Face, { src: faces[name]?.avatar, emoji: faces[name]?.emoji, name: name, size: 72 }) }, name))) })) : portrait ? (_jsx(Box, { width: 160, height: 160, rounded: 24, overflow: "hidden", borderWidth: 2, borderColor: "var(--white-15)", boxShadow: "0 10px 24px color-mix(in srgb, var(--pure-black) 70%, transparent)", mb: "$4", bg: "var(--neutral-900)", children: _jsx(View, { render: _jsx("img", { src: portrait, alt: withWhom }), width: "100%", height: "100%", objectFit: "cover" }) })) : null, room.length === 1 && !portrait ? (_jsx(Box, { mb: "$3", children: _jsx(Face, { emoji: mate?.emoji ?? persona?.emoji, name: withWhom, size: 72 }) })) : null, _jsx(Text, { render: "h1", fontSize: "$7", lineHeight: "$7", fontWeight: "700", color: "$ink", text: "center", mb: "$1", children: join(room) || heading || "What can I help with?" }), room.length === 1 && (mate?.description ?? persona?.description) ? (_jsx(Text, { fontSize: "$3", color: "$soft", text: "center", maxW: 500, mb: "$4", lineHeight: "$4", children: mate?.description ?? persona?.description })) : null] })),
|
|
1531
1534
|
// The transcript reads at the same measure. `Thread` defaults to 768
|
|
1532
1535
|
// itself; handing it the column here is what lets Width reach it.
|
|
1533
|
-
column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog,
|
|
1536
|
+
column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog, _jsxs(XStack, { items: "center", justify: "center", py: "$1", px: "$3", gap: "$2", children: [_jsx(Anchor, { href: site("/legal/terms"), fontSize: "$1", color: "$soft", children: "Terms" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "\u00B7" }), _jsx(Anchor, { href: site("/legal/privacy"), fontSize: "$1", color: "$soft", children: "Privacy" })] }), consent, _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
|
|
1534
1537
|
}
|
|
1535
1538
|
/** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
|
|
1536
1539
|
function Consent({ question, onAgree, onCancel, }) {
|
package/dist/Column.js
CHANGED
|
@@ -21,6 +21,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
21
21
|
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
22
22
|
import { Bell, Search, Settings2, SquarePen } from 'lucide-react';
|
|
23
23
|
import { DOTS, Grip, nav, NEW, path as written, useSessions } from '@hanzo/build';
|
|
24
|
+
import { tidy } from './lib/title.js';
|
|
24
25
|
import { DropdownMenu } from '@hanzo/ui';
|
|
25
26
|
import { Sidebar, SidebarIconButton, SidebarItem, StatusDot } from '@hanzo/ui/chat';
|
|
26
27
|
import { useId } from 'react';
|
|
@@ -78,7 +79,7 @@ function Activity({ host }) {
|
|
|
78
79
|
...(latest.length
|
|
79
80
|
? latest.map((s) => ({
|
|
80
81
|
key: s.id,
|
|
81
|
-
label: s.title
|
|
82
|
+
label: tidy(s.title, 56, 'Untitled run'),
|
|
82
83
|
description: [s.status, ago(s.updatedAt || s.createdAt)].filter(Boolean).join(' · '),
|
|
83
84
|
icon: _jsx(StatusDot, { status: DOTS[s.status] ?? 'idle' }),
|
|
84
85
|
onSelect: () => host.go(s.id),
|
package/dist/Palette.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
+
import { type Host } from '@hanzo/build';
|
|
2
3
|
import { type ShortcutKeys } from '@hanzo/ui';
|
|
4
|
+
import { type Place } from './lib/host.js';
|
|
3
5
|
import { type Hit } from './seek.js';
|
|
4
6
|
/** A row: what it is called, its glyph, and what pressing it does. */
|
|
5
7
|
export interface Action extends Hit {
|
|
@@ -9,10 +11,14 @@ export interface Action extends Hit {
|
|
|
9
11
|
/** Pressing it keeps the palette open. */
|
|
10
12
|
stay?: boolean;
|
|
11
13
|
}
|
|
12
|
-
export declare function Palette({ onClose, go, actions, }: {
|
|
14
|
+
export declare function Palette({ onClose, go, actions, mode, host, }: {
|
|
13
15
|
onClose: () => void;
|
|
14
16
|
/** Moves the app to one of its addresses (lib/host `talk`, `app`). */
|
|
15
17
|
go: (address: string) => void;
|
|
16
18
|
/** The frame's own actions, after the palette's; one with a palette action's id stands in for it. */
|
|
17
19
|
actions?: Action[];
|
|
20
|
+
/** The product the reader is in: what the empty field lists and what a query ranks first. */
|
|
21
|
+
mode?: Place['mode'];
|
|
22
|
+
/** Dev's host, whose runs the palette lists in Dev. */
|
|
23
|
+
host?: Host;
|
|
18
24
|
}): import("react").JSX.Element;
|
package/dist/Palette.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
// The one search. ⌘K / Ctrl+K from anywhere and the sidebar's search open it:
|
|
4
|
-
// a dialog with the field on top, listing
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
4
|
+
// a dialog with the field on top, listing the product's own recents while the
|
|
5
|
+
// field is empty — Chat's Recent chats, Dev's Recent runs — and, for a query,
|
|
6
|
+
// the product's own things first: in Chat, Chats, Projects, Agents, Skills and
|
|
7
|
+
// Actions; in Dev, Runs, Dev's places (Templates, Projects, Issues, Machines,
|
|
8
|
+
// Environments, Library, Scheduled tasks, Customize), Projects, Skills, Actions
|
|
9
|
+
// and Chats last; each cut to its best five (seek.ts `findIn`). A row opens
|
|
10
|
+
// where it lives: a chat at its address, a run or a place in Dev, a project in
|
|
11
|
+
// Dev, an agent in a new chat with it, a skill in Dev's Customize, an action
|
|
12
|
+
// runs. Arrows move, Enter opens, Escape closes (@hanzo/ui's CommandMenu);
|
|
13
|
+
// Keyboard shortcuts lists the app's keys, and Backspace on an empty field
|
|
14
|
+
// comes back.
|
|
11
15
|
//
|
|
12
16
|
// MOUNTED ONLY WHILE OPEN, and every list is read once as it opens, then
|
|
13
17
|
// searched here as the reader types: the conversations store (hanzoai/agent)
|
|
@@ -18,9 +22,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
22
|
// and the agents (crew.tsx) arrive while the reader types, and a read the
|
|
19
23
|
// palette closes on is aborted.
|
|
20
24
|
import { useEffect, useMemo, useState } from 'react';
|
|
21
|
-
import { BookOpen, Bot, Code2, FolderGit2, Keyboard, LogOut, MessageCircle, Puzzle, Settings, SquarePen } from 'lucide-react';
|
|
25
|
+
import { BookOpen, Bot, Boxes, Clock, Code2, FolderGit2, Keyboard, LayoutTemplate, Library, ListTodo, LogOut, MessageCircle, Puzzle, Server, Settings, SquarePen } from 'lucide-react';
|
|
22
26
|
import { useAi } from '@hanzo/ai/react';
|
|
23
|
-
import { path as written } from '@hanzo/build';
|
|
27
|
+
import { path as written, useSessions } from '@hanzo/build';
|
|
24
28
|
import { Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
|
|
25
29
|
import { CommandMenu } from '@hanzo/ui/product/CommandMenu';
|
|
26
30
|
import { ordered } from './agents.js';
|
|
@@ -30,7 +34,8 @@ import { empty, openThread, withAgents } from './open.js';
|
|
|
30
34
|
import { useThreadList } from './thread.js';
|
|
31
35
|
import { useSignOut } from './lib/signout.js';
|
|
32
36
|
import { app, talk } from './lib/host.js';
|
|
33
|
-
import {
|
|
37
|
+
import { tidy } from './lib/title.js';
|
|
38
|
+
import { find, findIn } from './seek.js';
|
|
34
39
|
const ICON = { size: 16, 'aria-hidden': true };
|
|
35
40
|
/** The app's keys, as Keyboard shortcuts lists them. */
|
|
36
41
|
const KEYS = [
|
|
@@ -43,6 +48,17 @@ const KEYS = [
|
|
|
43
48
|
{ id: 'forward', label: 'Forward, in the desktop app', shortcut: ['Alt', 'ArrowRight'] },
|
|
44
49
|
];
|
|
45
50
|
const SKILLS = written({ kind: 'customize', tab: 'skills' });
|
|
51
|
+
/** Dev's places, as its sidebar lists them, by the builder address each opens. */
|
|
52
|
+
const PLACES = [
|
|
53
|
+
{ id: 'templates', label: 'Templates', at: '-/templates', icon: _jsx(LayoutTemplate, { ...ICON }) },
|
|
54
|
+
{ id: 'projects', label: 'Projects', at: '-/projects', icon: _jsx(FolderGit2, { ...ICON }) },
|
|
55
|
+
{ id: 'issues', label: 'Issues', at: '-/issues', icon: _jsx(ListTodo, { ...ICON }) },
|
|
56
|
+
{ id: 'machines', label: 'Machines', at: '-/settings/machines', icon: _jsx(Server, { ...ICON }) },
|
|
57
|
+
{ id: 'environments', label: 'Environments', at: '-/settings/environments', icon: _jsx(Boxes, { ...ICON }) },
|
|
58
|
+
{ id: 'library', label: 'Library', at: '-/artifacts', icon: _jsx(Library, { ...ICON }) },
|
|
59
|
+
{ id: 'scheduled', label: 'Scheduled tasks', at: '-/automations', icon: _jsx(Clock, { ...ICON }) },
|
|
60
|
+
{ id: 'customize', label: 'Customize', at: '-/customize', icon: _jsx(Puzzle, { ...ICON }) },
|
|
61
|
+
];
|
|
46
62
|
/** A list read once as the palette opens; a refusal leaves it empty and the rest still answer. */
|
|
47
63
|
function useRead(path, shape) {
|
|
48
64
|
const ai = useAi();
|
|
@@ -69,7 +85,7 @@ const skills = (body) => rows(obj(body).tools)
|
|
|
69
85
|
.map(obj)
|
|
70
86
|
.filter((t) => str(t.name))
|
|
71
87
|
.map((t) => ({ id: str(t.name), label: str(t.name).replace(/^skill_/, ''), keywords: [str(t.description)] }));
|
|
72
|
-
export function Palette({ onClose, go, actions = [], }) {
|
|
88
|
+
export function Palette({ onClose, go, actions = [], mode = 'chat', host, }) {
|
|
73
89
|
const [query, setQuery] = useState('');
|
|
74
90
|
const [keys, setKeys] = useState(false);
|
|
75
91
|
const { threads } = useThreadList();
|
|
@@ -77,6 +93,7 @@ export function Palette({ onClose, go, actions = [], }) {
|
|
|
77
93
|
const made = useRead('/v1/projects', projects);
|
|
78
94
|
const known = useRead('/v1/tool/skills?activated=true', skills);
|
|
79
95
|
const signOut = useSignOut();
|
|
96
|
+
const runs = useSessions(host ?? null);
|
|
80
97
|
const groups = useMemo(() => {
|
|
81
98
|
const own = [
|
|
82
99
|
{ id: 'new-chat', label: 'New chat', icon: _jsx(SquarePen, { ...ICON }), run: () => (empty(), go(talk())) },
|
|
@@ -89,24 +106,46 @@ export function Palette({ onClose, go, actions = [], }) {
|
|
|
89
106
|
const doing = [...own.map((a) => actions.find((b) => b.id === a.id) ?? a), ...actions.filter((b) => !own.some((a) => a.id === b.id))];
|
|
90
107
|
const chats = threads.map((t) => ({
|
|
91
108
|
id: `chat-${t.id}`,
|
|
92
|
-
label: t.title
|
|
109
|
+
label: tidy(t.title),
|
|
93
110
|
at: t.updatedAt,
|
|
94
111
|
icon: _jsx(MessageCircle, { ...ICON }),
|
|
95
112
|
run: () => (openThread(t.id), go(talk(t.id))),
|
|
96
113
|
}));
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
const projectRows = { id: 'projects', label: 'Projects', items: made.map((p) => ({ ...p, id: `project-${p.id}`, icon: _jsx(FolderGit2, { ...ICON }), run: () => go(app(p.id)) })) };
|
|
115
|
+
const skillRows = { id: 'skills', label: 'Skills', items: known.map((s) => ({ ...s, id: `skill-${s.id}`, icon: _jsx(BookOpen, { ...ICON }), run: () => go(app(SKILLS)) })) };
|
|
116
|
+
const actionRows = { id: 'actions', label: 'Actions', items: doing };
|
|
117
|
+
const shown = mode === 'dev'
|
|
118
|
+
? findIn(query, {
|
|
119
|
+
label: 'Recent runs',
|
|
120
|
+
items: [...(runs?.value ?? [])].map((s) => ({
|
|
121
|
+
id: `run-${s.id}`,
|
|
122
|
+
label: tidy(s.title, 80, 'Untitled run'),
|
|
123
|
+
at: s.updatedAt || s.createdAt,
|
|
124
|
+
keywords: [s.status ?? ''],
|
|
125
|
+
icon: _jsx(Code2, { ...ICON }),
|
|
126
|
+
run: () => go(app(s.id)),
|
|
127
|
+
})),
|
|
128
|
+
}, [
|
|
129
|
+
{ id: 'runs', label: 'Runs', items: [...(runs?.value ?? [])].map((s) => ({ id: `run-${s.id}`, label: tidy(s.title, 80, 'Untitled run'), at: s.updatedAt || s.createdAt, keywords: [s.status ?? ''], icon: _jsx(Code2, { ...ICON }), run: () => go(app(s.id)) })) },
|
|
130
|
+
{ id: 'places', label: 'Dev', items: PLACES.map((p) => ({ id: `place-${p.id}`, label: p.label, icon: p.icon, run: () => go(app(p.at)) })) },
|
|
131
|
+
projectRows,
|
|
132
|
+
skillRows,
|
|
133
|
+
actionRows,
|
|
134
|
+
{ id: 'chats', label: 'Chats', items: chats },
|
|
135
|
+
])
|
|
136
|
+
: find(query, chats, [
|
|
137
|
+
projectRows,
|
|
138
|
+
{
|
|
139
|
+
id: 'agents',
|
|
140
|
+
label: 'Agents',
|
|
141
|
+
items: ordered(agents).map((one) => {
|
|
142
|
+
const name = called(one.name);
|
|
143
|
+
return { id: `agent-${one.id || one.name}`, label: name, icon: _jsx(Bot, { ...ICON }), run: () => (withAgents([name]), go(talk())) };
|
|
144
|
+
}),
|
|
145
|
+
},
|
|
146
|
+
skillRows,
|
|
147
|
+
actionRows,
|
|
148
|
+
]);
|
|
110
149
|
return shown.map((g) => ({
|
|
111
150
|
...g,
|
|
112
151
|
items: g.items.map((a) => ({
|
|
@@ -123,9 +162,9 @@ export function Palette({ onClose, go, actions = [], }) {
|
|
|
123
162
|
},
|
|
124
163
|
})),
|
|
125
164
|
}));
|
|
126
|
-
}, [query, threads, agents, made, known, actions, go, onClose, signOut]);
|
|
165
|
+
}, [query, threads, agents, made, known, actions, go, onClose, signOut, mode, runs]);
|
|
127
166
|
const listed = keys
|
|
128
167
|
? [{ id: 'keys', label: 'Keyboard shortcuts', items: KEYS.map((k) => ({ ...k, id: `key-${k.id}`, onSelect: () => setKeys(false) })) }]
|
|
129
168
|
: groups;
|
|
130
|
-
return (_jsx(Dialog, { open: true, onOpenChange: (open) => (open ? null : onClose()), children: _jsxs(DialogContent, { "data-slot": "palette", p: 0, gap: 0, width: "100%", maxW: 680, height: "min(520px, calc(100dvh - 32px))", rounded: "$6", overflow: "hidden", "$max-md": { width: '100vw', maxW: '100vw', height: '100dvh', rounded: 0, borderWidth: 0 }, children: [_jsx(DialogTitle, { position: "absolute", width: 1, height: 1, overflow: "hidden", opacity: 0, children: "Search" }), _jsx(CommandMenu, { groups: listed, query: query, onQueryChange: setQuery, placeholder: "Search\u2026", label: "Search", empty: keys ? 'No such key.' : query.trim() ? `Nothing matches “${query.trim()}”.` : 'No chats yet.', onBack: keys ? () => setKeys(false) : undefined, onClose: onClose })] }) }));
|
|
169
|
+
return (_jsx(Dialog, { open: true, onOpenChange: (open) => (open ? null : onClose()), children: _jsxs(DialogContent, { "data-slot": "palette", p: 0, gap: 0, width: "100%", maxW: 680, height: "min(520px, calc(100dvh - 32px))", rounded: "$6", overflow: "hidden", "$max-md": { width: '100vw', maxW: '100vw', height: '100dvh', rounded: 0, borderWidth: 0 }, children: [_jsx(DialogTitle, { position: "absolute", width: 1, height: 1, overflow: "hidden", opacity: 0, children: "Search" }), _jsx(CommandMenu, { groups: listed, query: query, onQueryChange: setQuery, placeholder: "Search\u2026", label: "Search", empty: keys ? 'No such key.' : query.trim() ? `Nothing matches “${query.trim()}”.` : mode === 'dev' ? 'No runs yet.' : 'No chats yet.', onBack: keys ? () => setKeys(false) : undefined, onClose: onClose })] }) }));
|
|
131
170
|
}
|
package/dist/Rail.d.ts
CHANGED
|
@@ -18,11 +18,9 @@ export declare function Toggle({ open, onPress, label }: {
|
|
|
18
18
|
onPress: () => void;
|
|
19
19
|
label?: string;
|
|
20
20
|
}): import("react").JSX.Element;
|
|
21
|
-
export declare function Rail({ host, mode,
|
|
21
|
+
export declare function Rail({ host, mode, open, toggle, flat, onChat, onDev, onStudio, onOrg, }: {
|
|
22
22
|
host: Host;
|
|
23
23
|
mode: Place['mode'];
|
|
24
|
-
/** Dev's builder address, or null in Chat. */
|
|
25
|
-
at: string | null;
|
|
26
24
|
/** The sidebar beside the rail is open. */
|
|
27
25
|
open: boolean;
|
|
28
26
|
/** Lead with the sidebar's toggle; absent where the host's bar holds it. */
|
|
@@ -33,8 +31,6 @@ export declare function Rail({ host, mode, at, open, toggle, flat, onChat, onDev
|
|
|
33
31
|
onChat: () => void;
|
|
34
32
|
onDev: () => void;
|
|
35
33
|
onStudio: () => void;
|
|
36
|
-
/** Moves the app to an address of this page. */
|
|
37
|
-
go: (address: string) => void;
|
|
38
34
|
/** Work in another organization. */
|
|
39
35
|
onOrg: (org: string) => void;
|
|
40
36
|
}): import("react").JSX.Element;
|
package/dist/Rail.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { 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, Studio
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// order — Chat, Dev, Studio — then More, a menu of the products on hosts of
|
|
5
|
+
// their own, and the account at the foot. It is how the app moves between its
|
|
6
|
+
// products; a phone, which has no rail, switches Chat and Dev in its bar
|
|
7
|
+
// (Top.tsx).
|
|
8
8
|
//
|
|
9
9
|
// Studio is the app's third mode (@hanzo/studio at /studio), each of the three
|
|
10
10
|
// opened where the reader left it.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// A mark is a product, and a product owns the sidebar beside the rail: Chat its
|
|
13
|
+
// conversations, Dev its places (@hanzo/build's New, Projects, Issues, Library,
|
|
14
|
+
// Templates, Scheduled tasks, Machines, Environments, Customize, Docs) and its
|
|
15
|
+
// runs, Studio its own. Nothing of Dev's is raised to the rail, and nothing of
|
|
16
|
+
// the rail's is listed inside Dev: one level of the app a place.
|
|
15
17
|
//
|
|
16
18
|
// It stands on the frame's floor, never cut as a pane (ground.ts): the rail and
|
|
17
19
|
// the sidebar beside it are the window, and the content is the panel inset in
|
|
@@ -20,35 +22,17 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
20
22
|
// The sidebar's toggle leads it (`toggle`) where the host has no bar of its own:
|
|
21
23
|
// a browser tab. A host that is a window draws the toggle in its bar beside Back
|
|
22
24
|
// and Forward (App.tsx `bar`), and its rail starts with Chat. On a phone the
|
|
23
|
-
// rail lies along the foot of the drawer (`flat`), the account at its end
|
|
24
|
-
|
|
25
|
-
// Customize lead More there.
|
|
26
|
-
import { Boxes, Clock, Code2, Ellipsis, FolderGit2, LayoutTemplate, Library, ListTodo, MessageSquare, PanelLeft, Puzzle, Server, SquareTerminal, Users, WandSparkles } from 'lucide-react';
|
|
25
|
+
// rail lies along the foot of the drawer (`flat`), the account at its end.
|
|
26
|
+
import { Code2, Ellipsis, MessageSquare, PanelLeft, SquareTerminal, Users, WandSparkles } from 'lucide-react';
|
|
27
27
|
import { XStack, YStack } from '@hanzo/gui';
|
|
28
|
-
import { route } from '@hanzo/build';
|
|
29
28
|
import { DropdownMenu } from '@hanzo/ui';
|
|
30
29
|
import { out } from './help.js';
|
|
31
30
|
import { Me } from './Me.js';
|
|
32
|
-
import {
|
|
31
|
+
import { TABS, WORKSPACE } from './lib/host.js';
|
|
33
32
|
/** The rail's width, px. */
|
|
34
33
|
export const RAIL = 48;
|
|
35
34
|
/** A mark, px: a 36px square on a 44px pitch. */
|
|
36
35
|
const MARK = 36;
|
|
37
|
-
/** The places the rail raises out of Dev, by the builder address each opens. */
|
|
38
|
-
const RAISED = { library: '-/artifacts', scheduled: '-/automations', customize: '-/customize' };
|
|
39
|
-
/** Which raised place a builder address stands on, if any. */
|
|
40
|
-
function raised(at) {
|
|
41
|
-
if (at === null)
|
|
42
|
-
return null;
|
|
43
|
-
const r = route(at);
|
|
44
|
-
if (r.kind === 'screen' && r.screen === 'artifacts')
|
|
45
|
-
return 'library';
|
|
46
|
-
if ((r.kind === 'screen' && r.screen === 'automations') || r.kind === 'automation')
|
|
47
|
-
return 'scheduled';
|
|
48
|
-
if (r.kind === 'customize')
|
|
49
|
-
return 'customize';
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
36
|
/** One mark: an icon button named for where it goes, its name its tooltip, lit where the reader is. */
|
|
53
37
|
export function Mark({ label, on = false, onPress, children, ...rest }) {
|
|
54
38
|
return (_jsx(XStack, { render: "button", "aria-label": label, ...{ title: label }, "aria-current": on ? 'page' : undefined, onPress: onPress, width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", shrink: 0, cursor: "pointer", opacity: on ? 1 : 0.62, bg: on ? '$edge' : 'transparent', hoverStyle: { bg: on ? '$edge' : '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' },
|
|
@@ -59,28 +43,12 @@ export function Mark({ label, on = false, onPress, children, ...rest }) {
|
|
|
59
43
|
export function Toggle({ open, onPress, label }) {
|
|
60
44
|
return (_jsx(Mark, { label: label ?? (open ? 'Close sidebar' : 'Open sidebar'), "data-slot": "sidebar-toggle", "aria-expanded": open, onPress: onPress, children: _jsx(PanelLeft, { size: 18, "aria-hidden": true }) }));
|
|
61
45
|
}
|
|
62
|
-
export function Rail({ host, mode,
|
|
63
|
-
|
|
64
|
-
const to = (p) => () => go(app(p));
|
|
65
|
-
// Everything else the app reaches, behind More: on a phone the raised places first, then Dev's other places, then the products on hosts of their own.
|
|
46
|
+
export function Rail({ host, mode, open, toggle, flat = false, onChat, onDev, onStudio, onOrg, }) {
|
|
47
|
+
// The products on hosts of their own, behind More.
|
|
66
48
|
const rest = [
|
|
67
|
-
...(flat
|
|
68
|
-
? [
|
|
69
|
-
{ key: 'library', label: 'Library', icon: _jsx(Library, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.library) },
|
|
70
|
-
{ key: 'scheduled', label: 'Scheduled tasks', icon: _jsx(Clock, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.scheduled) },
|
|
71
|
-
{ key: 'customize', label: 'Customize', icon: _jsx(Puzzle, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.customize) },
|
|
72
|
-
{ type: 'separator', key: 'raised' },
|
|
73
|
-
]
|
|
74
|
-
: []),
|
|
75
|
-
{ key: 'projects', label: 'Projects', icon: _jsx(FolderGit2, { size: 16, "aria-hidden": true }), onSelect: to('-/projects') },
|
|
76
|
-
{ key: 'templates', label: 'Templates', icon: _jsx(LayoutTemplate, { size: 16, "aria-hidden": true }), onSelect: to('-/templates') },
|
|
77
|
-
{ key: 'issues', label: 'Issues', icon: _jsx(ListTodo, { size: 16, "aria-hidden": true }), onSelect: to('-/issues') },
|
|
78
|
-
{ key: 'machines', label: 'Machines', icon: _jsx(Server, { size: 16, "aria-hidden": true }), onSelect: to('-/settings/machines') },
|
|
79
|
-
{ key: 'environments', label: 'Environments', icon: _jsx(Boxes, { size: 16, "aria-hidden": true }), onSelect: to('-/settings/environments') },
|
|
80
|
-
{ type: 'separator', key: 'away' },
|
|
81
49
|
{ key: 'tabs', label: 'Tabs', icon: _jsx(SquareTerminal, { size: 16, "aria-hidden": true }), onSelect: () => out(TABS) },
|
|
82
50
|
{ key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) },
|
|
83
51
|
];
|
|
84
52
|
const Stack = flat ? XStack : YStack;
|
|
85
|
-
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'
|
|
53
|
+
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', onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Studio", on: mode === 'studio', onPress: onStudio, "data-slot": "rail-studio", children: _jsx(WandSparkles, { 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 })] }));
|
|
86
54
|
}
|
package/dist/Shell.js
CHANGED
|
@@ -77,6 +77,7 @@ import { netKey } from "./conversations.js";
|
|
|
77
77
|
import { FIRST, SettingsList, } from "./Settings.js";
|
|
78
78
|
import { Me } from "./Me.js";
|
|
79
79
|
import { Palette } from "./Palette.js";
|
|
80
|
+
import { tidy } from "./lib/title.js";
|
|
80
81
|
import { summons } from "./seek.js";
|
|
81
82
|
import { called } from "./team.js";
|
|
82
83
|
import { checkoutUrl } from './lib/pay.js';
|
|
@@ -393,7 +394,7 @@ function Chats({ filter = "", onPick, }) {
|
|
|
393
394
|
const mergedThreads = useMemo(() => {
|
|
394
395
|
const list = threads.map((t) => ({
|
|
395
396
|
id: t.id,
|
|
396
|
-
title: t.title
|
|
397
|
+
title: tidy(t.title),
|
|
397
398
|
updatedAt: t.updatedAt,
|
|
398
399
|
thread: t,
|
|
399
400
|
}));
|
|
@@ -409,7 +410,7 @@ function Chats({ filter = "", onPick, }) {
|
|
|
409
410
|
}
|
|
410
411
|
return list;
|
|
411
412
|
}, [threads, customThreads]);
|
|
412
|
-
const found = mergedThreads.filter((t) => matches(t.title
|
|
413
|
+
const found = mergedThreads.filter((t) => matches(tidy(t.title), filter));
|
|
413
414
|
const byId = useMemo(() => new Map(threads.map((t) => [t.id, t])), [threads]);
|
|
414
415
|
// PINNED FIRST, by the server's pin (thread.tsx), then the days.
|
|
415
416
|
const shown = found.filter((t) => !t.thread?.pinned);
|
|
@@ -433,7 +434,7 @@ function Chats({ filter = "", onPick, }) {
|
|
|
433
434
|
const item = (_jsx(SidebarItem, { active: t.id === thread, ...(t.thread ? { pr: "$8", $touchable: { pr: 52 } } : null), onPress: () => {
|
|
434
435
|
open(t.id);
|
|
435
436
|
onPick?.();
|
|
436
|
-
}, children: _jsxs(YStack, { minW: 0, children: [_jsxs(XStack, { items: "center", gap: "$1.5", minW: 0, children: [t.kind === "call" ? _jsx(Video, { size: 13, color: "var(--muted-foreground)", "aria-label": "Call" }) : null, t.kind === "group" ? _jsx(Users, { size: 13, color: "var(--muted-foreground)", "aria-label": "Group" }) : null, _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: t.title
|
|
437
|
+
}, children: _jsxs(YStack, { minW: 0, children: [_jsxs(XStack, { items: "center", gap: "$1.5", minW: 0, children: [t.kind === "call" ? _jsx(Video, { size: 13, color: "var(--muted-foreground)", "aria-label": "Call" }) : null, t.kind === "group" ? _jsx(Users, { size: 13, color: "var(--muted-foreground)", "aria-label": "Group" }) : null, _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: tidy(t.title) })] }), _jsx(Span, { id: t.id, updatedAt: t.updatedAt })] }) }, t.id));
|
|
437
438
|
// A stored conversation carries its menu; a call or a group is no conversation of the store's.
|
|
438
439
|
return t.thread ? (_jsx(ThreadRow, { t: t.thread, active: t.id === thread, listed: false, children: item }, t.id)) : (item);
|
|
439
440
|
}) }, band.label)))] }) }), _jsx(SharedWithYou, { filter: filter, onPick: onPick })] }));
|
|
@@ -455,13 +456,13 @@ function SharedWithYou({ filter, onPick }) {
|
|
|
455
456
|
on = false;
|
|
456
457
|
};
|
|
457
458
|
}, []);
|
|
458
|
-
const shown = (items ?? []).filter((one) => matches(one.title
|
|
459
|
+
const shown = (items ?? []).filter((one) => matches(tidy(one.title), filter));
|
|
459
460
|
if (shown.length === 0)
|
|
460
461
|
return null;
|
|
461
462
|
return (_jsx(SidebarSection, { label: "Shared with you", children: shown.map((one) => (_jsx(SidebarItem, { onPress: () => {
|
|
462
463
|
onPick?.();
|
|
463
464
|
window.location.assign(address(one.share));
|
|
464
|
-
}, children: _jsxs(YStack, { minW: 0, children: [_jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: one.title
|
|
465
|
+
}, children: _jsxs(YStack, { minW: 0, children: [_jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: tidy(one.title) }), one.by ? (_jsxs(Text, { fontSize: "$1", color: "$soft", numberOfLines: 1, children: ["From ", one.by] })) : null] }) }, one.share))) }));
|
|
465
466
|
}
|
|
466
467
|
/**
|
|
467
468
|
* The projects. A run is something that happened, a project is the thing it
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A title fit to show: a conversation's or a run's title as the server kept it,
|
|
3
|
+
* less the markup a first message can carry (`<workspace-files>{…}</workspace-files>`,
|
|
4
|
+
* a lone tag, a leading JSON object), its whitespace one space, cut to `max`
|
|
5
|
+
* characters with an ellipsis; `fallback` when nothing readable is left.
|
|
6
|
+
*/
|
|
7
|
+
export declare function tidy(title: string | null | undefined, max?: number, fallback?: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A title fit to show: a conversation's or a run's title as the server kept it,
|
|
3
|
+
* less the markup a first message can carry (`<workspace-files>{…}</workspace-files>`,
|
|
4
|
+
* a lone tag, a leading JSON object), its whitespace one space, cut to `max`
|
|
5
|
+
* characters with an ellipsis; `fallback` when nothing readable is left.
|
|
6
|
+
*/
|
|
7
|
+
export function tidy(title, max = 80, fallback = 'Untitled') {
|
|
8
|
+
let t = (title ?? '').replace(/<([a-z][\w-]*)[^>]*>[\s\S]*?<\/\1>/gi, ' ').replace(/<\/?[a-z][\w-]*[^>]*>/gi, ' ');
|
|
9
|
+
// A JSON object the message opened with, balanced, goes whole.
|
|
10
|
+
const m = t.match(/^\s*\{/);
|
|
11
|
+
if (m) {
|
|
12
|
+
let depth = 0;
|
|
13
|
+
let quoted = false;
|
|
14
|
+
for (let i = m[0].length - 1; i < t.length; i++) {
|
|
15
|
+
const c = t[i];
|
|
16
|
+
if (quoted) {
|
|
17
|
+
if (c === '\\')
|
|
18
|
+
i++;
|
|
19
|
+
else if (c === '"')
|
|
20
|
+
quoted = false;
|
|
21
|
+
}
|
|
22
|
+
else if (c === '"')
|
|
23
|
+
quoted = true;
|
|
24
|
+
else if (c === '{')
|
|
25
|
+
depth++;
|
|
26
|
+
else if (c === '}' && --depth === 0) {
|
|
27
|
+
t = t.slice(i + 1);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
t = t.replace(/\s+/g, ' ').trim();
|
|
33
|
+
if (!t)
|
|
34
|
+
return fallback;
|
|
35
|
+
return t.length > max ? `${t.slice(0, max - 1).trimEnd()}…` : t;
|
|
36
|
+
}
|
package/dist/seek.d.ts
CHANGED
|
@@ -33,5 +33,14 @@ export declare function rank<T extends Hit>(rows: T[], query: string, cap?: numb
|
|
|
33
33
|
* `CAP`, a group with nothing left out.
|
|
34
34
|
*/
|
|
35
35
|
export declare function find<T extends Hit>(query: string, chats: T[], groups: Group<T>[]): Group<T>[];
|
|
36
|
+
/**
|
|
37
|
+
* The same search for any product: an empty field lists the newest `RECENT` of
|
|
38
|
+
* `recent` under its label (a product's own recents: Chat's conversations, Dev's
|
|
39
|
+
* runs), and a query ranks every group in the order given.
|
|
40
|
+
*/
|
|
41
|
+
export declare function findIn<T extends Hit>(query: string, recent: {
|
|
42
|
+
label: string;
|
|
43
|
+
items: T[];
|
|
44
|
+
}, groups: Group<T>[]): Group<T>[];
|
|
36
45
|
/** Whether a key press opens the palette: ⌘K or Ctrl+K, once a press, from anywhere, a field included. */
|
|
37
46
|
export declare const summons: (e: Press) => boolean;
|
package/dist/seek.js
CHANGED
|
@@ -43,13 +43,19 @@ export function rank(rows, query, cap = CAP) {
|
|
|
43
43
|
* `CAP`, a group with nothing left out.
|
|
44
44
|
*/
|
|
45
45
|
export function find(query, chats, groups) {
|
|
46
|
+
return findIn(query, { label: 'Recent chats', items: chats }, [{ id: 'chats', label: 'Chats', items: chats }, ...groups]);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The same search for any product: an empty field lists the newest `RECENT` of
|
|
50
|
+
* `recent` under its label (a product's own recents: Chat's conversations, Dev's
|
|
51
|
+
* runs), and a query ranks every group in the order given.
|
|
52
|
+
*/
|
|
53
|
+
export function findIn(query, recent, groups) {
|
|
46
54
|
if (!query.trim()) {
|
|
47
|
-
const newest = [...
|
|
48
|
-
return newest.length ? [{ id: 'recent', label:
|
|
55
|
+
const newest = [...recent.items].sort((a, b) => (b.at ?? '').localeCompare(a.at ?? '')).slice(0, RECENT);
|
|
56
|
+
return newest.length ? [{ id: 'recent', label: recent.label, items: newest }] : [];
|
|
49
57
|
}
|
|
50
|
-
return
|
|
51
|
-
.map((g) => ({ ...g, items: rank(g.items, query) }))
|
|
52
|
-
.filter((g) => g.items.length);
|
|
58
|
+
return groups.map((g) => ({ ...g, items: rank(g.items, query) })).filter((g) => g.items.length);
|
|
53
59
|
}
|
|
54
60
|
/** Whether a key press opens the palette: ⌘K or Ctrl+K, once a press, from anywhere, a field included. */
|
|
55
61
|
export const summons = (e) => (e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && !e.repeat && !e.isComposing && (e.key ?? '').toLowerCase() === 'k';
|
package/dist/thread.js
CHANGED
|
@@ -20,6 +20,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
20
20
|
// flight over what it last read, and reads the store again once it answers.
|
|
21
21
|
// A pin is the server's, never this browser's: it follows the person to every
|
|
22
22
|
// device they sign in on.
|
|
23
|
+
import { tidy } from './lib/title.js';
|
|
23
24
|
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
|
|
24
25
|
import { useAi } from '@hanzo/ai/react';
|
|
25
26
|
import { Archive, ArchiveRestore, Clock, ExternalLink, Link2, MoreHorizontal, Pencil, Pin, PinOff, Share2, Trash2 } from 'lucide-react';
|
|
@@ -128,7 +129,7 @@ export function useThreadActions() {
|
|
|
128
129
|
};
|
|
129
130
|
}, [ai]);
|
|
130
131
|
}
|
|
131
|
-
const titleOf = (t) => t.title
|
|
132
|
+
const titleOf = (t) => tidy(t.title);
|
|
132
133
|
/** A conversation's own address, whole: the app at `/?chat=<id>` (lib/host `talk`), wherever the rooms are drawn. */
|
|
133
134
|
export const href = (id) => new URL(site(talk(id)), window.location.href).href;
|
|
134
135
|
/** Opens a conversation in a tab of its own. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.83",
|
|
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.
|
|
52
|
+
"@hanzo/build": ">=0.2.69",
|
|
53
53
|
"@hanzo/composer": ">=0.2.0",
|
|
54
54
|
"@hanzo/event": ">=0.3.56",
|
|
55
55
|
"@hanzo/gui": "^8.3.5",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@hanzo/ai": "0.6.23",
|
|
69
69
|
"@hanzo/appearance": "0.2.8",
|
|
70
|
-
"@hanzo/build": "0.2.
|
|
70
|
+
"@hanzo/build": "0.2.69",
|
|
71
71
|
"@hanzo/composer": "0.2.0",
|
|
72
72
|
"@hanzo/design": "0.5.34",
|
|
73
73
|
"@hanzo/event": "0.3.56",
|