@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.
- package/dist/Chat.js +75 -71
- 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.
|
|
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.
|
|
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.
|
|
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",
|