@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.
Files changed (110) hide show
  1. package/assets/personas/ada.md +187 -0
  2. package/assets/personas/adi_shamir.md +155 -0
  3. package/assets/personas/al_khwarizmi.md +168 -0
  4. package/assets/personas/al_kindi.md +172 -0
  5. package/assets/personas/alan_kay.md +154 -0
  6. package/assets/personas/analyst.md +138 -0
  7. package/assets/personas/andrew_yao.md +155 -0
  8. package/assets/personas/auguste_kerckhoffs.md +168 -0
  9. package/assets/personas/barbara_liskov.md +154 -0
  10. package/assets/personas/butler_lampson.md +156 -0
  11. package/assets/personas/charles_babbage.md +175 -0
  12. package/assets/personas/creative.md +141 -0
  13. package/assets/personas/curry.md +169 -0
  14. package/assets/personas/cynthia_dwork.md +158 -0
  15. package/assets/personas/david_chaum.md +157 -0
  16. package/assets/personas/david_parnas.md +153 -0
  17. package/assets/personas/dennis_ritchie.md +153 -0
  18. package/assets/personas/dijkstra.md +187 -0
  19. package/assets/personas/donald_knuth.md +156 -0
  20. package/assets/personas/douglas_engelbart.md +175 -0
  21. package/assets/personas/edgar_codd.md +151 -0
  22. package/assets/personas/engineer.md +140 -0
  23. package/assets/personas/feynman.md +178 -0
  24. package/assets/personas/finney.md +187 -0
  25. package/assets/personas/fred_brooks.md +152 -0
  26. package/assets/personas/geoffrey_hinton.md +194 -0
  27. package/assets/personas/herbert_simon.md +190 -0
  28. package/assets/personas/hickey.md +169 -0
  29. package/assets/personas/hopper.md +191 -0
  30. package/assets/personas/hubert_dreyfus.md +184 -0
  31. package/assets/personas/humberto_maturana.md +156 -0
  32. package/assets/personas/index.json +1 -0
  33. package/assets/personas/jcr_licklider.md +178 -0
  34. package/assets/personas/jobs.md +177 -0
  35. package/assets/personas/joe_armstrong.md +155 -0
  36. package/assets/personas/john_backus.md +179 -0
  37. package/assets/personas/john_mccarthy.md +177 -0
  38. package/assets/personas/jon_postel.md +149 -0
  39. package/assets/personas/joseph_weizenbaum.md +185 -0
  40. package/assets/personas/judea_pearl.md +192 -0
  41. package/assets/personas/karen_sparck_jones.md +154 -0
  42. package/assets/personas/konrad_zuse.md +175 -0
  43. package/assets/personas/kunihiko_fukushima.md +152 -0
  44. package/assets/personas/leslie_lamport.md +156 -0
  45. package/assets/personas/markov.md +171 -0
  46. package/assets/personas/mentor.md +141 -0
  47. package/assets/personas/nancy_lynch.md +154 -0
  48. package/assets/personas/niklaus_wirth.md +154 -0
  49. package/assets/personas/norbert_wiener.md +179 -0
  50. package/assets/personas/north-star.md +138 -0
  51. package/assets/personas/peter_naur.md +151 -0
  52. package/assets/personas/pike.md +165 -0
  53. package/assets/personas/radia_perlman.md +156 -0
  54. package/assets/personas/raj_reddy.md +154 -0
  55. package/assets/personas/ralph_merkle.md +152 -0
  56. package/assets/personas/richard_sutton.md +156 -0
  57. package/assets/personas/robin_milner.md +149 -0
  58. package/assets/personas/shafi_goldwasser.md +156 -0
  59. package/assets/personas/shannon.md +184 -0
  60. package/assets/personas/stafford_beer.md +157 -0
  61. package/assets/personas/taher_elgamal.md +180 -0
  62. package/assets/personas/tesla.md +181 -0
  63. package/assets/personas/tony_hoare.md +153 -0
  64. package/assets/personas/torvalds.md +188 -0
  65. package/assets/personas/tpope.md +163 -0
  66. package/assets/personas/turing.md +188 -0
  67. package/assets/personas/whitfield_diffie.md +159 -0
  68. package/assets/personas/zeekay.md +151 -0
  69. package/dist/App.js +26 -22
  70. package/dist/Chat.js +63 -46
  71. package/dist/Column.d.ts +3 -3
  72. package/dist/Column.js +5 -27
  73. package/dist/Me.d.ts +8 -3
  74. package/dist/Me.js +79 -108
  75. package/dist/Palette.d.ts +18 -0
  76. package/dist/Palette.js +131 -0
  77. package/dist/Rail.js +3 -15
  78. package/dist/Recents.d.ts +1 -2
  79. package/dist/Recents.js +9 -9
  80. package/dist/Shell.d.ts +7 -0
  81. package/dist/Shell.js +65 -46
  82. package/dist/Top.d.ts +17 -0
  83. package/dist/Top.js +58 -0
  84. package/dist/agents.d.ts +64 -0
  85. package/dist/agents.js +62 -0
  86. package/dist/crew.d.ts +26 -5
  87. package/dist/crew.js +208 -83
  88. package/dist/failure.d.ts +1 -1
  89. package/dist/failure.js +7 -6
  90. package/dist/help.d.ts +43 -0
  91. package/dist/help.js +58 -0
  92. package/dist/lib/web.d.ts +6 -0
  93. package/dist/lib/web.js +39 -7
  94. package/dist/recent.d.ts +2 -2
  95. package/dist/recent.js +3 -5
  96. package/dist/seek.d.ts +37 -0
  97. package/dist/seek.js +55 -0
  98. package/dist/team.js +3 -11
  99. package/dist/thread.js +7 -2
  100. package/dist/view.d.ts +44 -0
  101. package/dist/view.js +98 -0
  102. package/package.json +5 -5
  103. package/dist/Account.d.ts +0 -12
  104. package/dist/Account.js +0 -65
  105. package/dist/Find.d.ts +0 -7
  106. package/dist/Find.js +0 -82
  107. package/dist/Profile.d.ts +0 -4
  108. package/dist/Profile.js +0 -67
  109. package/dist/Standing.d.ts +0 -4
  110. 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 { Crew, HOUSE, useCrew } from "./crew.js";
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
- const mind = useMind(target);
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: { reasoning_effort: mind.effort, ...(!anonymous && house(rowOf(model)?.family) ? { tools: WEB } : {}) },
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
- const voice = useVoice({ speech: ear, ...useDictation(draft, setDraft) });
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 || lift || how?.wallet || how?.visitor) ? (_jsx(XStack, { gap: "$2", flexWrap: "wrap", children: resign ? (_jsx(Button, { size: "sm", onClick: () => {
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 said the
1341
- // opening (the heading and the starters) and the composer sit as one
1342
- // group in the middle of the height, with or without the plan bars in
1343
- // the composer's foot; the moment a turn exists the transcript takes the
1344
- // height and the composer docks under it.
1345
- center: true,
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, _jsx(XStack, { flexWrap: "wrap", justify: "center", gap: "$2", maxW: 560, px: "$3", pb: "$4", children: STARTERS.map((one) => (_jsx(Box, { render: "button", onClick: () => {
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
- // A CHARACTER IS WHO YOU ARE WRITING TO, so the field says so, and
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 PAPERCLIP is the Sources tab's "Attach files", one press closer:
1445
- // the same `take`, into the same `hold`.
1446
- onAttach: () => take(at, via),
1447
- // The one model-and-effort chip Dev's composer has (@hanzo/build).
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
- }) }), said.length === 0 ? _jsx(Crew, {}) : null, 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) })] }));
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, seek, drawer, foot, places, }: {
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
- /** Each new value asks for the search, focused (⌘K). */
15
- seek?: number;
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 filter over Recents (⌘K / Ctrl+K from anywhere, `seek`).
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, X } from 'lucide-react';
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 { useEffect, useId, useRef, useState } from 'react';
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, seek = 0, drawer = false, foot, places, }) {
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 }), mode === 'studio' ? null : (_jsx(SidebarIconButton, { label: mode === 'chat' ? 'Search chats' : 'Search runs', onPress: seeking ? done : ask, "aria-expanded": seeking || !!words, ...HEAD, children: _jsx(Search, { size: 16, "aria-hidden": true }) }))] }), seeking || words ? (_jsxs(XStack, { "data-slot": "app-filter", items: "center", gap: "$2", pl: "$3", pr: "$1", height: 36, rounded: 999, borderWidth: 1, borderColor: "$borderColor", bg: "$edge", shrink: 0, mb: "$1.5", children: [_jsx(Search, { size: 15, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { render: _jsx("input", { ref: field, value: words, onChange: (e) => setWords(e.target.value), onKeyDown: (e) => {
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: 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: Host;
25
+ host: Who;
24
26
  /** The rail's: the face alone, the menu opening beside it. */
25
27
  narrow?: boolean;
26
- /** The menu opens above the face, not beside it: the rail laid along a phone drawer's foot. */
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;