@hanzo/rooms 0.1.79 → 0.1.81

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 (2) hide show
  1. package/dist/Chat.js +75 -71
  2. package/package.json +3 -3
package/dist/Chat.js CHANGED
@@ -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, Suggestions, useCarry, useLanguage, useMind, useSuggestions, 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, useMention, 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';
@@ -1348,6 +1348,79 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1348
1348
  window.location.assign(dev(slug));
1349
1349
  },
1350
1350
  };
1351
+ // THE COMPOSER'S OPTIONS, held apart so its keys (the `/` and `@` lists' arrows,
1352
+ // Ctrl+U) hook in through `useMention` before the frame is drawn.
1353
+ const composing = {
1354
+ value: draft,
1355
+ onChange: setDraft,
1356
+ onSend: submit,
1357
+ onStop: chat.stop,
1358
+ busy: chat.streaming,
1359
+ // Files alone are a message: what is held goes with an empty draft too.
1360
+ ready: Boolean(draft.trim() || files.length),
1361
+ // `label` keeps the accessible name constant; the placeholder is the composer's own.
1362
+ label: ASK,
1363
+ note: caution(),
1364
+ // A phone's pill is the tall one: the field across the top, the controls under it.
1365
+ narrow: !media.sm,
1366
+ // In the app's frame the composer floats on the pane as a pane of its own.
1367
+ framed,
1368
+ // A file or an image pasted into the field is held like a picked one;
1369
+ // words paste as words.
1370
+ field: {
1371
+ onPaste: (e) => {
1372
+ if (!e.clipboardData?.files?.length)
1373
+ return;
1374
+ e.preventDefault();
1375
+ void hold(at, Array.from(e.clipboardData.files), via);
1376
+ },
1377
+ },
1378
+ // WHAT GOES WITH THE MESSAGE, above the frame and at its width: the
1379
+ // pause and its ways past, one chip a file, and talk mode's words.
1380
+ head: files.length || turnedAway || prepWrong || preparing || talk.open || talk.refusal || pause ? (_jsxs(_Fragment, { children: [banner ? (_jsx(LimitedBanner, { message: banner.message, actions: banner.actions, onAction: act, onUsage: () => showSettings("usage") })) : null, actWrong ? (_jsx(Text, { role: "alert", "data-slot": "limited-banner-wrong", fontSize: "$2", color: BAD, children: actWrong })) : null, files.length ? (_jsx(XStack, { gap: "$1.5", flexWrap: "wrap", role: "list", "aria-label": "Attached files", "data-slot": "composer-attached", children: files.map((one) => (_jsx(HeldChip, { one: one, onOpen: openFile, onRemove: () => drop(at, one.id), onSave: anonymous ? undefined : (url, id) => keepOne(url, id, one.name, one.type) }, one.id))) })) : null, preparing ? (_jsxs(Text, { role: "status", "aria-live": "polite", "data-slot": "composer-preparing", fontSize: "$2", color: "$soft", children: [preparing, "\u2026"] })) : null, turnedAway || prepWrong ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: turnedAway || prepWrong })) : null, talk.open || talk.refusal ? (_jsx(Text, { role: "status", "aria-live": "polite", "data-slot": "composer-talk-reply", fontSize: "$2", color: talk.refusal ? BAD : "$soft", children: talk.refusal
1381
+ ? worded(talk.refusal)
1382
+ : talk.reply
1383
+ ? `Hanzo: ${talk.reply}`
1384
+ : talk.state === "listening"
1385
+ ? "Listening — speak, and Hanzo answers aloud."
1386
+ : "Connecting…" })) : null] })) : undefined,
1387
+ // THE `+`: files (the Sources tab's "Attach files", one press closer),
1388
+ // who is in the conversation, the skills, and the two switches.
1389
+ plus: anonymous
1390
+ ? { upload: () => take(at, via) }
1391
+ : {
1392
+ upload: () => take(at, via),
1393
+ agents: agentShelf,
1394
+ skills: carry.skills,
1395
+ plugins: carry.plugins,
1396
+ research: { on: carry.gear.gear.research, set: (on) => carry.gear.set({ research: on }) },
1397
+ memory: { on: carry.gear.gear.memory, set: (on) => carry.gear.set({ memory: on }) },
1398
+ },
1399
+ // Who is in the room, then what the conversation carries, as chips after `+`.
1400
+ on: [
1401
+ ...room.map((name) => ({
1402
+ id: `agent:${name}`,
1403
+ label: name,
1404
+ icon: _jsx(Face, { src: faces[name]?.avatar, emoji: faces[name]?.emoji, name: name, size: 16 }),
1405
+ onRemove: () => seat(room.filter((one) => one !== name)),
1406
+ })),
1407
+ ...(anonymous ? [] : carry.on),
1408
+ ],
1409
+ slash: anonymous ? undefined : carry.skills,
1410
+ at: anonymous ? undefined : carry.plugins,
1411
+ mode: anonymous ? null : { value: carry.gear.gear.mode, set: (m) => carry.gear.set({ mode: m }) },
1412
+ // The model, the effort and the mode under the frame (@hanzo/build).
1413
+ mind,
1414
+ limits,
1415
+ onUpgrade: (id) => setAsk({ model: nameOf(id, listed) }),
1416
+ voice,
1417
+ language: spoken,
1418
+ onLanguage: speakIn,
1419
+ talk,
1420
+ // While nothing has been said: what is worth doing next.
1421
+ below: said.length === 0 && !anonymous ? _jsx(Suggestions, { rows: hints.rows, onPick: setDraft, onDismiss: hints.dismiss }) : undefined,
1422
+ };
1423
+ const mention = useMention(composing);
1351
1424
  return (_jsxs(YStack, { flex: 1, minH: 0, width: "100%", position: "relative", "data-slot": "chat-room",
1352
1425
  // A LINK IN AN ANSWER OPENS IN A TAB beside the conversation when the tab
1353
1426
  // can show it — a site or preview we host — and in a browser tab of its
@@ -1457,76 +1530,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1457
1530
  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] })),
