@hanzo/rooms 0.1.78 → 0.1.80
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 +84 -73
- 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';
|
|
@@ -757,8 +757,14 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
757
757
|
if (kept && kept !== open) {
|
|
758
758
|
written.current.add(kept);
|
|
759
759
|
reopen(kept);
|
|
760
|
+
// THE SERVER TITLES A THREAD FROM ITS FIRST TURN, block and all: a turn
|
|
761
|
+
// that carried files or a plugin's words is renamed to the words alone.
|
|
762
|
+
const first = chat.messages.find((m) => m.role === "user");
|
|
763
|
+
const read = first ? told(words(first.content)) : null;
|
|
764
|
+
if (read?.carried)
|
|
765
|
+
void ai.threads.update(kept, { title: read.text.trim().slice(0, 80) || "Files" }).catch(() => { });
|
|
760
766
|
}
|
|
761
|
-
}, [kept, open, reopen]);
|
|
767
|
+
}, [kept, open, reopen]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
762
768
|
// The thread's turns, replaced wholesale or through an updater. `set` is the
|
|
763
769
|
// hook's own state setter, so it takes either.
|
|
764
770
|
const apply = useRef(set);
|
|
@@ -1181,7 +1187,8 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1181
1187
|
// The files held for this message, as bytes, for a plugin that reads them (PDF).
|
|
1182
1188
|
const bytes = await Promise.all(files.filter((h) => h.href).map(async (h) => new File([await (await fetch(h.href)).blob()], h.name, { type: h.type })));
|
|
1183
1189
|
const got = await carry.prep({ text, files: bytes, thread: open ?? "" }, new AbortController().signal);
|
|
1184
|
-
|
|
1190
|
+
// An answer of a plugin's own (a deck) is the answer, whatever else was prepared.
|
|
1191
|
+
if (got.reply)
|
|
1185
1192
|
return answered(text, got.reply);
|
|
1186
1193
|
deliver(text, got.system ?? "");
|
|
1187
1194
|
}
|
|
@@ -1341,6 +1348,79 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1341
1348
|
window.location.assign(dev(slug));
|
|
1342
1349
|
},
|
|
1343
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);
|
|
1344
1424
|
return (_jsxs(YStack, { flex: 1, minH: 0, width: "100%", position: "relative", "data-slot": "chat-room",
|
|
1345
1425
|
// A LINK IN AN ANSWER OPENS IN A TAB beside the conversation when the tab
|
|
1346
1426
|
// can show it — a site or preview we host — and in a browser tab of its
|
|
@@ -1450,76 +1530,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1450
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] })),
|
|
1451
1531
|
// The transcript reads at the same measure. `Thread` defaults to 768
|
|
1452
1532
|
// itself; handing it the column here is what lets Width reach it.
|
|
1453
|
-
column: { maxW: MEASURE }, composer: prompt({
|
|
1454
|
-
value: draft,
|
|
1455
|
-
onChange: setDraft,
|
|
1456
|
-
onSend: submit,
|
|
1457
|
-
onStop: chat.stop,
|
|
1458
|
-
busy: chat.streaming,
|
|
1459
|
-
// Files alone are a message: what is held goes with an empty draft too.
|
|
1460
|
-
ready: Boolean(draft.trim() || files.length),
|
|
1461
|
-
// `label` keeps the accessible name constant; the placeholder is the composer's own.
|
|
1462
|
-
label: ASK,
|
|
1463
|
-
note: caution(),
|
|
1464
|
-
// A phone's pill is the tall one: the field across the top, the controls under it.
|
|
1465
|
-
narrow: !media.sm,
|
|
1466
|
-
// In the app's frame the composer floats on the pane as a pane of its own.
|
|
1467
|
-
framed,
|
|
1468
|
-
// A file or an image pasted into the field is held like a picked one;
|
|
1469
|
-
// words paste as words.
|
|
1470
|
-
field: {
|
|
1471
|
-
onPaste: (e) => {
|
|
1472
|
-
if (!e.clipboardData?.files?.length)
|
|
1473
|
-
return;
|
|
1474
|
-
e.preventDefault();
|
|
1475
|
-
void hold(at, Array.from(e.clipboardData.files), via);
|
|
1476
|
-
},
|
|
1477
|
-
},
|
|
1478
|
-
// WHAT GOES WITH THE MESSAGE, above the frame and at its width: the
|
|
1479
|
-
// pause and its ways past, one chip a file, and talk mode's words.
|
|
1480
|
-
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
|
|
1481
|
-
? worded(talk.refusal)
|
|
1482
|
-
: talk.reply
|
|
1483
|
-
? `Hanzo: ${talk.reply}`
|
|
1484
|
-
: talk.state === "listening"
|
|
1485
|
-
? "Listening — speak, and Hanzo answers aloud."
|
|
1486
|
-
: "Connecting…" })) : null] })) : undefined,
|
|
1487
|
-
// THE `+`: files (the Sources tab's "Attach files", one press closer),
|
|
1488
|
-
// who is in the conversation, the skills, and the two switches.
|
|
1489
|
-
plus: anonymous
|
|
1490
|
-
? { upload: () => take(at, via) }
|
|
1491
|
-
: {
|
|
1492
|
-
upload: () => take(at, via),
|
|
1493
|
-
agents: agentShelf,
|
|
1494
|
-
skills: carry.skills,
|
|
1495
|
-
plugins: carry.plugins,
|
|
1496
|
-
research: { on: carry.gear.gear.research, set: (on) => carry.gear.set({ research: on }) },
|
|
1497
|
-
memory: { on: carry.gear.gear.memory, set: (on) => carry.gear.set({ memory: on }) },
|
|
1498
|
-
},
|
|
1499
|
-
// Who is in the room, then what the conversation carries, as chips after `+`.
|
|
1500
|
-
on: [
|
|
1501
|
-
...room.map((name) => ({
|
|
1502
|
-
id: `agent:${name}`,
|
|
1503
|
-
label: name,
|
|
1504
|
-
icon: _jsx(Face, { src: faces[name]?.avatar, emoji: faces[name]?.emoji, name: name, size: 16 }),
|
|
1505
|
-
onRemove: () => seat(room.filter((one) => one !== name)),
|
|
1506
|
-
})),
|
|
1507
|
-
...(anonymous ? [] : carry.on),
|
|
1508
|
-
],
|
|
1509
|
-
slash: anonymous ? undefined : carry.skills,
|
|
1510
|
-
at: anonymous ? undefined : carry.plugins,
|
|
1511
|
-
mode: anonymous ? null : { value: carry.gear.gear.mode, set: (m) => carry.gear.set({ mode: m }) },
|
|
1512
|
-
// The model, the effort and the mode under the frame (@hanzo/build).
|
|
1513
|
-
mind,
|
|
1514
|
-
limits,
|
|
1515
|
-
onUpgrade: (id) => setAsk({ model: nameOf(id, listed) }),
|
|
1516
|
-
voice,
|
|
1517
|
-
language: spoken,
|
|
1518
|
-
onLanguage: speakIn,
|
|
1519
|
-
talk,
|
|
1520
|
-
// While nothing has been said: what is worth doing next.
|
|
1521
|
-
below: said.length === 0 && !anonymous ? _jsx(Suggestions, { rows: hints.rows, onPick: setDraft, onDismiss: hints.dismiss }) : undefined,
|
|
1522
|
-
}) }), _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) })] }));
|
|
1523
1534
|
}
|
|
1524
1535
|
/** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
|
|
1525
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.80",
|
|
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.67",
|
|
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.67",
|
|
71
71
|
"@hanzo/composer": "0.2.0",
|
|
72
72
|
"@hanzo/design": "0.5.34",
|
|
73
73
|
"@hanzo/event": "0.3.56",
|