@hanzo/rooms 0.1.73 → 0.1.75
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/assets/personas/ada.md +187 -0
- package/assets/personas/adi_shamir.md +155 -0
- package/assets/personas/al_khwarizmi.md +168 -0
- package/assets/personas/al_kindi.md +172 -0
- package/assets/personas/alan_kay.md +154 -0
- package/assets/personas/analyst.md +138 -0
- package/assets/personas/andrew_yao.md +155 -0
- package/assets/personas/auguste_kerckhoffs.md +168 -0
- package/assets/personas/barbara_liskov.md +154 -0
- package/assets/personas/butler_lampson.md +156 -0
- package/assets/personas/charles_babbage.md +175 -0
- package/assets/personas/creative.md +141 -0
- package/assets/personas/curry.md +169 -0
- package/assets/personas/cynthia_dwork.md +158 -0
- package/assets/personas/david_chaum.md +157 -0
- package/assets/personas/david_parnas.md +153 -0
- package/assets/personas/dennis_ritchie.md +153 -0
- package/assets/personas/dijkstra.md +187 -0
- package/assets/personas/donald_knuth.md +156 -0
- package/assets/personas/douglas_engelbart.md +175 -0
- package/assets/personas/edgar_codd.md +151 -0
- package/assets/personas/engineer.md +140 -0
- package/assets/personas/feynman.md +178 -0
- package/assets/personas/finney.md +187 -0
- package/assets/personas/fred_brooks.md +152 -0
- package/assets/personas/geoffrey_hinton.md +194 -0
- package/assets/personas/herbert_simon.md +190 -0
- package/assets/personas/hickey.md +169 -0
- package/assets/personas/hopper.md +191 -0
- package/assets/personas/hubert_dreyfus.md +184 -0
- package/assets/personas/humberto_maturana.md +156 -0
- package/assets/personas/index.json +1 -0
- package/assets/personas/jcr_licklider.md +178 -0
- package/assets/personas/jobs.md +177 -0
- package/assets/personas/joe_armstrong.md +155 -0
- package/assets/personas/john_backus.md +179 -0
- package/assets/personas/john_mccarthy.md +177 -0
- package/assets/personas/jon_postel.md +149 -0
- package/assets/personas/joseph_weizenbaum.md +185 -0
- package/assets/personas/judea_pearl.md +192 -0
- package/assets/personas/karen_sparck_jones.md +154 -0
- package/assets/personas/konrad_zuse.md +175 -0
- package/assets/personas/kunihiko_fukushima.md +152 -0
- package/assets/personas/leslie_lamport.md +156 -0
- package/assets/personas/markov.md +171 -0
- package/assets/personas/mentor.md +141 -0
- package/assets/personas/nancy_lynch.md +154 -0
- package/assets/personas/niklaus_wirth.md +154 -0
- package/assets/personas/norbert_wiener.md +179 -0
- package/assets/personas/north-star.md +138 -0
- package/assets/personas/peter_naur.md +151 -0
- package/assets/personas/pike.md +165 -0
- package/assets/personas/radia_perlman.md +156 -0
- package/assets/personas/raj_reddy.md +154 -0
- package/assets/personas/ralph_merkle.md +152 -0
- package/assets/personas/richard_sutton.md +156 -0
- package/assets/personas/robin_milner.md +149 -0
- package/assets/personas/shafi_goldwasser.md +156 -0
- package/assets/personas/shannon.md +184 -0
- package/assets/personas/stafford_beer.md +157 -0
- package/assets/personas/taher_elgamal.md +180 -0
- package/assets/personas/tesla.md +181 -0
- package/assets/personas/tony_hoare.md +153 -0
- package/assets/personas/torvalds.md +188 -0
- package/assets/personas/tpope.md +163 -0
- package/assets/personas/turing.md +188 -0
- package/assets/personas/whitfield_diffie.md +159 -0
- package/assets/personas/zeekay.md +151 -0
- package/dist/App.js +26 -22
- package/dist/Chat.js +63 -46
- package/dist/Column.d.ts +3 -3
- package/dist/Column.js +5 -27
- package/dist/Me.d.ts +8 -3
- package/dist/Me.js +79 -108
- package/dist/Palette.d.ts +18 -0
- package/dist/Palette.js +131 -0
- package/dist/Rail.js +3 -15
- package/dist/Recents.d.ts +1 -2
- package/dist/Recents.js +9 -9
- package/dist/Shell.d.ts +7 -0
- package/dist/Shell.js +65 -46
- package/dist/Top.d.ts +17 -0
- package/dist/Top.js +58 -0
- package/dist/agents.d.ts +64 -0
- package/dist/agents.js +62 -0
- package/dist/crew.d.ts +26 -5
- package/dist/crew.js +208 -83
- package/dist/failure.d.ts +1 -1
- package/dist/failure.js +7 -6
- package/dist/help.d.ts +43 -0
- package/dist/help.js +58 -0
- package/dist/lib/web.d.ts +6 -0
- package/dist/lib/web.js +39 -7
- package/dist/recent.d.ts +2 -2
- package/dist/recent.js +3 -5
- package/dist/seek.d.ts +37 -0
- package/dist/seek.js +55 -0
- package/dist/team.js +3 -11
- package/dist/thread.js +7 -2
- package/dist/view.d.ts +44 -0
- package/dist/view.js +98 -0
- package/package.json +5 -5
- package/dist/Account.d.ts +0 -12
- package/dist/Account.js +0 -65
- package/dist/Find.d.ts +0 -7
- package/dist/Find.js +0 -82
- package/dist/Profile.d.ts +0 -4
- package/dist/Profile.js +0 -67
- package/dist/Standing.d.ts +0 -4
- package/dist/Standing.js +0 -16
package/dist/Chat.js
CHANGED
|
@@ -38,7 +38,7 @@ import { download, dropped, fileOf, pct, readable, refOf, retrieve } from "./lib
|
|
|
38
38
|
import { channel, drop, hold, landed, localOf, spend, take, usePane, useWorkFile } from "./pane.js";
|
|
39
39
|
import { Face, brief as roomBrief, join, roleOf, roomTurn, roster as rosterLine, rosterOf, speakers, speaksOf, voiceOf } from "./cast.js";
|
|
40
40
|
import { cleanForSpeech, speakAgent } from "./speech.js";
|
|
41
|
-
import {
|
|
41
|
+
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";
|
|
@@ -49,7 +49,7 @@ import { app, dev, go } from "./lib/host.js";
|
|
|
49
49
|
import { useRooms } from "./host.js";
|
|
50
50
|
import { chats, house, paidPlan, paused, setCreditsAfterAllowance, useLimits } from "./lib/limits.js";
|
|
51
51
|
import { LimitedBanner } from "@hanzo/ui/product/LimitedBanner";
|
|
52
|
-
import { WEB } from "./lib/web.js";
|
|
52
|
+
import { RESEARCH, WEB } from "./lib/web.js";
|
|
53
53
|
import { useHydrated } from "./lib/hydrated.js";
|
|
54
54
|
import { base, served } from "./lib/ai.js";
|
|
55
55
|
import { onServed } from "./lib/served.js";
|
|
@@ -63,7 +63,7 @@ import { Share } from "./Share.js";
|
|
|
63
63
|
import { ConversationMenu, ThreadMore, useThreadList } from "./thread.js";
|
|
64
64
|
import { Schedule } from "./Schedule.js";
|
|
65
65
|
import { keepFile, useAttach, useKeep } from "./keep.js";
|
|
66
|
-
import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, Summary, useDeck, useKey, useMind, usePanel, frames, name as tabName, } from "@hanzo/build";
|
|
66
|
+
import { artifacts, lead, nameOf, PAGE, Panel, PanelToggle, prompt, renders, Reply, Served, Summary, useDeck, useKey, Suggestions, useCarry, useLanguage, useMind, useSuggestions, usePanel, frames, name as tabName, } from "@hanzo/build";
|
|
67
67
|
import { checkoutUrl } from './lib/pay.js';
|
|
68
68
|
import { Upgrade, useFree } from './Upgrade.js';
|
|
69
69
|
import { enter, LOGIN } from './lib/destination.js';
|
|
@@ -88,26 +88,6 @@ const SCRIM = mix("var(--pure-black)", 72, "srgb");
|
|
|
88
88
|
* room has always read at, so an untouched setting changes nothing.
|
|
89
89
|
*/
|
|
90
90
|
const MEASURE = "var(--container-prose, 48rem)";
|
|
91
|
-
/**
|
|
92
|
-
* WHAT AN EMPTY ROOM OFFERS.
|
|
93
|
-
*
|
|
94
|
-
* A blank field asks the reader to invent the first move, and the commonest
|
|
95
|
-
* answer to "what can I help with?" is not knowing what to ask for. These are
|
|
96
|
-
* four shapes of question rather than four topics — read the code you have,
|
|
97
|
-
* find the fault in what it printed, plan the thing you have not built, and
|
|
98
|
-
* shorten something long — so between them they say what kind of help this is
|
|
99
|
-
* without claiming a skill nobody verified.
|
|
100
|
-
*
|
|
101
|
-
* Pressing one FILLS the field and does not send it. Each is the opening of a
|
|
102
|
-
* question the reader has to finish with their own material, and sending it as
|
|
103
|
-
* written would ask about nothing.
|
|
104
|
-
*/
|
|
105
|
-
const STARTERS = [
|
|
106
|
-
"Explain what this code does",
|
|
107
|
-
"Find the fault in this stack trace",
|
|
108
|
-
"Plan how to ship this feature",
|
|
109
|
-
"Summarise this document",
|
|
110
|
-
];
|
|
111
91
|
/**
|
|
112
92
|
* The store, or null when the browser will not give us one.
|
|
113
93
|
*
|
|
@@ -522,12 +502,24 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
522
502
|
// (@hanzo/build `useMind`): the catalog from `GET /v1/models` and what the
|
|
523
503
|
// reader kept, changed from the composer's chip or from Settings.
|
|
524
504
|
const target = useMemo(() => ({ api: api(), token: bearer, org: org() }), [anonymous]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
525
|
-
|
|
505
|
+
// THE PLAN decides what the chip may offer: Free answers from the free lane
|
|
506
|
+
// alone, so it opens there and lists the rest as upgrades (@hanzo/build).
|
|
507
|
+
const catalog = useRef([]);
|
|
508
|
+
const { limits, notice, reload: reread } = useLimits(!anonymous, org(), (id) => nameOf(id, catalog.current));
|
|
509
|
+
const mind = useMind(target, anonymous ? "free" : limits?.plan);
|
|
510
|
+
catalog.current = mind.models;
|
|
526
511
|
const ai = useAi();
|
|
527
512
|
// In the app's frame the composer floats on the pane as a pane of its own.
|
|
528
513
|
const framed = useContext(Framed);
|
|
529
514
|
const opened = useOpen();
|
|
530
515
|
const { thread: open, open: reopen, agents: room, openAgent, seat, emptied, aside, showAside, showSettings } = opened;
|
|
516
|
+
// What the conversation carries beside its words (@hanzo/build `useCarry`):
|
|
517
|
+
// the skills picked from `+`, what Hanzo remembers and the mode, said at the
|
|
518
|
+
// end of the room's system turn so every request carries them.
|
|
519
|
+
const carry = useCarry(target, open ?? "", (tab) => go(app(`-/customize/${tab}`)));
|
|
520
|
+
const agentShelf = useAgentShelf();
|
|
521
|
+
// Up to three things worth doing next, from the person's own activity (@hanzo/build).
|
|
522
|
+
const hints = useSuggestions(target);
|
|
531
523
|
// THE HEADER'S ⋯ IS THE ROW'S MENU (thread.tsx): rename, pin, share, its
|
|
532
524
|
// link, a new tab, archive and delete, kept by the server. A conversation not
|
|
533
525
|
// in the list yet (just begun, or archived) has no menu until it is.
|
|
@@ -695,7 +687,6 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
695
687
|
// bars and no near note — those live on Settings → Usage. It says something
|
|
696
688
|
// only once the reader is turned away: a reply answered from a free model, or
|
|
697
689
|
// a refusal.
|
|
698
|
-
const { limits, notice, reload: reread } = useLimits(!anonymous, org(), (id) => nameOf(id, listed));
|
|
699
690
|
const held = paidPlan(limits);
|
|
700
691
|
const pause = notice && (notice.fallback || notice.refused) ? notice : null;
|
|
701
692
|
// The banner's words and ways: the plan by name, when it comes back, and the two ways on.
|
|
@@ -723,7 +714,10 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
723
714
|
// web tools mark the turn, and the client's fetch (lib/web.ts) looks it up
|
|
724
715
|
// before the model is asked and asks without them, so a question about the
|
|
725
716
|
// world, in any language, is answered from what the web says now and streams.
|
|
726
|
-
params: {
|
|
717
|
+
params: {
|
|
718
|
+
reasoning_effort: mind.effort,
|
|
719
|
+
...(!anonymous && house(rowOf(model)?.family) ? { tools: carry.gear.gear.research ? [...WEB, RESEARCH] : WEB } : {}),
|
|
720
|
+
},
|
|
727
721
|
...(open ? { thread: open } : {}),
|
|
728
722
|
onError: (e) => {
|
|
729
723
|
const needs = planRequired(e);
|
|
@@ -763,7 +757,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
763
757
|
// of them talks. It is the first turn every request carries and it follows the room, so
|
|
764
758
|
// pressing a face adds them to the conversation that is open rather than
|
|
765
759
|
// starting another; the turns already said stay where they are.
|
|
766
|
-
const turn = useMemo(() => roomTurn(room, brief), [room, brief]);
|
|
760
|
+
const turn = useMemo(() => [roomTurn(room, brief), carry.text].filter(Boolean).join("\n\n") || null, [room, brief, carry.text]);
|
|
767
761
|
const turnNow = useRef(turn);
|
|
768
762
|
turnNow.current = turn;
|
|
769
763
|
const roomNow = useRef(room);
|
|
@@ -882,7 +876,9 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
882
876
|
// as a transcript.
|
|
883
877
|
const { token } = useIamToken();
|
|
884
878
|
const ear = useMemo(() => speech({ ...base(), ...(token ? { token } : {}) }), [token]);
|
|
885
|
-
|
|
879
|
+
// The language dictation listens in: the person's own (Settings › General), or the browser's.
|
|
880
|
+
const [spoken, speakIn] = useLanguage(target);
|
|
881
|
+
const voice = useVoice({ speech: ear, ...(spoken ? { language: spoken } : {}), ...useDictation(draft, setDraft) });
|
|
886
882
|
// TALK MODE: a hands-free conversation on Hanzo's realtime socket, /v1/voice.
|
|
887
883
|
// The mic streams up, the answer streams back as speech with its words in
|
|
888
884
|
// `talk.reply`, and talking over it stops it. Its own conversation, so its
|
|
@@ -1248,7 +1244,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1248
1244
|
: how?.provider
|
|
1249
1245
|
? `${familyOf(model) || nameOf(model, listed)} is unavailable right now.`
|
|
1250
1246
|
: plainly(wrong, "this conversation");
|
|
1251
|
-
return (_jsx(Failure, { onRetry: resign || bill?.code === "free_plan_cap" ? undefined : () => speak(text, question.id), children: _jsxs(YStack, { gap: "$2", children: [_jsxs(Text, { fontSize: "$3", color: "$ink", children: [whom.length ? `No answer from ${join(whom)}. ` : "", said] }), how?.provider && wrong ? _jsx(Why, { text: detail(wrong) }) : null, !bill && (resign ||
|
|
1247
|
+
return (_jsx(Failure, { onRetry: resign || lift || bill?.code === "free_plan_cap" ? undefined : () => speak(text, question.id), children: _jsxs(YStack, { gap: "$2", children: [_jsxs(Text, { fontSize: "$3", color: "$ink", children: [whom.length ? `No answer from ${join(whom)}. ` : "", said] }), how?.provider && wrong ? _jsx(Why, { text: detail(wrong) }) : null, lift || (!bill && (resign || how?.wallet || how?.visitor)) ? (_jsx(XStack, { gap: "$2", flexWrap: "wrap", children: resign ? (_jsx(Button, { size: "sm", onClick: () => {
|
|
1252
1248
|
if (typeof window !== "undefined") {
|
|
1253
1249
|
localStorage.removeItem("hanzo_iam_access_token");
|
|
1254
1250
|
localStorage.removeItem("hanzo_iam_refresh_token");
|
|
@@ -1337,12 +1333,12 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1337
1333
|
if (!open)
|
|
1338
1334
|
setPlan(null);
|
|
1339
1335
|
}, 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 || "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, 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, 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,
|
|
1340
|
-
// AN EMPTY ROOM CENTRES ITS COMPOSER. While nothing has been
|
|
1341
|
-
// opening (the heading
|
|
1342
|
-
//
|
|
1343
|
-
// the composer
|
|
1344
|
-
//
|
|
1345
|
-
center:
|
|
1336
|
+
// AN EMPTY ROOM CENTRES ITS COMPOSER on a laptop. While nothing has been
|
|
1337
|
+
// said the opening (the heading) and the composer sit as one group in the
|
|
1338
|
+
// middle of the height; the moment a turn exists the transcript takes the
|
|
1339
|
+
// height and the composer docks under it. A phone keeps it at the foot,
|
|
1340
|
+
// over the keyboard, from the first moment.
|
|
1341
|
+
center: media.sm,
|
|
1346
1342
|
// AS IT WAS WRITTEN. The package renders a turn's words plainly and
|
|
1347
1343
|
// says so — it ships no markdown pipeline because the plugin set is the
|
|
1348
1344
|
// surface's to choose — so an answer arrived here reading "```ts" with
|
|
@@ -1400,9 +1396,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1400
1396
|
// with it when it did. `Ask` passes its own, because the page embedding
|
|
1401
1397
|
// it already has an `<h1>` and a second is the defect that took the
|
|
1402
1398
|
// landings off /chat and /dev.
|
|
1403
|
-
empty: heading === null ? undefined : (_jsxs(YStack, { "data-slot": "opening", items: "center", width: "100%", py: "$6", 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
|
|
1404
|
-
setDraft(`${one} `);
|
|
1405
|
-
}, px: "$3", py: "$2", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: "$raised" }, children: _jsx(Text, { fontSize: "$2", color: "$soft", children: one }) }, one))) })] })),
|
|
1399
|
+
empty: heading === null ? undefined : (_jsxs(YStack, { "data-slot": "opening", items: "center", width: "100%", py: "$6", 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] })),
|
|
1406
1400
|
// The transcript reads at the same measure. `Thread` defaults to 768
|
|
1407
1401
|
// itself; handing it the column here is what lets Width reach it.
|
|
1408
1402
|
column: { maxW: MEASURE }, composer: prompt({
|
|
@@ -1413,9 +1407,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1413
1407
|
busy: chat.streaming,
|
|
1414
1408
|
// Files alone are a message: what is held goes with an empty draft too.
|
|
1415
1409
|
ready: Boolean(draft.trim() || files.length),
|
|
1416
|
-
//
|
|
1417
|
-
// `label` keeps the accessible name constant while it does.
|
|
1418
|
-
placeholder: room.length ? `Message ${join(room)}` : placeholder,
|
|
1410
|
+
// `label` keeps the accessible name constant; the placeholder is the composer's own.
|
|
1419
1411
|
label: ASK,
|
|
1420
1412
|
note: caution(),
|
|
1421
1413
|
// A phone's pill is the tall one: the field across the top, the controls under it.
|
|
@@ -1441,15 +1433,40 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1441
1433
|
: talk.state === "listening"
|
|
1442
1434
|
? "Listening — speak, and Hanzo answers aloud."
|
|
1443
1435
|
: "Connecting…" })) : null] })) : undefined,
|
|
1444
|
-
// THE
|
|
1445
|
-
// the
|
|
1446
|
-
|
|
1447
|
-
|
|
1436
|
+
// THE `+`: files (the Sources tab's "Attach files", one press closer),
|
|
1437
|
+
// who is in the conversation, the skills, and the two switches.
|
|
1438
|
+
plus: anonymous
|
|
1439
|
+
? { upload: () => take(at, via) }
|
|
1440
|
+
: {
|
|
1441
|
+
upload: () => take(at, via),
|
|
1442
|
+
agents: agentShelf,
|
|
1443
|
+
skills: carry.skills,
|
|
1444
|
+
research: { on: carry.gear.gear.research, set: (on) => carry.gear.set({ research: on }) },
|
|
1445
|
+
memory: { on: carry.gear.gear.memory, set: (on) => carry.gear.set({ memory: on }) },
|
|
1446
|
+
},
|
|
1447
|
+
// Who is in the room, then what the conversation carries, as chips after `+`.
|
|
1448
|
+
on: [
|
|
1449
|
+
...room.map((name) => ({
|
|
1450
|
+
id: `agent:${name}`,
|
|
1451
|
+
label: name,
|
|
1452
|
+
icon: _jsx(Face, { src: faces[name]?.avatar, emoji: faces[name]?.emoji, name: name, size: 16 }),
|
|
1453
|
+
onRemove: () => seat(room.filter((one) => one !== name)),
|
|
1454
|
+
})),
|
|
1455
|
+
...(anonymous ? [] : carry.on),
|
|
1456
|
+
],
|
|
1457
|
+
slash: anonymous ? undefined : carry.skills,
|
|
1458
|
+
mode: anonymous ? null : { value: carry.gear.gear.mode, set: (m) => carry.gear.set({ mode: m }) },
|
|
1459
|
+
// The model, the effort and the mode under the frame (@hanzo/build).
|
|
1448
1460
|
mind,
|
|
1449
1461
|
limits,
|
|
1462
|
+
onUpgrade: (id) => setAsk({ model: nameOf(id, listed) }),
|
|
1450
1463
|
voice,
|
|
1464
|
+
language: spoken,
|
|
1465
|
+
onLanguage: speakIn,
|
|
1451
1466
|
talk,
|
|
1452
|
-
|
|
1467
|
+
// While nothing has been said: what is worth doing next.
|
|
1468
|
+
below: said.length === 0 && !anonymous ? _jsx(Suggestions, { rows: hints.rows, onPick: setDraft, onDismiss: hints.dismiss }) : undefined,
|
|
1469
|
+
}) }), _jsx(AgentMaker, {}), isFree(model) ? (_jsx(XStack, { items: "center", justify: "center", py: "$1", px: "$3", borderTopWidth: 1, borderColor: "$borderColor", bg: "$background", children: _jsxs(Text, { fontSize: "$1", color: "$soft", children: ["Free chat is data-shared to train models. ", _jsx(Anchor, { href: site("/privacy"), fontSize: "$1", color: "$ink", children: "Privacy" })] }) })) : null, consent, _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
|
|
1453
1470
|
}
|
|
1454
1471
|
/** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
|
|
1455
1472
|
function Consent({ question, onAgree, onCancel, }) {
|
package/dist/Column.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Host } from '@hanzo/build';
|
|
|
2
2
|
import { type RailLink } from '@hanzo/ui/chat';
|
|
3
3
|
import { type ReactNode } from 'react';
|
|
4
4
|
import { type Place } from './lib/host.js';
|
|
5
|
-
export declare function Column({ host, mode, thread, go, onShut,
|
|
5
|
+
export declare function Column({ host, mode, thread, go, onShut, onSeek, drawer, foot, places, }: {
|
|
6
6
|
host: Host;
|
|
7
7
|
mode: Place['mode'];
|
|
8
8
|
/** The Chat conversation the pane holds, or null. */
|
|
@@ -11,8 +11,8 @@ export declare function Column({ host, mode, thread, go, onShut, seek, drawer, f
|
|
|
11
11
|
go: (address: string) => void;
|
|
12
12
|
/** Shut the sidebar: its edge pulled in past the floor. */
|
|
13
13
|
onShut: () => void;
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/** Opens the palette. */
|
|
15
|
+
onSeek?: () => void;
|
|
16
16
|
/** Drawn in the phone's drawer: no edge, cut as a floating pane. */
|
|
17
17
|
drawer?: boolean;
|
|
18
18
|
/** What stands at its foot: the drawer's rail, laid flat. */
|
package/dist/Column.js
CHANGED
|
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
//
|
|
11
11
|
// The bell is the org's run activity (@hanzo/build `useSessions`): the runs
|
|
12
12
|
// that moved last, each one press from its page, and Notifications in Settings.
|
|
13
|
-
// The search opens the
|
|
13
|
+
// The search opens the palette (Palette.tsx; ⌘K / Ctrl+K from anywhere).
|
|
14
14
|
//
|
|
15
15
|
// Dev's places are @hanzo/build's `nav`, the one list every rail draws, each a
|
|
16
16
|
// row under a quiet group label.
|
|
@@ -19,11 +19,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
19
19
|
// double-clicked back (@hanzo/build's `Grip`), kept per browser. Pulled in past
|
|
20
20
|
// its floor it shuts (`onShut`), as the sidebar's toggle shuts it.
|
|
21
21
|
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
22
|
-
import { Bell, Search, Settings2, SquarePen
|
|
22
|
+
import { Bell, Search, Settings2, SquarePen } from 'lucide-react';
|
|
23
23
|
import { DOTS, Grip, nav, NEW, path as written, useSessions } from '@hanzo/build';
|
|
24
24
|
import { DropdownMenu } from '@hanzo/ui';
|
|
25
25
|
import { Sidebar, SidebarIconButton, SidebarItem, StatusDot } from '@hanzo/ui/chat';
|
|
26
|
-
import {
|
|
26
|
+
import { useId } from 'react';
|
|
27
27
|
import { Made } from './Me.js';
|
|
28
28
|
import { Recents } from './Recents.js';
|
|
29
29
|
import { Setup } from './Setup.js';
|
|
@@ -89,11 +89,7 @@ function Activity({ host }) {
|
|
|
89
89
|
];
|
|
90
90
|
return (_jsx(DropdownMenu, { minWidth: 280, trigger: _jsx(XStack, { render: "button", "aria-label": "Activity", ...{ title: 'Activity' }, ...HEAD, items: "center", justify: "center", cursor: "pointer", opacity: 0.7, hoverStyle: { bg: '$hover', opacity: 1 }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: _jsx(Bell, { size: 16, "aria-hidden": true }) }), items: items }));
|
|
91
91
|
}
|
|
92
|
-
export function Column({ host, mode, thread, go, onShut,
|
|
93
|
-
const [words, setWords] = useState('');
|
|
94
|
-
// The search field shows once asked for, and while it holds words.
|
|
95
|
-
const [seeking, setSeeking] = useState(false);
|
|
96
|
-
const field = useRef(null);
|
|
92
|
+
export function Column({ host, mode, thread, go, onShut, onSeek, drawer = false, foot, places, }) {
|
|
97
93
|
// The desktop sidebar and a phone's drawer can both be in the page: each names its own group labels.
|
|
98
94
|
const uid = useId();
|
|
99
95
|
const span = useSpan(SPAN, WIDTH, FLOOR, CEIL);
|
|
@@ -105,27 +101,9 @@ export function Column({ host, mode, thread, go, onShut, seek = 0, drawer = fals
|
|
|
105
101
|
empty();
|
|
106
102
|
go(talk());
|
|
107
103
|
};
|
|
108
|
-
const ask = () => {
|
|
109
|
-
setSeeking(true);
|
|
110
|
-
requestAnimationFrame(() => field.current?.focus());
|
|
111
|
-
};
|
|
112
|
-
const done = () => {
|
|
113
|
-
setWords('');
|
|
114
|
-
setSeeking(false);
|
|
115
|
-
};
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
if (seek)
|
|
118
|
-
ask();
|
|
119
|
-
}, [seek]);
|
|
120
104
|
return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
|
|
121
105
|
// inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
|
|
122
|
-
style: drawer ? undefined : span.style, position: "relative", flex: drawer ? 1 : undefined, shrink: drawer ? 1 : 0, minW: 0, height: "100%", width: drawer ? undefined : 'var(--span)', children: [_jsxs(Sidebar, { "data-slot": "app-rail", role: "navigation", "aria-label": brand(), ...(drawer ? { ...pane(), ...FLOAT } : { bg: 'transparent', borderWidth: 0 }), ...FILL, px: "$2", pt: drawer ? '$2' : 0, pb: "$2", gap: "$0", children: [_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: "$2", pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) }), _jsx(Activity, { host: host }),
|
|
123
|
-
if (e.key === 'Escape')
|
|
124
|
-
done();
|
|
125
|
-
}, onBlur: () => {
|
|
126
|
-
if (!words)
|
|
127
|
-
setSeeking(false);
|
|
128
|
-
}, placeholder: mode === 'chat' ? 'Search chats' : 'Search runs', "aria-label": "Filter recents" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$3" }), words ? (_jsx(SidebarIconButton, { label: "Clear search", onPress: done, width: 28, height: 28, rounded: 999, children: _jsx(X, { size: 14, "aria-hidden": true }) })) : null] })) : null, mode === 'chat' ? (_jsx(YStack, { role: "list", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, bg: "transparent", hoverStyle: { bg: '$hover' }, icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "New chat" }) }) }) })) : (_jsx(YStack, { "data-slot": "app-places", shrink: 0, children: groups.map((g, i) => (_jsxs(YStack, { pt: i ? '$2' : 0, children: [i ? (_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$3", color: "$soft", children: g.label })) : null, _jsx(YStack, { role: "list", "aria-labelledby": i ? `${uid}-${g.id}` : undefined, "aria-label": i ? undefined : g.label, gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.id === NEW ? _jsx(SquarePen, { size: 16, "aria-hidden": true }) : link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: _jsx(Text, { fontSize: "$3", color: link.active ? '$ink' : '$quiet', children: link.label }) }) }, link.id))) })] }, g.id))) })), mode === 'studio' ? null : _jsx(Recents, { host: host, mode: mode, thread: thread, words: words, onOpen: open, onNew: create }), _jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] }), foot] }), drawer ? null : (_jsx(Grip, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: "Resize sidebar",
|
|
106
|
+
style: drawer ? undefined : span.style, position: "relative", flex: drawer ? 1 : undefined, shrink: drawer ? 1 : 0, minW: 0, height: "100%", width: drawer ? undefined : 'var(--span)', children: [_jsxs(Sidebar, { "data-slot": "app-rail", role: "navigation", "aria-label": brand(), ...(drawer ? { ...pane(), ...FLOAT } : { bg: 'transparent', borderWidth: 0 }), ...FILL, px: "$2", pt: drawer ? '$2' : 0, pb: "$2", gap: "$0", children: [_jsxs(XStack, { "data-slot": "app-head", items: "center", gap: "$1", pl: "$2", pr: 2, height: 52, shrink: 0, minW: 0, children: [_jsx(Text, { flex: 1, minW: 0, fontSize: 18, lineHeight: 24, fontWeight: "600", color: "$ink", numberOfLines: 1, children: wordmark(mode) }), _jsx(Activity, { host: host }), _jsx(SidebarIconButton, { label: "Search", onPress: onSeek, "aria-haspopup": "dialog", ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) })] }), mode === 'chat' ? (_jsx(YStack, { role: "list", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, bg: "transparent", hoverStyle: { bg: '$hover' }, icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "New chat" }) }) }) })) : (_jsx(YStack, { "data-slot": "app-places", shrink: 0, children: groups.map((g, i) => (_jsxs(YStack, { pt: i ? '$2' : 0, children: [i ? (_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$3", color: "$soft", children: g.label })) : null, _jsx(YStack, { role: "list", "aria-labelledby": i ? `${uid}-${g.id}` : undefined, "aria-label": i ? undefined : g.label, gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.id === NEW ? _jsx(SquarePen, { size: 16, "aria-hidden": true }) : link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: _jsx(Text, { fontSize: "$3", color: link.active ? '$ink' : '$quiet', children: link.label }) }) }, link.id))) })] }, g.id))) })), mode === 'studio' ? null : _jsx(Recents, { host: host, mode: mode, thread: thread, onOpen: open, onNew: create }), _jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] }), foot] }), drawer ? null : (_jsx(Grip, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: onShut, label: "Resize sidebar",
|
|
129
107
|
// In the gutter between the sidebar and the panel, not over the sidebar's own rows.
|
|
130
108
|
r: GUTTER_END, width: "var(--pane-gap)" }))] }));
|
|
131
109
|
}
|
package/dist/Me.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Host } from '@hanzo/build';
|
|
2
|
+
/** What the menu reads of its host: who is signed in, in which organization, and how they leave. */
|
|
3
|
+
export type Who = Pick<Host, 'api' | 'token' | 'org' | 'person' | 'signOut'>;
|
|
2
4
|
/** One organization as the menu draws it. */
|
|
3
5
|
export interface Org {
|
|
4
6
|
name: string;
|
|
@@ -10,7 +12,7 @@ export interface Org {
|
|
|
10
12
|
export declare const initials: (name: string) => string;
|
|
11
13
|
/** The person's picture, or their initials. */
|
|
12
14
|
export declare function Face({ host, size }: {
|
|
13
|
-
host:
|
|
15
|
+
host: Who;
|
|
14
16
|
size?: number;
|
|
15
17
|
}): import("react").JSX.Element;
|
|
16
18
|
/**
|
|
@@ -20,10 +22,13 @@ export declare function Face({ host, size }: {
|
|
|
20
22
|
*/
|
|
21
23
|
export declare const upgrade: (family: string | null | undefined) => string;
|
|
22
24
|
export declare function Me({ host, narrow, above, onOrg, }: {
|
|
23
|
-
host:
|
|
25
|
+
host: Who;
|
|
24
26
|
/** The rail's: the face alone, the menu opening beside it. */
|
|
25
27
|
narrow?: boolean;
|
|
26
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The menu opens above the face, not beside it: the rail laid along a phone
|
|
30
|
+
* drawer's foot. That copy leaves ⌘/ to the rail's own, so one press opens one dialog.
|
|
31
|
+
*/
|
|
27
32
|
above?: boolean;
|
|
28
33
|
/** Work in another organization: picked, then the app opened in it. */
|
|
29
34
|
onOrg: (org: string) => void;
|