1458
1531
  // The transcript reads at the same measure. `Thread` defaults to 768
1459
1532
  // itself; handing it the column here is what lets Width reach it.
1460
- column: { maxW: MEASURE }, composer: prompt({
1461
- value: draft,
1462
- onChange: setDraft,
1463
- onSend: submit,
1464
- onStop: chat.stop,
1465
- busy: chat.streaming,
1466
- // Files alone are a message: what is held goes with an empty draft too.
1467
- ready: Boolean(draft.trim() || files.length),
1468
- // `label` keeps the accessible name constant; the placeholder is the composer's own.
1469
- label: ASK,
1470
- note: caution(),
1471
- // A phone's pill is the tall one: the field across the top, the controls under it.
1472
- narrow: !media.sm,
1473
- // In the app's frame the composer floats on the pane as a pane of its own.
1474
- framed,
1475
- // A file or an image pasted into the field is held like a picked one;
1476
- // words paste as words.
1477
- field: {
1478
- onPaste: (e) => {
1479
- if (!e.clipboardData?.files?.length)
1480
- return;
1481
- e.preventDefault();
1482
- void hold(at, Array.from(e.clipboardData.files), via);
1483
- },
1484
- },
1485
- // WHAT GOES WITH THE MESSAGE, above the frame and at its width: the
1486
- // pause and its ways past, one chip a file, and talk mode's words.
1487
- head: files.length || turnedAway || prepWrong || preparing || talk.open || talk.refusal || pause ? (_jsxs(_Fragment, { children: [banner ? (_jsx(LimitedBanner, { message: banner.message, actions: banner.actions, onAction: act, onUsage: () => showSettings("usage") })) : null, actWrong ? (_jsx(Text, { role: "alert", "data-slot": "limited-banner-wrong", fontSize: "$2", color: BAD, children: actWrong })) : null, files.length ? (_jsx(XStack, { gap: "$1.5", flexWrap: "wrap", role: "list", "aria-label": "Attached files", "data-slot": "composer-attached", children: files.map((one) => (_jsx(HeldChip, { one: one, onOpen: openFile, onRemove: () => drop(at, one.id), onSave: anonymous ? undefined : (url, id) => keepOne(url, id, one.name, one.type) }, one.id))) })) : null, preparing ? (_jsxs(Text, { role: "status", "aria-live": "polite", "data-slot": "composer-preparing", fontSize: "$2", color: "$soft", children: [preparing, "\u2026"] })) : null, turnedAway || prepWrong ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: turnedAway || prepWrong })) : null, talk.open || talk.refusal ? (_jsx(Text, { role: "status", "aria-live": "polite", "data-slot": "composer-talk-reply", fontSize: "$2", color: talk.refusal ? BAD : "$soft", children: talk.refusal
1488
- ? worded(talk.refusal)
1489
- : talk.reply
1490
- ? `Hanzo: ${talk.reply}`
1491
- : talk.state === "listening"
1492
- ? "Listening — speak, and Hanzo answers aloud."
1493
- : "Connecting…" })) : null] })) : undefined,
1494
- // THE `+`: files (the Sources tab's "Attach files", one press closer),
1495
- // who is in the conversation, the skills, and the two switches.
1496
- plus: anonymous
1497
- ? { upload: () => take(at, via) }
1498
- : {
1499
- upload: () => take(at, via),
1500
- agents: agentShelf,
1501
- skills: carry.skills,
1502
- plugins: carry.plugins,
1503
- research: { on: carry.gear.gear.research, set: (on) => carry.gear.set({ research: on }) },
1504
- memory: { on: carry.gear.gear.memory, set: (on) => carry.gear.set({ memory: on }) },
1505
- },
1506
- // Who is in the room, then what the conversation carries, as chips after `+`.
1507
- on: [
1508
- ...room.map((name) => ({
1509
- id: `agent:${name}`,
1510
- label: name,
1511
- icon: _jsx(Face, { src: faces[name]?.avatar, emoji: faces[name]?.emoji, name: name, size: 16 }),
1512
- onRemove: () => seat(room.filter((one) => one !== name)),
1513
- })),
1514
- ...(anonymous ? [] : carry.on),
1515
- ],
1516
- slash: anonymous ? undefined : carry.skills,
1517
- at: anonymous ? undefined : carry.plugins,
1518
- mode: anonymous ? null : { value: carry.gear.gear.mode, set: (m) => carry.gear.set({ mode: m }) },
1519
- // The model, the effort and the mode under the frame (@hanzo/build).
1520
- mind,
1521
- limits,
1522
- onUpgrade: (id) => setAsk({ model: nameOf(id, listed) }),
1523
- voice,
1524
- language: spoken,
1525
- onLanguage: speakIn,
1526
- talk,
1527
- // While nothing has been said: what is worth doing next.
1528
- below: said.length === 0 && !anonymous ? _jsx(Suggestions, { rows: hints.rows, onPick: setDraft, onDismiss: hints.dismiss }) : undefined,
1529
- }) }), _jsx(AgentMaker, {}), carry.dialog, 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) })] }));
1533
+ column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog, 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) })] }));
1530
1534
  }
1531
1535
  /** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
1532
1536
  function Consent({ question, onAgree, onCancel, }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.79",
3
+ "version": "0.1.81",
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.66",
52
+ "@hanzo/build": ">=0.2.68",
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.66",
70
+ "@hanzo/build": "0.2.68",
71
71
  "@hanzo/composer": "0.2.0",
72
72
  "@hanzo/design": "0.5.34",
73
73
  "@hanzo/event": "0.3.56",