@hanzo/rooms 0.1.92 → 0.1.94
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.d.ts +3 -5
- package/dist/Chat.js +20 -78
- package/dist/lib/ai.d.ts +1 -1
- package/dist/lib/ai.js +2 -2
- package/package.json +1 -1
package/dist/Chat.d.ts
CHANGED
|
@@ -2,10 +2,9 @@ export declare function Chat(): import("react").JSX.Element;
|
|
|
2
2
|
/**
|
|
3
3
|
* The same conversation, embedded in a page that is not /chat.
|
|
4
4
|
*
|
|
5
|
-
* R8: there were TWO live chat surfaces
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* drift, so /models mounts this and the second surface is gone. It carries no
|
|
5
|
+
* R8: there were TWO live chat surfaces, so /models mounts this one and the
|
|
6
|
+
* second is gone. The data-share choice is made once at onboarding; this room
|
|
7
|
+
* does not ask again when a message is sent. It carries no
|
|
9
8
|
* heading of its own: the host page already has one, and a second `<h1>` is the
|
|
10
9
|
* defect that took the landings off /chat and /dev.
|
|
11
10
|
*
|
|
@@ -32,4 +31,3 @@ export declare function refusalOf(error: Error): {
|
|
|
32
31
|
visitor: boolean;
|
|
33
32
|
provider: boolean;
|
|
34
33
|
};
|
|
35
|
-
/** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
|
package/dist/Chat.js
CHANGED
|
@@ -17,10 +17,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
17
17
|
// the free pool, so what changes at sign-in is the menu, not the room.
|
|
18
18
|
//
|
|
19
19
|
// The FREE lane is about PRICE, not identity: a free turn spends no balance and
|
|
20
|
-
// is data-shared in exchange
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
20
|
+
// is data-shared in exchange. That choice is made once, at onboarding, where
|
|
21
|
+
// the terms are linked. Nothing under the composer repeats it. Signing in buys
|
|
22
|
+
// the paid models and a history that follows you; it is never the price of
|
|
23
|
+
// asking a question.
|
|
24
24
|
//
|
|
25
25
|
// This file used to branch on `isAuthenticated` and hand a visitor a composer
|
|
26
26
|
// whose only action was a trip to hanzo.id, captioned "Sign in to send". The
|
|
@@ -31,7 +31,6 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "r
|
|
|
31
31
|
import { billed, detail, familyOf, planRequired, plainly, refused } from "./failure.js";
|
|
32
32
|
import { Prose } from "./Prose.js";
|
|
33
33
|
import { useAgent, useAi, useChat, } from "@hanzo/ai/react";
|
|
34
|
-
import { freeCopy, grantConsent, hasConsent, isFree, } from "@hanzo/ai";
|
|
35
34
|
import { ASK, Chat as ChatSurface, Failure, Parts, words } from "@hanzo/ui/chat";
|
|
36
35
|
import { carriedIn, carrying, compose, partsOf, said as told, weigh } from "./lib/attach.js";
|
|
37
36
|
import { download, dropped, fileOf, pct, readable, refOf, retrieve } from "./lib/files.js";
|
|
@@ -69,10 +68,8 @@ import { checkoutUrl } from './lib/pay.js';
|
|
|
69
68
|
import { Upgrade, useFree } from './Upgrade.js';
|
|
70
69
|
import { enter, LOGIN } from './lib/destination.js';
|
|
71
70
|
import { first } from './lib/first.js';
|
|
72
|
-
import { BAD
|
|
73
|
-
import { brand
|
|
74
|
-
/** The veil under the free lane's consent sheet. */
|
|
75
|
-
const SCRIM = mix("var(--pure-black)", 72, "srgb");
|
|
71
|
+
import { BAD } from "./lib/mix.js";
|
|
72
|
+
import { brand } from './where.js';
|
|
76
73
|
// NO REASONING PANEL HERE. Every assistant turn opened a "Thinking" disclosure
|
|
77
74
|
// listing seven steps — "Planning CSS extraction for chat styles", "Designing
|
|
78
75
|
// responsive top bar grid layout" — typed into this file as a literal array.
|
|
@@ -89,24 +86,6 @@ const SCRIM = mix("var(--pure-black)", 72, "srgb");
|
|
|
89
86
|
* room has always read at, so an untouched setting changes nothing.
|
|
90
87
|
*/
|
|
91
88
|
const MEASURE = "var(--container-prose, 48rem)";
|
|
92
|
-
/**
|
|
93
|
-
* The store, or null when the browser will not give us one.
|
|
94
|
-
*
|
|
95
|
-
* Reading `window.localStorage` THROWS `SecurityError` where site data is
|
|
96
|
-
* blocked — third-party contexts, Safari's Block All Cookies, a hardened
|
|
97
|
-
* profile — and this is called from inside a send handler, so an unguarded
|
|
98
|
-
* access takes the send down rather than the storage.
|
|
99
|
-
*/
|
|
100
|
-
const store = () => {
|
|
101
|
-
if (typeof window === "undefined")
|
|
102
|
-
return null;
|
|
103
|
-
try {
|
|
104
|
-
return window.localStorage;
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
89
|
/**
|
|
111
90
|
* The text this screen arrived with, TYPED INTO THE COMPOSER — never sent.
|
|
112
91
|
*
|
|
@@ -182,8 +161,8 @@ export function Chat() {
|
|
|
182
161
|
// mounted under the provider the shell already put above it.
|
|
183
162
|
//
|
|
184
163
|
// ONE room, for everyone. `anonymous` changes which models are on offer and
|
|
185
|
-
// nothing else — the composer
|
|
186
|
-
//
|
|
164
|
+
// nothing else — the composer and the transcript are the same components
|
|
165
|
+
// either way, because a visitor is having the same conversation.
|
|
187
166
|
//
|
|
188
167
|
// The conversations are NOT drawn here. `Threads` is the list and the shell
|
|
189
168
|
// owns the column it sits in — one sidebar for six rooms, already built, and a
|
|
@@ -194,10 +173,9 @@ export function Chat() {
|
|
|
194
173
|
/**
|
|
195
174
|
* The same conversation, embedded in a page that is not /chat.
|
|
196
175
|
*
|
|
197
|
-
* R8: there were TWO live chat surfaces
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* drift, so /models mounts this and the second surface is gone. It carries no
|
|
176
|
+
* R8: there were TWO live chat surfaces, so /models mounts this one and the
|
|
177
|
+
* second is gone. The data-share choice is made once at onboarding; this room
|
|
178
|
+
* does not ask again when a message is sent. It carries no
|
|
201
179
|
* heading of its own: the host page already has one, and a second `<h1>` is the
|
|
202
180
|
* defect that took the landings off /chat and /dev.
|
|
203
181
|
*
|
|
@@ -476,9 +454,9 @@ function Plan({ onPress }) {
|
|
|
476
454
|
*
|
|
477
455
|
* `anonymous` opens on the free pool and offers only what the free pool can
|
|
478
456
|
* answer, because listing a paid model to someone with no balance is an offer
|
|
479
|
-
* that fails at send. Everything else — the transcript, the composer
|
|
480
|
-
*
|
|
481
|
-
*
|
|
457
|
+
* that fails at send. Everything else — the transcript, the composer — is
|
|
458
|
+
* identical, which is the point: signing in later changes the models on the
|
|
459
|
+
* menu, not the room.
|
|
482
460
|
*/
|
|
483
461
|
function Thread({ placeholder = message(), initial, anonymous = false, heading, }) {
|
|
484
462
|
/**
|
|
@@ -487,16 +465,13 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
487
465
|
* `useState(anonymous ? FREE : ENSO)` read the flag ONCE, on the first render
|
|
488
466
|
* — which is the render before hydration settles, where `anonymous` is always
|
|
489
467
|
* false. So every visitor opened on ENSO while the composer beside them said
|
|
490
|
-
* "Enso Free"
|
|
491
|
-
*
|
|
492
|
-
* block never asked. A visitor was sending on the paid pool having agreed to
|
|
493
|
-
* nothing, because a state initialiser cannot see a value that arrives later.
|
|
468
|
+
* "Enso Free". A visitor was sending on the paid pool, because a state
|
|
469
|
+
* initialiser cannot see a value that arrives later.
|
|
494
470
|
*
|
|
495
471
|
* A CHOICE, or the identity. `null` is "nobody has chosen", which is a
|
|
496
472
|
* different fact from either model and the only one that can defer to a flag
|
|
497
473
|
* that settles after mount. Picking from the menu still wins, permanently.
|
|
498
474
|
*/
|
|
499
|
-
const [asking, setAsking] = useState(null);
|
|
500
475
|
// THE MODEL AND THE EFFORT are the one choice Chat and Dev share
|
|
501
476
|
// (@hanzo/build `useMind`): the catalog from `GET /v1/models` and what the
|
|
502
477
|
// reader kept, changed from the composer's chip or from Settings.
|
|
@@ -707,8 +682,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
707
682
|
// EVERY MODEL IS CHOSEN, never asked for: one the plan does not cover still
|
|
708
683
|
// picks, and the gateway answers it with the policy's refusal or from Enso,
|
|
709
684
|
// which the room then shows. A turn the gateway refuses `plan_required`
|
|
710
|
-
// opens the upgrade, titled with the model.
|
|
711
|
-
// free-lane notice.
|
|
685
|
+
// opens the upgrade, titled with the model.
|
|
712
686
|
const free = useFree(!anonymous) && !held;
|
|
713
687
|
const [ask, setAsk] = useState(null);
|
|
714
688
|
// KEEPING THE CONVERSATION is the reader's account, not this pane's choice:
|
|
@@ -1138,17 +1112,8 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1138
1112
|
* no longer allowed to draw. `:focus-within` would say this in CSS; the
|
|
1139
1113
|
* stylesheet that would hold it cannot reach past gui's own rule, so the
|
|
1140
1114
|
* state is read where the event is. */
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
// FAILS CLOSED. This read `s && !hasConsent(s)`, so a browser that gives
|
|
1144
|
-
// us no store answered "no consent needed" and sent a data-shared turn
|
|
1145
|
-
// having asked nobody. No store means no record, and no record means ask.
|
|
1146
|
-
if (isFree(model) && (!s || !hasConsent(s))) {
|
|
1147
|
-
setAsking(text);
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
speak(text);
|
|
1151
|
-
}, [model, speak]);
|
|
1115
|
+
// A free send goes. The data-share choice was made at onboarding, once.
|
|
1116
|
+
const guarded = speak;
|
|
1152
1117
|
/**
|
|
1153
1118
|
* The draft and the files, sent as one turn.
|
|
1154
1119
|
*
|
|
@@ -1317,25 +1282,6 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1317
1282
|
enter(LOGIN);
|
|
1318
1283
|
}, children: "Sign in" })) : lift ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setAsk({ ...lift, model: nameOf(model, listed) }), children: "Upgrade" })) : how?.visitor ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => enter(), children: "Sign in to keep going" })) : (_jsx(Anchor, { href: checkoutUrl(), children: _jsx(Button, { size: "sm", variant: "outline", children: "Add credit" }) })) })) : null] }) }));
|
|
1319
1284
|
};
|
|
1320
|
-
// The agreement is asked OVER the room, not instead of it. Returning the
|
|
1321
|
-
// consent on its own replaced the whole pane: the question the reader had
|
|
1322
|
-
// just typed left the screen, and the first thing a stranger saw of the
|
|
1323
|
-
// product was a page about training data. The room stays; the sheet sits on
|
|
1324
|
-
// top of it, and the question is still there behind it.
|
|
1325
|
-
const consent = asking === null ? null : (_jsx(Consent, { question: told(asking).text, onAgree: () => {
|
|
1326
|
-
const s = store();
|
|
1327
|
-
// Nothing to persist into is not a reason to refuse the turn the
|
|
1328
|
-
// reader just agreed to; it means the question is asked again next
|
|
1329
|
-
// time, which is the honest cost of a browser that stores nothing.
|
|
1330
|
-
if (s)
|
|
1331
|
-
grantConsent(s);
|
|
1332
|
-
const held = asking;
|
|
1333
|
-
setAsking(null);
|
|
1334
|
-
if (held)
|
|
1335
|
-
speak(held);
|
|
1336
|
-
}, onCancel: () => {
|
|
1337
|
-
setAsking(null);
|
|
1338
|
-
} }));
|
|
1339
1285
|
// Dev, from the Summary's Create file or site: a move inside the app where the
|
|
1340
1286
|
// host's app reads the path (hanzo.ai), else Dev on hanzo.ai, as the shell's
|
|
1341
1287
|
// Dev place goes from a host without it (hanzo.team).
|
|
@@ -1573,9 +1519,5 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1573
1519
|
empty: heading === null ? undefined : (_jsxs(YStack, { "data-slot": "opening", items: "center", width: "100%", pt: "$6", pb: "$2", ...(media.sm ? null : { flex: 1, justify: "flex-end" }), 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 || (firstName ? `What’s up, ${firstName}?` : "What’s up?") }), 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] })),
|
|
1574
1520
|
// The transcript reads at the same measure. `Thread` defaults to 768
|
|
1575
1521
|
// itself; handing it the column here is what lets Width reach it.
|
|
1576
|
-
column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog, _jsx(
|
|
1577
|
-
}
|
|
1578
|
-
/** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
|
|
1579
|
-
function Consent({ question, onAgree, onCancel, }) {
|
|
1580
|
-
return (_jsx(YStack, { position: "absolute", t: 0, l: 0, r: 0, b: 0, items: "center", justify: "center", p: "$4", z: 40, bg: SCRIM, children: _jsxs(YStack, { width: "100%", maxW: 512, px: "$5", py: "$5", gap: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$background", children: [_jsx(Text, { render: "h2", fontSize: "$6", fontWeight: "500", color: "$ink", children: freeCopy.consentTitle }), question ? (_jsxs(Text, { fontSize: "$3", color: "$soft", numberOfLines: 2, children: ["Your question is waiting: \u201C", question, "\u201D"] })) : null, _jsx(Text, { fontSize: "$4", color: "$soft", children: freeCopy.consentBody }), _jsx(YStack, { gap: "$2", children: freeCopy.consentPoints.map((point) => (_jsx(Text, { fontSize: "$3", color: "$soft", children: point }, point))) }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", children: [_jsx(Button, { onClick: onAgree, children: freeCopy.consentAgreeCta }), _jsx(Button, { variant: "outline", onClick: onCancel, children: freeCopy.consentCancelCta })] }), _jsxs(Text, { fontSize: "$2", color: "$soft", children: [freeCopy.termsText, " ", _jsx(Anchor, { href: site("/legal/terms"), children: "Terms" }), " · ", _jsx(Anchor, { href: site("/legal/privacy"), children: "Privacy" })] })] }) }));
|
|
1522
|
+
column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog, _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
|
|
1581
1523
|
}
|
package/dist/lib/ai.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export declare const base: () => {
|
|
|
40
40
|
export interface Ai {
|
|
41
41
|
client: AiClient | null;
|
|
42
42
|
model: string;
|
|
43
|
-
/** Answering
|
|
43
|
+
/** Answering on the free pool. The data-share choice was made at onboarding. */
|
|
44
44
|
free: boolean;
|
|
45
45
|
/** Mounted client-side. The static export always prerenders the signed-out
|
|
46
46
|
* page, so nothing interactive may render until this is true. */
|
package/dist/lib/ai.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// their account like every other turn they run.
|
|
6
6
|
//
|
|
7
7
|
// Signed out — the FREE POOL, anonymously. There is no account to bill and none
|
|
8
|
-
// is needed: a free turn spends no balance
|
|
9
|
-
//
|
|
8
|
+
// is needed: a free turn spends no balance. The data-share choice is made
|
|
9
|
+
// once, at onboarding. A widget key, if
|
|
10
10
|
// one is configured, rides along so the turn attributes to the org that minted
|
|
11
11
|
// it; without one the turn is simply anonymous. Either way the client EXISTS,
|
|
12
12
|
// because a chat you cannot send is not a chat.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.94",
|
|
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",
|