@hanzo/rooms 0.1.9 → 0.1.11
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/assets/agents/antje.jpg +0 -0
- package/assets/agents/creative.png +0 -0
- package/assets/agents/des.png +0 -0
- package/assets/agents/dev.png +0 -0
- package/assets/agents/einstein.png +0 -0
- package/assets/agents/feynman.png +0 -0
- package/assets/agents/leo.png +0 -0
- package/assets/agents/maya.png +0 -0
- package/assets/agents/nora.png +0 -0
- package/assets/agents/vi.png +0 -0
- package/assets/agents/zach.jpg +0 -0
- package/assets/bots/chief.png +0 -0
- package/assets/bots/inbox-manager.png +0 -0
- package/assets/bots/sales-outbound.png +0 -0
- package/dist/Chat.js +37 -23
- package/dist/Column.js +3 -2
- package/dist/Directory.js +7 -4
- package/dist/Guide.js +2 -1
- package/dist/Market.js +2 -1
- package/dist/Me.js +4 -3
- package/dist/Meet.js +26 -7
- package/dist/ModeMark.d.ts +1 -1
- package/dist/ModeMark.js +3 -2
- package/dist/Orgs.js +2 -2
- package/dist/Setup.js +2 -2
- package/dist/Shared.js +3 -2
- package/dist/Shell.js +6 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/integrations.d.ts +17 -3
- package/dist/lib/integrations.js +1389 -1
- package/dist/lib/terms.d.ts +19 -0
- package/dist/lib/terms.js +16 -0
- package/dist/look.js +2 -1
- package/dist/tours.js +9 -1
- package/dist/where.d.ts +8 -8
- package/dist/where.js +2 -0
- package/package.json +6 -4
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/Chat.js
CHANGED
|
@@ -48,8 +48,8 @@ import { Meters } from "./meters.js";
|
|
|
48
48
|
import { useHydrated } from "./lib/hydrated.js";
|
|
49
49
|
import { base, served } from "./lib/ai.js";
|
|
50
50
|
import { onServed } from "./lib/served.js";
|
|
51
|
-
import { speech, useVoice, Voice } from "@hanzo/voice";
|
|
52
|
-
import { ArrowUp, Image as ImageMark, Mic, Paperclip, Square, Star, PanelRight, X } from "lucide-react";
|
|
51
|
+
import { REFUSED, speech, useDictation, useTalk, useVoice, Voice } from "@hanzo/voice";
|
|
52
|
+
import { ArrowUp, AudioLines, Image as ImageMark, Mic, Paperclip, Square, Star, PanelRight, X } from "lucide-react";
|
|
53
53
|
import { ENSO, FREE } from "./lib/ai.js";
|
|
54
54
|
import { openThread, useOpen } from "./open.js";
|
|
55
55
|
import { Beside, Framed } from "./Shell.js";
|
|
@@ -66,7 +66,7 @@ import { Upgrade, useFree } from './Upgrade.js';
|
|
|
66
66
|
import { enter, LOGIN } from './lib/destination.js';
|
|
67
67
|
import { first } from './lib/first.js';
|
|
68
68
|
import { BAD, mix } from "./lib/mix.js";
|
|
69
|
-
import { site } from './where.js';
|
|
69
|
+
import { brand, site } from './where.js';
|
|
70
70
|
/** The veil under the free lane's consent sheet. */
|
|
71
71
|
const SCRIM = mix("var(--pure-black)", 72, "srgb");
|
|
72
72
|
// NO REASONING PANEL HERE. Every assistant turn opened a "Thinking" disclosure
|
|
@@ -237,12 +237,12 @@ export function Ask({ placeholder, idle, }) {
|
|
|
237
237
|
*
|
|
238
238
|
* The package defaults to "Ask anything" and argues for it against
|
|
239
239
|
* "Message <model>" — a name that moves renames the control every time the
|
|
240
|
-
* model does. This is not that: it is the PRODUCT's name
|
|
241
|
-
* and it is already how this file addresses a
|
|
242
|
-
* ${persona.name}`). `label` stays "Ask anything", so the
|
|
243
|
-
* not move when the placeholder does.
|
|
240
|
+
* model does. This is not that: it is the PRODUCT's name — the host's brand,
|
|
241
|
+
* Hanzo AI or Hanzo Team — and it is already how this file addresses a
|
|
242
|
+
* character (`Message ${persona.name}`). `label` stays "Ask anything", so the
|
|
243
|
+
* accessible name does not move when the placeholder does.
|
|
244
244
|
*/
|
|
245
|
-
const
|
|
245
|
+
const message = () => `Message ${brand()}`;
|
|
246
246
|
/** The id the room's system turn is held under in the thread. */
|
|
247
247
|
const ROOM = "room";
|
|
248
248
|
/** The models that route a turn rather than name the one that answers it. */
|
|
@@ -305,7 +305,7 @@ const STALL = 300_000;
|
|
|
305
305
|
* consent — is identical, which is the point: signing in later changes the
|
|
306
306
|
* models on the menu, not the room.
|
|
307
307
|
*/
|
|
308
|
-
function Thread({ placeholder =
|
|
308
|
+
function Thread({ placeholder = message(), initial, anonymous = false, heading, }) {
|
|
309
309
|
/**
|
|
310
310
|
* The model, and WHO decides it.
|
|
311
311
|
*
|
|
@@ -617,18 +617,21 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
|
|
|
617
617
|
const depth = useRef(0);
|
|
618
618
|
useSeed(initial, setDraft);
|
|
619
619
|
// DICTATION through Hanzo's own ear: the mic records and each pause is
|
|
620
|
-
// posted to /v1/audio/transcriptions (`zen-scribe`)
|
|
621
|
-
// the
|
|
622
|
-
//
|
|
623
|
-
// the
|
|
624
|
-
//
|
|
620
|
+
// posted to /v1/audio/transcriptions (`zen-scribe`), and what was said lands
|
|
621
|
+
// AFTER what the draft already held (`useDictation`) — a pause between two
|
|
622
|
+
// sentences never loses the first. `speech()` defaults to the Hanzo gateway
|
|
623
|
+
// and `base()` is the one place a host is read from, so pointing a local
|
|
624
|
+
// cloud at one points both. The token is the reader's own — the package never
|
|
625
|
+
// invents a credential — and a refusal is worn by `<Voice/>`, never passed off
|
|
626
|
+
// as a transcript.
|
|
625
627
|
const { token } = useIamToken();
|
|
626
628
|
const ear = useMemo(() => speech({ ...base(), ...(token ? { token } : {}) }), [token]);
|
|
627
|
-
const voice = useVoice({
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
629
|
+
const voice = useVoice({ speech: ear, ...useDictation(draft, setDraft) });
|
|
630
|
+
// TALK MODE: a hands-free conversation on Hanzo's realtime socket, /v1/voice.
|
|
631
|
+
// The mic streams up, the answer streams back as speech with its words in
|
|
632
|
+
// `talk.reply`, and talking over it stops it. Its own conversation, so its
|
|
633
|
+
// turns are drawn above the composer rather than posted into the thread.
|
|
634
|
+
const talk = useTalk({ ...base(), ...(token ? { token } : {}), org: org() ?? undefined });
|
|
632
635
|
// WHILE THE MIC IS OPEN, A FINISHED REPLY IS READ ALOUD by the platform's
|
|
633
636
|
// voice, through the conversation's own mouth so speaking over it stops it.
|
|
634
637
|
// A room's reply is read part by part, each in its speaker's cast voice.
|
|
@@ -639,11 +642,12 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
|
|
|
639
642
|
const lastMsg = msgs[msgs.length - 1];
|
|
640
643
|
if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content === "string") {
|
|
641
644
|
const parts = room.length > 1 ? speakers(lastMsg.content, room) : [{ who: withWhom, text: lastMsg.content }];
|
|
645
|
+
// A part spoken over ends the reading rather than handing on to the next.
|
|
642
646
|
void (async () => {
|
|
643
647
|
for (const part of parts) {
|
|
644
648
|
const said = cleanForSpeech(part.text);
|
|
645
|
-
if (said)
|
|
646
|
-
|
|
649
|
+
if (said && !(await voice.say(said, voiceOf(part.who ?? withWhom ?? undefined))))
|
|
650
|
+
break;
|
|
647
651
|
}
|
|
648
652
|
})();
|
|
649
653
|
}
|
|
@@ -1047,8 +1051,18 @@ function Thread({ placeholder = MESSAGE, initial, anonymous = false, heading, })
|
|
|
1047
1051
|
},
|
|
1048
1052
|
// WHAT GOES WITH THE MESSAGE, above the frame: one chip a file, each
|
|
1049
1053
|
// with its own remove, and the reason any file was refused.
|
|
1050
|
-
head: files.length || turnedAway ? (_jsxs(YStack, { width: "calc(100% - 32px)", maxW: `calc(${MEASURE} - 3rem)`, mx: "auto", gap: "$1.5", children: [files.length ? (_jsx(XStack, { gap: "$1.5", flexWrap: "wrap", role: "list", "aria-label": "Attached files", "data-slot": "composer-attached", children: files.map((one) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$2.5", pr: "$1", py: "$1", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", bg: "$hover", maxW: 260, children: [one.kind === "image" ? (_jsx(ImageMark, { size: 12, "aria-hidden": true, color: "var(--muted-foreground)" })) : (_jsx(Paperclip, { size: 12, "aria-hidden": true, color: "var(--muted-foreground)" })), _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: one.name }), _jsx(Box, { render: "button", "aria-label": `Remove ${one.name}`, onClick: () => drop(at, one.id), p: "$1", rounded: "$10", hoverStyle: { bg: "$raised" }, children: _jsx(X, { size: 12, "aria-hidden": true }) })] }, one.id))) })) : null, turnedAway ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: turnedAway })) : null
|
|
1051
|
-
|
|
1054
|
+
head: files.length || turnedAway || talk.open || talk.refusal ? (_jsxs(YStack, { width: "calc(100% - 32px)", maxW: `calc(${MEASURE} - 3rem)`, mx: "auto", gap: "$1.5", children: [files.length ? (_jsx(XStack, { gap: "$1.5", flexWrap: "wrap", role: "list", "aria-label": "Attached files", "data-slot": "composer-attached", children: files.map((one) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$2.5", pr: "$1", py: "$1", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", bg: "$hover", maxW: 260, children: [one.kind === "image" ? (_jsx(ImageMark, { size: 12, "aria-hidden": true, color: "var(--muted-foreground)" })) : (_jsx(Paperclip, { size: 12, "aria-hidden": true, color: "var(--muted-foreground)" })), _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, children: one.name }), _jsx(Box, { render: "button", "aria-label": `Remove ${one.name}`, onClick: () => drop(at, one.id), p: "$1", rounded: "$10", hoverStyle: { bg: "$raised" }, children: _jsx(X, { size: 12, "aria-hidden": true }) })] }, one.id))) })) : null, turnedAway ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: turnedAway })) : 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
|
|
1055
|
+
? REFUSED.lost
|
|
1056
|
+
: talk.reply
|
|
1057
|
+
? `Hanzo: ${talk.reply}`
|
|
1058
|
+
: talk.state === "listening"
|
|
1059
|
+
? "Listening — speak, and Hanzo answers aloud."
|
|
1060
|
+
: "Connecting…" })) : null] })) : undefined,
|
|
1061
|
+
send: (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Enso, { effort: effort, onEffort: setEffort, model: wanted, onModel: choose, models: offered }), _jsx(Control, { asChild: true, size: ROUND, children: _jsx(Voice, { voice: voice, says: { idle: "Dictate", listening: "Dictating — click to stop" }, children: (state) => (_jsx(Mic, { size: 15, fill: state === "idle" ? "none" : "currentColor" })) }) }), _jsx(Control, { asChild: true, size: ROUND, children: _jsx(Voice, { voice: talk, "data-slot": "composer-talk", says: {
|
|
1062
|
+
idle: "Talk with Hanzo",
|
|
1063
|
+
listening: "In conversation — click to hang up",
|
|
1064
|
+
speaking: "Hanzo is speaking — talk to interrupt",
|
|
1065
|
+
}, children: () => _jsx(AudioLines, { size: 15 }) }) }), _jsx(Control, { type: "button", size: ROUND, fill: true, "data-slot": "composer-send", "aria-label": chat.streaming ? "Stop" : "Send", opacity: chat.streaming || draft.trim() || files.length ? 1 : 0.4, onClick: () => (chat.streaming ? chat.stop() : submit()), children: chat.streaming ? (_jsx(Square, { size: 13, fill: "currentColor" })) : (_jsx(ArrowUp, { size: 15, strokeWidth: 2.5 })) })] })),
|
|
1052
1066
|
// WHAT THE PLAN HAS LEFT, under the frame and at its measure, for a
|
|
1053
1067
|
// plan holder only: Free keeps the free lane's own notice.
|
|
1054
1068
|
foot: held && limits && (limits.session || limits.day || limits.month) ? (_jsx(YStack, { width: "calc(100% - 32px)", maxW: `calc(${MEASURE} - 3rem)`, mx: "auto", children: _jsx(Meters, { limits: limits }) })) : undefined,
|
package/dist/Column.js
CHANGED
|
@@ -25,6 +25,7 @@ import { useSpan } from './span.js';
|
|
|
25
25
|
import { pane } from './ground.js';
|
|
26
26
|
import { pick } from './lib/session.js';
|
|
27
27
|
import { app, talk } from './lib/host.js';
|
|
28
|
+
import { brand } from './where.js';
|
|
28
29
|
/** The grip stands in the gutter between the column and the pane. gui carries a `calc()` to the page unchanged; its
|
|
29
30
|
* types only name custom properties. */
|
|
30
31
|
const GUTTER_END = 'calc(-1 * var(--pane-gap))';
|
|
@@ -74,10 +75,10 @@ export function Column({ host, mode, thread, onChat, onDev, go, drawer = false,
|
|
|
74
75
|
window.location.assign(mode === 'dev' ? app('') : talk());
|
|
75
76
|
};
|
|
76
77
|
if (collapsed)
|
|
77
|
-
return (_jsx(YStack, { "data-slot": "app-column", "data-collapsed": "", position: "relative", shrink: 0, width: RAIL, "$max-md": { display: 'none' }, children: _jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "true", role: "navigation", "aria-label":
|
|
78
|
+
return (_jsx(YStack, { "data-slot": "app-column", "data-collapsed": "", position: "relative", shrink: 0, width: RAIL, "$max-md": { display: 'none' }, children: _jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "true", role: "navigation", "aria-label": brand(), ...pane(), ...FILL, px: "$1.5", py: "$2.5", gap: "$1.5", items: "center", children: [_jsx(SidebarIconButton, { label: "Expand sidebar", onPress: () => setCollapsed(false), width: 36, height: 36, rounded: 999, children: _jsx(PanelLeft, { size: 16 }) }), _jsx(ModeMark, { mode: mode, onChat: onChat, onDev: onDev, stacked: true }), _jsx(SidebarIconButton, { label: mode === 'chat' ? 'New chat' : 'New run', onPress: create, width: 36, height: 36, rounded: 999, children: _jsx(Plus, { size: 16 }) }), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, onOrg: org })] }) }));
|
|
78
79
|
return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
|
|
79
80
|
// inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
|
|
80
|
-
style: drawer ? undefined : span.style, position: "relative", shrink: 0, height: "100%", width: drawer ? '100%' : 'var(--span)', ...(drawer ? {} : { '$max-md': { display: 'none' } }), children: [_jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "false", role: "navigation", "aria-label":
|
|
81
|
+
style: drawer ? undefined : span.style, position: "relative", shrink: 0, height: "100%", width: drawer ? '100%' : 'var(--span)', ...(drawer ? {} : { '$max-md': { display: 'none' } }), children: [_jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "false", role: "navigation", "aria-label": brand(), ...pane(), ...FILL, px: "$2.5", pt: "$3", pb: "$2", gap: "$0", children: [_jsxs(XStack, { "data-slot": "mode-switch", items: "center", gap: "$2", px: "$1.5", pb: "$3", minW: 0, children: [_jsx(HanzoMark, { size: 18 }), _jsx(ModeMark, { mode: mode, onChat: onChat, onDev: onDev })] }), _jsxs(XStack, { "data-slot": "app-filter", items: "center", gap: "$2", px: "$3", height: 36, rounded: 999, borderWidth: 1, borderColor: "$borderColor", bg: "$edge", shrink: 0, children: [_jsx(Search, { size: 15, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { render: _jsx("input", { value: words, onChange: (e) => setWords(e.target.value), onKeyDown: (e) => {
|
|
81
82
|
if (e.key === 'Escape')
|
|
82
83
|
setWords('');
|
|
83
84
|
}, placeholder: "Filter", "aria-label": "Filter recents" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$2" })] }), _jsxs(YStack, { role: "list", mt: "$2.5", gap: 2, shrink: 0, children: [_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: fresh, icon: _jsx(Plus, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: mode === 'chat' ? 'New chat' : 'New run' }) }), mode === 'dev'
|
package/dist/Directory.js
CHANGED
|
@@ -44,7 +44,6 @@ import { ArrowUpRight, Check, Plus, Search, X } from 'lucide-react';
|
|
|
44
44
|
import { Box, Dialog, DialogContent, DialogTitle, Input, Text, XStack, YStack, } from '@hanzo/ui';
|
|
45
45
|
import { Grid } from '@hanzo/ui/grid';
|
|
46
46
|
import { useIam } from '@hanzo/iam/react';
|
|
47
|
-
import { where } from './where.js';
|
|
48
47
|
import { ProviderMark } from './ProviderMark.js';
|
|
49
48
|
import { say } from './failure.js';
|
|
50
49
|
import { api } from './lib/api.js';
|
|
@@ -85,10 +84,14 @@ const ENDPOINT = {
|
|
|
85
84
|
* provider the registry publishes is always drawn.
|
|
86
85
|
*/
|
|
87
86
|
const LEAD = ['tiktok', 'instagram', 'linkedin', 'slack', 'x', 'whatsapp', 'discord', 'telegram', 'facebook', 'teams', 'threads', 'youtube'];
|
|
88
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* The published integrations (lib/integrations), as directory rows. Real,
|
|
89
|
+
* public, counted. Loaded when the tab first opens, so a page the rooms frame
|
|
90
|
+
* carries none of the catalogue's guides.
|
|
91
|
+
*/
|
|
89
92
|
let listing = null;
|
|
90
|
-
const apps = () => (listing ??= (
|
|
91
|
-
.then((
|
|
93
|
+
const apps = () => (listing ??= import('./lib/integrations.js')
|
|
94
|
+
.then(({ INTEGRATIONS }) => INTEGRATIONS.map((i) => ({
|
|
92
95
|
id: i.slug,
|
|
93
96
|
name: i.name,
|
|
94
97
|
publisher: i.creator,
|
package/dist/Guide.js
CHANGED
|
@@ -29,6 +29,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
|
|
|
29
29
|
import { createPortal } from 'react-dom';
|
|
30
30
|
import { Text, View, XStack, YStack } from '@hanzo/gui';
|
|
31
31
|
import { Button } from '@hanzo/ui';
|
|
32
|
+
import { brand } from './where.js';
|
|
32
33
|
import { useIam } from '@hanzo/iam/react';
|
|
33
34
|
import { useAi } from './lib/ai.js';
|
|
34
35
|
import { Next, Scene } from './Scene.js';
|
|
@@ -259,7 +260,7 @@ export function Guide({ room = 'chat' }) {
|
|
|
259
260
|
// The dim is four panels around the spot rather than one box with a hole:
|
|
260
261
|
// a hole wants a mask, and a mask over a blurred backdrop reads as a seam.
|
|
261
262
|
const panel = { position: 'fixed', bg: 'var(--surface-scrim)', z: 'var(--z-overlay)', onClick: () => done('skipped') };
|
|
262
|
-
return createPortal(_jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label":
|
|
263
|
+
return createPortal(_jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": `Getting started with ${brand()}`, children: [_jsx(View, { ...panel, t: 0, l: 0, r: 0, height: Math.max(0, spot.top) }), _jsx(View, { ...panel, t: spot.top + spot.height, l: 0, r: 0, b: 0 }), _jsx(View, { ...panel, t: spot.top, l: 0, width: Math.max(0, spot.left), height: spot.height }), _jsx(View, { ...panel, t: spot.top, l: spot.left + spot.width, r: 0, height: spot.height }), _jsx(View, { position: "fixed", t: spot.top, l: spot.left, width: spot.width, height: spot.height, rounded: 10, borderWidth: 1, borderColor: "var(--ring)", boxShadow: RING, pointerEvents: "none", z: ABOVE }), stop.scene ? (_jsx(Scene, { step: `${i + 1} / ${stops.length}`, onNext: next, onSkip: () => done('skipped') })) : (_jsxs(YStack, { ref: cardRef, position: "fixed", t: card.top, l: card.left, width: CARD_W, z: "var(--z-modal)", bg: "var(--popover)", borderWidth: 1, borderColor: "var(--border-control)", rounded: 12, boxShadow: "var(--shadow-floating)", pt: 18, px: 18, pb: 15, children: [_jsx(View, { "aria-hidden": true, position: "absolute", width: 11, height: 11, bg: "var(--popover)", rotate: "45deg", borderColor: "var(--border-control)", ...POINTER[side] }), _jsxs(XStack, { items: "center", gap: 8, mb: 9, children: [_jsxs(Text, { fontFamily: "$body", lineHeight: LEAD, fontSize: FLOOR, letterSpacing: 1.1, textTransform: "uppercase", color: "$faint", fontVariant: ['tabular-nums'], children: [i + 1, " / ", stops.length] }), _jsx(View, { flex: 1, height: 1, bg: "$borderColor" }), _jsx(XStack, { gap: 3, children: stops.map((s, n) => (_jsx(View, { width: n === i ? 14 : 5, height: 5, rounded: 3, bg: n <= i ? '$ink' : '$rim', transition: "200ms" }, s.id))) })] }), _jsx(Text, { render: "h2", fontFamily: "$body", mb: 7, fontSize: "$6", fontWeight: "600", lineHeight: 22.1, letterSpacing: -0.17, color: "$ink", children: stop.title }), _jsx(Text, { render: "p", fontFamily: "$body", mb: 16, fontSize: "$3", lineHeight: 22.4, color: "$quiet", children: stop.body }), _jsxs(XStack, { items: "center", gap: 8, children: [_jsx(Button, { variant: "linkMuted", size: "sm", px: 2, onClick: () => done('skipped'), children: "Skip" }), _jsx(View, { flex: 1 }), i > 0 ? (_jsx(Button, { size: "sm", onClick: back, children: "Back" })) : null, _jsx(Next, { onPress: next, label: last ? 'Done' : 'Next' })] })] }))] }), document.body);
|
|
263
264
|
}
|
|
264
265
|
/** Let a room's guide be asked for again — a menu row can call this and reload. */
|
|
265
266
|
export function replayGuide(room = 'chat') {
|
package/dist/Market.js
CHANGED
|
@@ -22,6 +22,7 @@ import { Box, Text, XStack, YStack } from '@hanzo/ui';
|
|
|
22
22
|
import { useIam } from '@hanzo/iam/react';
|
|
23
23
|
import { hasSession } from './lib/session.js';
|
|
24
24
|
import { useAi } from './lib/ai.js';
|
|
25
|
+
import { brand } from './where.js';
|
|
25
26
|
/** A listing reachable over HTTP is one this app can connect on its own. */
|
|
26
27
|
const reachable = (listing) => (listing.transports ?? []).includes('streamable-http');
|
|
27
28
|
/** Labels a namespace carries that name no brand: `com.cloudflare.mcp` is
|
|
@@ -137,7 +138,7 @@ export function Market({ scope }) {
|
|
|
137
138
|
return _jsx(Empty, { children: "Sign in to browse agents you can add." });
|
|
138
139
|
if (failed)
|
|
139
140
|
return _jsx(Empty, { children: say(failed, 'the marketplace') });
|
|
140
|
-
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(YStack, { p: "$5", gap: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [
|
|
141
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(YStack, { p: "$5", gap: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsxs(Text, { fontSize: "$7", lineHeight: "$7", fontWeight: "500", color: "$ink", children: ["Put AI to work in ", brand()] }), _jsxs(Text, { fontSize: "$3", color: "$soft", children: ["Browse agents that bring your tools into ", brand(), "."] }), _jsxs(XStack, { mt: "$2", width: 420, maxW: "100%", height: 36, items: "center", gap: "$2", px: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Search, { size: 14, "aria-hidden": true }), _jsx(Text, { render: _jsx("input", { value: ask, onChange: (e) => setAsk(e.target.value), placeholder: "Search agents" }), flex: 1, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit" })] })] }), !listings ? (_jsx(Empty, { children: "Reading the shelf." })) : shown.length === 0 ? (_jsx(Empty, { children: scope === 'installed'
|
|
141
142
|
? 'No agents installed yet.'
|
|
142
143
|
: ask
|
|
143
144
|
? 'Nothing in the registry matches that.'
|
package/dist/Me.js
CHANGED
|
@@ -32,6 +32,7 @@ import { org as current, orgs, pick, renew, scope, superAdmin } from './lib/sess
|
|
|
32
32
|
import { payPage } from './lib/pay.js';
|
|
33
33
|
import { planName } from './lib/plans.js';
|
|
34
34
|
import { ENTRY } from './lib/host.js';
|
|
35
|
+
import { where } from './where.js';
|
|
35
36
|
import { SupportPicker } from './Support.js';
|
|
36
37
|
import { BAD } from './lib/mix.js';
|
|
37
38
|
/** The documentation Get help opens. */
|
|
@@ -140,7 +141,7 @@ export function Me({ host, narrow = false, onOrg, }) {
|
|
|
140
141
|
const other = usePlan(personal && personal.name !== current ? personal.name : null, menu.open);
|
|
141
142
|
const planFor = (o) => (o.name === current ? planOf(tier) : o.personal ? other : '');
|
|
142
143
|
const who = host.person?.name || host.person?.email || '';
|
|
143
|
-
const
|
|
144
|
+
const standing = [said(here?.role ?? ''), here?.display ?? current].filter(Boolean).join(' · ');
|
|
144
145
|
// The organization in use takes focus once the menu is drawn — after the
|
|
145
146
|
// popover has placed its own focus — so the keys reach the rows at once.
|
|
146
147
|
const land = (node) => {
|
|
@@ -161,11 +162,11 @@ export function Me({ host, narrow = false, onOrg, }) {
|
|
|
161
162
|
return null;
|
|
162
163
|
return (_jsxs(_Fragment, { children: [_jsxs(Popover, { open: menu.open, onOpenChange: menu.onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 8, children: [_jsx(Popover.Anchor, { width: "100%", children: _jsxs(XStack, { ref: (el) => {
|
|
163
164
|
row.current = el instanceof HTMLElement ? el : null;
|
|
164
|
-
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }),
|
|
165
|
+
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), standing ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: standing })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
|
|
165
166
|
menu.onOpenChange(false);
|
|
166
167
|
if (o.name !== current)
|
|
167
168
|
onOrg(o.name);
|
|
168
|
-
} }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(ORG_SETTINGS)) }), here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null, _jsx(Rule, {}), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: "Add funds", sub: typeof tier?.balance?.effectiveAvailable === 'number' ? `Credits ${formatCents(tier.balance.effectiveAvailable)}` : 'Credits', onPress: () => (menu.onOpenChange(false), window.open(payPage(), '_blank', 'noopener')), trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Settings", onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(Item, { icon: _jsx(Gauge, { size: 15, "aria-hidden": true }), label: "Usage", onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: "View all plans", onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(Item, { icon: _jsx(LifeBuoy, { size: 15, "aria-hidden": true }), label: "Get help", onPress: () => (menu.onOpenChange(false), window.open(DOCS, '_blank', 'noopener,noreferrer')) }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(Rule, {}), _jsx(Item, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), label: "Log out", onPress: () => (menu.onOpenChange(false), host.signOut?.()) })] })) : null] }) }) })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null] }));
|
|
169
|
+
} }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(ORG_SETTINGS)) }), where().team ? null : here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null, _jsx(Rule, {}), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: "Add funds", sub: typeof tier?.balance?.effectiveAvailable === 'number' ? `Credits ${formatCents(tier.balance.effectiveAvailable)}` : 'Credits', onPress: () => (menu.onOpenChange(false), window.open(payPage(), '_blank', 'noopener')), trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Settings", onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(Item, { icon: _jsx(Gauge, { size: 15, "aria-hidden": true }), label: "Usage", onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: "View all plans", onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(Item, { icon: _jsx(LifeBuoy, { size: 15, "aria-hidden": true }), label: "Get help", onPress: () => (menu.onOpenChange(false), window.open(DOCS, '_blank', 'noopener,noreferrer')) }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(Rule, {}), _jsx(Item, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), label: "Log out", onPress: () => (menu.onOpenChange(false), host.signOut?.()) })] })) : null] }) }) })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null] }));
|
|
169
170
|
}
|
|
170
171
|
/**
|
|
171
172
|
* The sheet that creates an organization: a name, then `POST /v1/account/orgs`,
|
package/dist/Meet.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
|
-
import { Video, VideoOff, Mic, MicOff, PhoneOff, Copy, Check, Plus, Calendar, ExternalLink, Bot, Volume2, Square, Send, UserPlus, Tv, LayoutGrid, ArrowRight, } from 'lucide-react';
|
|
4
|
+
import { Video, VideoOff, Mic, MicOff, PhoneOff, Copy, Check, Plus, Calendar, ExternalLink, Bot, Volume2, Square, Send, UserPlus, Tv, LayoutGrid, ArrowRight, Captions, } from 'lucide-react';
|
|
5
5
|
import { Text, View, XStack, YStack } from '@hanzo/gui';
|
|
6
6
|
import { Button } from '@hanzo/ui';
|
|
7
7
|
import { useIamToken } from '@hanzo/iam/react';
|
|
8
|
-
import { REFUSED, speech, useVoice } from '@hanzo/voice';
|
|
8
|
+
import { REFUSED, speech, useTranscript, useVoice } from '@hanzo/voice';
|
|
9
9
|
import { Face, voiceOf } from './cast.js';
|
|
10
10
|
import { member } from './team.js';
|
|
11
11
|
import { cleanForSpeech, speakAgent, stopAgentSpeech, AudioWave, useAgentSpeech } from './speech.js';
|
|
@@ -220,8 +220,20 @@ export function Meet() {
|
|
|
220
220
|
onUtterance: (transcript) => void handleSpokenInput(transcript),
|
|
221
221
|
});
|
|
222
222
|
const handsFreeSay = handsFree.say;
|
|
223
|
+
// LIVE TRANSCRIPT: what is said in the call lands in its notes as it settles,
|
|
224
|
+
// on Hanzo's growing transcript (/v1/audio/transcript), and the line under the
|
|
225
|
+
// notes shows the words still being decoded. Silence is neither decoded nor
|
|
226
|
+
// billed. No SpeechRecognition anywhere, so every browser that can record
|
|
227
|
+
// takes part.
|
|
228
|
+
const [caption, setCaption] = useState('');
|
|
229
|
+
const transcriber = useTranscript({
|
|
230
|
+
speech: ear,
|
|
231
|
+
onPartial: setCaption,
|
|
232
|
+
onSettled: (text) => setLiveNotes((prev) => [...prev, `${clock()}: You: ${text}`]),
|
|
233
|
+
});
|
|
234
|
+
const transcriberNote = transcriber.reason ?? (transcriber.refusal ? REFUSED.lost : null);
|
|
223
235
|
const handleSpokenInput = useCallback(async (transcript) => {
|
|
224
|
-
const time =
|
|
236
|
+
const time = clock();
|
|
225
237
|
setLiveNotes((prev) => [...prev, `${time}: You (voice): ${transcript}`]);
|
|
226
238
|
try {
|
|
227
239
|
const reply = await generateAgentMeetingResponse(transcript, activeCoPilotAgent, token);
|
|
@@ -238,7 +250,7 @@ export function Meet() {
|
|
|
238
250
|
if (!q)
|
|
239
251
|
return;
|
|
240
252
|
setAgentQuestion('');
|
|
241
|
-
const time =
|
|
253
|
+
const time = clock();
|
|
242
254
|
setLiveNotes((prev) => [...prev, `${time}: You: ${q}`]);
|
|
243
255
|
try {
|
|
244
256
|
const reply = await generateAgentMeetingResponse(q, activeCoPilotAgent, token);
|
|
@@ -263,9 +275,12 @@ export function Meet() {
|
|
|
263
275
|
['grid', 'Agent Grid', LayoutGrid],
|
|
264
276
|
].map(([view, label, Icon]) => (_jsxs(Button, { size: "sm", variant: meetingView === view ? 'primary' : 'ghost', ...(meetingView === view ? null : { color: '$soft' }), "aria-pressed": meetingView === view, onClick: () => setMeetingView(view), children: [_jsx(Icon, { size: 12 }), label] }, view))) }), botAssisting ? (_jsx(View, { display: "none", "$max-md": { display: 'flex' }, children: _jsxs(Button, { size: "sm", variant: copilot ? 'secondary' : 'default', onClick: () => setCopilot((v) => !v), "aria-label": "Co-pilot", "aria-expanded": copilot, "aria-controls": "meet-copilot", children: [_jsx(Bot, { size: 13 }), "Co-pilot"] }) })) : null, _jsxs(Button, { size: "sm", onClick: () => handleCopyLink(activeMeetingRoom), children: [copied ? _jsx(Check, { size: 13 }) : _jsx(Copy, { size: 13 }), copied ? 'Link Copied' : 'Copy Room Link'] }), _jsxs(Button, { size: "sm", variant: "destructive", onClick: () => {
|
|
265
277
|
stopAgentSpeech();
|
|
266
|
-
// Leaving the call hangs up the hands-free conversation
|
|
278
|
+
// Leaving the call hangs up the hands-free conversation and
|
|
279
|
+
// settles the transcript with it.
|
|
267
280
|
if (handsFree.open)
|
|
268
281
|
handsFree.toggle();
|
|
282
|
+
if (transcriber.open)
|
|
283
|
+
transcriber.toggle();
|
|
269
284
|
setActiveMeetingRoom(null);
|
|
270
285
|
}, children: [_jsx(PhoneOff, { size: 14 }), "Leave Call"] })] })) : (_jsx(XStack, { items: "center", gap: 8, shrink: 1, minW: "auto", children: _jsx(Button, { asChild: true, variant: "linkMuted", size: "sm", px: 0, children: _jsxs("a", { href: "https://meet.hanzo.ai", target: "_blank", rel: "noopener noreferrer", children: ["meet.hanzo.ai", _jsx(ExternalLink, { size: 12 })] }) }) }))] }), activeMeetingRoom ? (_jsxs(XStack, { flex: 1, overflow: "hidden", position: "relative", children: [_jsx(XStack, { flex: 1, minW: "auto", position: "relative", bg: "$sunken", children: meetingView === 'iframe' ? (_jsx(View, { render: _jsx("iframe", { src: `https://meet.hanzo.ai/${activeMeetingRoom}#config.prejoinPageEnabled=false&config.startWithAudioMuted=${!micOn}&config.startWithVideoMuted=${!cameraOn}`, allow: "camera; microphone; display-capture; autoplay; clipboard-write; speaker-selection; screen-wake-lock", title: `Hanzo Meet Room: ${activeMeetingRoom}` }), width: "100%", height: "100%", borderWidth: 0, bg: "$sunken" }, "stage")) : (_jsxs(View, { flex: 1, display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 12, p: 16, overflowY: "auto", bg: "$background", children: [_jsx(YStack, { bg: "$panel", rounded: 12, borderWidth: 1, borderColor: "$borderColor", position: "relative", overflow: "hidden", items: "center", justify: "center", minH: 220, children: _jsxs(YStack, { items: "center", children: [_jsx(YStack, { width: 72, height: 72, rounded: 9999, bg: "$raised", items: "center", justify: "center", mb: 8, children: _jsx(Text, { fontSize: "$8", fontWeight: "700", color: "$ink", children: "You" }) }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Alex (Host)" }), _jsx(Text, { fontSize: "$1", color: micOn ? GOOD : '$soft', children: micOn ? 'Mic Active' : 'Muted' })] }) }), invitedAgents.map((agentName) => {
|
|
271
286
|
const voice = voiceOf(agentName);
|
|
@@ -283,12 +298,12 @@ export function Meet() {
|
|
|
283
298
|
z: 'var(--z-sheet)',
|
|
284
299
|
borderLeftWidth: 0,
|
|
285
300
|
}, children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: 8, children: [_jsx(Face, { name: activeCoPilotAgent, size: 28 }), _jsxs(YStack, { children: [_jsx(Text, { fontSize: "$2", fontWeight: "700", color: "$ink", children: activeCoPilotAgent }), _jsxs(Text, { fontSize: FLOOR, color: "$faint", children: ["AI Co-pilot \u00B7 Voice: ", voiceOf(activeCoPilotAgent)] })] })] }), _jsx(Text, { fontSize: FLOOR, fontWeight: "700", color: GOOD, bg: "$hover", px: 6, py: 2, rounded: 4, children: "Live in call" })] }), _jsx(XStack, { gap: 4, flexWrap: "wrap", children: invitedAgents.map((agent) => (_jsx(Button, { size: "sm", variant: activeCoPilotAgent === agent ? 'secondary' : 'ghost', color: activeCoPilotAgent === agent ? '$ink' : '$soft', "aria-pressed": activeCoPilotAgent === agent, onClick: () => setActiveCoPilotAgent(agent), children: agent }, agent))) }), _jsxs(XStack, { gap: 8, children: [_jsx(Button, { size: "sm", flex: 1, variant: speaking ? 'secondary' : 'default', disabled: !speaking && !liveNotes.length, onClick: handleReadBriefing, children: speaking ? (_jsxs(_Fragment, { children: [_jsx(AudioWave, { active: true, color: "var(--state-success)" }), "Stop Briefing", _jsx(Square, { size: 11, fill: "currentColor" })] })) : (_jsxs(_Fragment, { children: [_jsx(Volume2, { size: 14 }), "Briefing"] })) }), _jsxs(Button, { size: "sm", variant: handsFree.open ? 'secondary' : 'default', "data-testid": "hands-free-voice-toggle", "data-state": handsFree.state, "data-refusal": handsFree.refusal ? handsFree.refusal.service : undefined, "aria-pressed": handsFree.open, disabled: !!handsFree.blocked, title: handsFreeNote ??
|
|
286
|
-
(handsFree.open ? 'Hands-Free Voice Active (Speaks and interrupts on voice)' : 'Enable Hands-Free Voice Mode'), onClick: handsFree.toggle, children: [_jsx(Mic, { size: 14, color: handsFree.open ? 'var(--state-success)' : 'currentColor' }), handsFree.open ? 'Hands-Free On' : 'Hands-Free'] })] }), handsFreeNote ? (_jsx(Text, { fontSize: "$1", color: "$soft", role: "status", children: handsFreeNote })) : null, _jsx(YStack, { flex: 1, overflowY: "auto", bg: "$background", rounded: 8, borderWidth: 1, borderColor: "$borderColor", p: 12, gap: 8, children: liveNotes.map((note, i) => (_jsx(Text, { fontSize: "$1", color: "$quiet", lineHeight: 16.5, pb: 8, borderBottomWidth: 1, borderBottomColor: "$borderColor", children: note }, i))) }), _jsxs(XStack, { render: "form", onSubmit: handleAskAgent, gap: 6, children: [_jsx(Text, { render: _jsx("input", { type: "text", value: agentQuestion, onChange: (e) => setAgentQuestion(e.target.value), placeholder: `Ask ${activeCoPilotAgent} in meeting...` }), flex: 1, bg: "$background", borderWidth: 1, borderColor: "var(--border-control)", rounded: 6, px: 10, py: 8, color: "$ink", fontSize: "$1", outlineStyle: "none" }), _jsx(Button, { type: "submit", variant: "primary", size: "icon-sm", disabled: !agentQuestion.trim(), "aria-label": "Ask", children: _jsx(Send, { size: 12 }) })] }), _jsxs(XStack, { gap: 8, children: [_jsx(Button, { size: "sm", flex: 1, disabled: !agentQuestion.trim(), title: "Add what is typed above to the notes, without asking the co-pilot", onClick: () => {
|
|
301
|
+
(handsFree.open ? 'Hands-Free Voice Active (Speaks and interrupts on voice)' : 'Enable Hands-Free Voice Mode'), onClick: handsFree.toggle, children: [_jsx(Mic, { size: 14, color: handsFree.open ? 'var(--state-success)' : 'currentColor' }), handsFree.open ? 'Hands-Free On' : 'Hands-Free'] }), _jsxs(Button, { size: "sm", variant: transcriber.open ? 'secondary' : 'default', "data-testid": "live-transcript-toggle", "data-state": transcriber.state, "data-refusal": transcriber.refusal ? transcriber.refusal.service : undefined, "aria-pressed": transcriber.open, disabled: !!transcriber.blocked, title: transcriberNote ?? (transcriber.open ? 'Transcribing — click to stop' : 'Transcribe what is said into the notes'), onClick: transcriber.toggle, children: [_jsx(Captions, { size: 14, color: transcriber.open ? 'var(--state-success)' : 'currentColor' }), transcriber.open ? 'Transcribing' : 'Transcribe'] })] }), handsFreeNote ? (_jsx(Text, { fontSize: "$1", color: "$soft", role: "status", children: handsFreeNote })) : null, transcriberNote ? (_jsx(Text, { fontSize: "$1", color: "$soft", role: "status", children: transcriberNote })) : null, _jsxs(YStack, { flex: 1, overflowY: "auto", bg: "$background", rounded: 8, borderWidth: 1, borderColor: "$borderColor", p: 12, gap: 8, children: [liveNotes.map((note, i) => (_jsx(Text, { fontSize: "$1", color: "$quiet", lineHeight: 16.5, pb: 8, borderBottomWidth: 1, borderBottomColor: "$borderColor", children: note }, i))), transcriber.open && caption ? (_jsx(Text, { "data-testid": "live-caption", fontSize: "$1", color: "$soft", lineHeight: 16.5, "aria-live": "polite", children: caption })) : null] }), _jsxs(XStack, { render: "form", onSubmit: handleAskAgent, gap: 6, children: [_jsx(Text, { render: _jsx("input", { type: "text", value: agentQuestion, onChange: (e) => setAgentQuestion(e.target.value), placeholder: `Ask ${activeCoPilotAgent} in meeting...` }), flex: 1, bg: "$background", borderWidth: 1, borderColor: "var(--border-control)", rounded: 6, px: 10, py: 8, color: "$ink", fontSize: "$1", outlineStyle: "none" }), _jsx(Button, { type: "submit", variant: "primary", size: "icon-sm", disabled: !agentQuestion.trim(), "aria-label": "Ask", children: _jsx(Send, { size: 12 }) })] }), _jsxs(XStack, { gap: 8, children: [_jsx(Button, { size: "sm", flex: 1, disabled: !agentQuestion.trim(), title: "Add what is typed above to the notes, without asking the co-pilot", onClick: () => {
|
|
287
302
|
const note = agentQuestion.trim();
|
|
288
303
|
if (!note)
|
|
289
304
|
return;
|
|
290
305
|
setAgentQuestion('');
|
|
291
|
-
setLiveNotes((prev) => [...prev, `${
|
|
306
|
+
setLiveNotes((prev) => [...prev, `${clock()}: You (note): ${note}`]);
|
|
292
307
|
}, children: "+ Add Live Note" }), _jsx(Button, { variant: "primary", size: "sm", onClick: () => {
|
|
293
308
|
const text = liveNotes.join('\n');
|
|
294
309
|
navigator.clipboard.writeText(text);
|
|
@@ -311,3 +326,7 @@ export function Meet() {
|
|
|
311
326
|
setActiveMeetingRoom(m.roomName);
|
|
312
327
|
}, children: [_jsx(Video, { size: 12 }), "Join Call"] }), _jsx(Button, { size: "icon-sm", "aria-label": "Copy invite link", title: "Copy invite link", onClick: () => handleCopyLink(m.roomName), children: _jsx(Copy, { size: 12 }) })] })] }, m.id))) }), _jsxs(YStack, { mt: "auto", p: 12, bg: "$background", rounded: 8, borderWidth: 1, borderColor: "$borderColor", gap: 6, children: [_jsxs(XStack, { items: "center", gap: 8, children: [_jsx(Calendar, { size: 14, color: "var(--muted-foreground)" }), _jsx(Text, { fontSize: "$2", fontWeight: "700", color: "$ink", children: "Need to book a slot?" })] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Use the Hanzo Calendar Scheduler to coordinate available times across team members and bots." }), _jsx(Button, { asChild: true, variant: "link", size: "sm", px: 0, justify: "flex-start", children: _jsxs("a", { href: "/cal", children: ["Open Calendar Scheduler", _jsx(ArrowRight, { size: 11 })] }) })] })] })] }))] }));
|
|
313
328
|
}
|
|
329
|
+
/** A note's time, as the reader's clock shows it: 4:22 PM, never "4:22:". */
|
|
330
|
+
function clock() {
|
|
331
|
+
return new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
|
332
|
+
}
|
package/dist/ModeMark.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The product's name in each mode:
|
|
1
|
+
/** The product's name in each mode: the host's brand in Chat (Hanzo AI, Hanzo Team), Hanzo Dev in Dev. */
|
|
2
2
|
export declare const wordmark: (mode: 'chat' | 'dev') => string;
|
|
3
3
|
export declare function ModeMark({ mode, onChat, onDev, stacked, }: {
|
|
4
4
|
mode: 'chat' | 'dev';
|
package/dist/ModeMark.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { MessageSquare, Code2 } from 'lucide-react';
|
|
4
4
|
import { XStack, YStack, Text, Tooltip, TooltipContent, TooltipTrigger } from '@hanzo/ui';
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { brand } from './where.js';
|
|
6
|
+
/** The product's name in each mode: the host's brand in Chat (Hanzo AI, Hanzo Team), Hanzo Dev in Dev. */
|
|
7
|
+
export const wordmark = (mode) => (mode === 'dev' ? 'Hanzo Dev' : brand());
|
|
7
8
|
/** One side of the switch: an icon button named for its mode, with the name as its tooltip. */
|
|
8
9
|
function Side({ name, on, onPress, children }) {
|
|
9
10
|
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { children: _jsx(XStack, { render: "button", "aria-label": name, "aria-pressed": on, onPress: onPress, width: 28, height: 24, rounded: 999, items: "center", justify: "center", bg: on ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, cursor: "pointer", children: children }) }), _jsx(TooltipContent, { children: _jsx(Text, { fontSize: "$2", children: name }) })] }));
|
package/dist/Orgs.js
CHANGED
|
@@ -45,7 +45,7 @@ import { createInvitation, inviteLink } from './lib/invite.js';
|
|
|
45
45
|
import { countSignup } from './lib/intent.js';
|
|
46
46
|
import { BAD, mix } from './lib/mix.js';
|
|
47
47
|
import { useRooms } from './host.js';
|
|
48
|
-
import { site, where } from './where.js';
|
|
48
|
+
import { brand, site, where } from './where.js';
|
|
49
49
|
/** A template to set up once the organization is live. */
|
|
50
50
|
const SETUP = 'hanzo:org:template';
|
|
51
51
|
/** Checkout for a plan, coming back to this room. */
|
|
@@ -304,7 +304,7 @@ function Step({ at, of }) {
|
|
|
304
304
|
return (_jsxs(Text, { render: "p", mb: 10, fontSize: "$2", fontFamily: "$mono", color: "$soft", children: ["Step ", at, " of ", of] }));
|
|
305
305
|
}
|
|
306
306
|
function Brand() {
|
|
307
|
-
return (_jsx(Text, { fontSize: "$6", fontWeight: "500", mb: 28, color: "var(--foreground)", children:
|
|
307
|
+
return (_jsx(Text, { fontSize: "$6", fontWeight: "500", mb: 28, color: "var(--foreground)", children: brand() }));
|
|
308
308
|
}
|
|
309
309
|
/** The free rung's name in the billing catalog. */
|
|
310
310
|
const FREE_TIER = 'free';
|
package/dist/Setup.js
CHANGED
|
@@ -15,7 +15,7 @@ import { firstRun } from '@hanzo/ui/onboarding';
|
|
|
15
15
|
import { useKept } from './kept.js';
|
|
16
16
|
import { orgs } from './lib/session.js';
|
|
17
17
|
import { api } from './lib/api.js';
|
|
18
|
-
import { site } from './where.js';
|
|
18
|
+
import { brand, site } from './where.js';
|
|
19
19
|
/** Put away in this browser. */
|
|
20
20
|
const LATER = 'hanzo.onboarding.later';
|
|
21
21
|
export function Setup() {
|
|
@@ -32,5 +32,5 @@ export function Setup() {
|
|
|
32
32
|
}, [later]);
|
|
33
33
|
if (later || !owed)
|
|
34
34
|
return null;
|
|
35
|
-
return (_jsxs(XStack, { "data-slot": "setup", role: "region", "aria-label":
|
|
35
|
+
return (_jsxs(XStack, { "data-slot": "setup", role: "region", "aria-label": `Set up ${brand()}`, items: "center", gap: "$2.5", px: "$3", py: "$2.5", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$edge", children: [_jsx(Sparkles, { size: 16, "aria-hidden": true }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsxs(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: ["Finish setting up ", brand()] }), _jsx(Text, { render: _jsx("a", { href: site(`/onboarding?next=${encodeURIComponent('/')}`) }), color: "var(--soft, var(--muted-foreground))", fontSize: "$1", textDecorationLine: "underline", self: "flex-start", children: "Set up" })] }), _jsx(XStack, { render: "button", "aria-label": "Not now", onPress: () => setLater(true), p: "$1", rounded: 999, hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 14, "aria-hidden": true }) })] }));
|
|
36
36
|
}
|
package/dist/Shared.js
CHANGED
|
@@ -16,6 +16,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
16
16
|
import { useEffect, useState } from 'react';
|
|
17
17
|
import { useIam } from '@hanzo/iam/react';
|
|
18
18
|
import { Anchor, Button, Text, XStack, YStack } from '@hanzo/ui';
|
|
19
|
+
import { brand } from './where.js';
|
|
19
20
|
import { useHydrated } from './lib/hydrated.js';
|
|
20
21
|
import { enter, LOGIN, signUp } from './lib/destination.js';
|
|
21
22
|
import { Prose } from './Prose.js';
|
|
@@ -76,14 +77,14 @@ export function Shared() {
|
|
|
76
77
|
: hydrated && !isLoading && aim && !('token' in aim) && !isAuthenticated
|
|
77
78
|
? { kind: 'ask' }
|
|
78
79
|
: state;
|
|
79
|
-
return (_jsxs(YStack, { role: "main", height: "100dvh", overflowY: "auto", bg: "$background", children: [_jsxs(XStack, { height: 48, px: "$4", items: "center", justify: "space-between", borderBottomWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Anchor, { href: "/", "aria-label":
|
|
80
|
+
return (_jsxs(YStack, { role: "main", height: "100dvh", overflowY: "auto", bg: "$background", children: [_jsxs(XStack, { height: 48, px: "$4", items: "center", justify: "space-between", borderBottomWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Anchor, { href: "/", "aria-label": brand(), children: _jsx(Text, { fontSize: "$4", fontWeight: "600", color: "$ink", children: brand() }) }), shown.kind === 'read' && shown.shared.full ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "Shared with you \u00B7 Read only" })) : null] }), _jsx(YStack, { width: "100%", maxW: MEASURE, mx: "auto", px: "$4", py: "$6", gap: "$4", children: _jsx(Body, { state: shown, signedIn: Boolean(isAuthenticated), onOpen: () => setAgreed(true) }) })] }));
|
|
80
81
|
}
|
|
81
82
|
function Body({ state, signedIn, onOpen }) {
|
|
82
83
|
if (state.kind === 'reading') {
|
|
83
84
|
return (_jsx(Text, { fontSize: "$3", color: "$soft", "aria-live": "polite", children: "Opening the chat." }));
|
|
84
85
|
}
|
|
85
86
|
if (state.kind === 'missing' || state.kind === 'gone') {
|
|
86
|
-
return (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(Text, { render: "h1", fontSize: "$7", fontWeight: "600", color: "$ink", children: "This chat is no longer shared." }), _jsx(Text, { fontSize: "$3", color: "$soft", children: state.kind === 'missing' ? 'The link is incomplete. Ask for it again.' : 'The link was turned off, or it was never a link to a chat.' }), _jsx(Anchor, { href: "/", children:
|
|
87
|
+
return (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(Text, { render: "h1", fontSize: "$7", fontWeight: "600", color: "$ink", children: "This chat is no longer shared." }), _jsx(Text, { fontSize: "$3", color: "$soft", children: state.kind === 'missing' ? 'The link is incomplete. Ask for it again.' : 'The link was turned off, or it was never a link to a chat.' }), _jsx(Anchor, { href: "/", children: _jsxs(Button, { size: "sm", variant: "outline", children: ["Go to ", brand()] }) })] }));
|
|
87
88
|
}
|
|
88
89
|
if (state.kind === 'ask') {
|
|
89
90
|
return _jsx(Ask, { signedIn: signedIn });
|
package/dist/Shell.js
CHANGED
|
@@ -113,7 +113,7 @@ const CONTROL = 28;
|
|
|
113
113
|
import { getStoredPeople, getStoredAgents, getCustomThreads, createDirectChat, } from "./contacts-store.js";
|
|
114
114
|
import { bare } from './lib/bare.js';
|
|
115
115
|
import { BAD } from './lib/mix.js';
|
|
116
|
-
import { site, where } from './where.js';
|
|
116
|
+
import { brand, site, where } from './where.js';
|
|
117
117
|
/**
|
|
118
118
|
* The destinations, in rail order.
|
|
119
119
|
*
|
|
@@ -128,7 +128,7 @@ const HOME = {
|
|
|
128
128
|
return where().home;
|
|
129
129
|
},
|
|
130
130
|
get title() {
|
|
131
|
-
return
|
|
131
|
+
return brand();
|
|
132
132
|
},
|
|
133
133
|
label: "Home",
|
|
134
134
|
// The chat bubble. Home IS the conversation with the org — the rooms it talks
|
|
@@ -152,7 +152,9 @@ const HOME = {
|
|
|
152
152
|
const CHAT = {
|
|
153
153
|
id: "chat",
|
|
154
154
|
route: "/chat",
|
|
155
|
-
title
|
|
155
|
+
get title() {
|
|
156
|
+
return brand();
|
|
157
|
+
},
|
|
156
158
|
label: "Chat",
|
|
157
159
|
icon: MessageSquare,
|
|
158
160
|
create: "New chat",
|
|
@@ -1206,7 +1208,7 @@ function Frame({ mode, children, navigate, back, forward, }) {
|
|
|
1206
1208
|
return;
|
|
1207
1209
|
e.preventDefault();
|
|
1208
1210
|
navigate("/");
|
|
1209
|
-
}, "aria-label":
|
|
1211
|
+
}, "aria-label": `${brand()} home`, title: "Home" }), "data-slot": "home", display: "flex", items: "center", justify: "center", width: 28, height: 28, color: "inherit", children: _jsx(HanzoMark, { size: 18 }) }), settled && isAuthenticated ? (_jsx(XStack, { shrink: 1, minW: 0, children: _jsx(OrgProjectSwitcher, { ...orgState, alwaysShow: true }) })) : null] }), _jsxs(YStack, { "data-slot": "places-list", px: "$2", pb: "$2", "$max-md": { display: "none" }, children: [places.filter((place) => matches(place.label, filter)).map((place) => (_jsx(SidebarItem, { icon: _jsx(place.icon, { size: 16, "aria-hidden": true }), active: place.id === mode, onPress: () => {
|
|
1210
1212
|
navigate?.(place.route);
|
|
1211
1213
|
onPick?.();
|
|
1212
1214
|
}, children: place.label }, place.id))), matches("Settings", filter) ? (_jsx(SidebarItem, { icon: _jsx(Settings2, { size: 16, "aria-hidden": true }), onPress: () => showSettings(FIRST), children: "Settings" })) : null] }), _jsx(XStack, { px: "$3", pb: "$2", children: _jsxs(XStack, { flex: 1, height: 30, items: "center", gap: "$2", px: "$2", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Search, { size: 13, "aria-hidden": true }), _jsx(Text, { render: _jsx("input", { value: filter, onChange: (e) => setFilter(e.target.value), onKeyDown: (e) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* The root names what a host passes; each room is its own subpath, so a page
|
|
9
9
|
* loads the rooms it draws and no others.
|
|
10
10
|
*/
|
|
11
|
-
export { configure, where, site } from './where.js';
|
|
11
|
+
export { configure, where, site, brand } from './where.js';
|
|
12
12
|
export type { Addresses } from './where.js';
|
|
13
13
|
export { Rooms, Look, useRooms, useLook, useOrg } from './host.js';
|
|
14
14
|
export type { Router, Link, RoomsProps } from './host.js';
|
package/dist/index.js
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
* The root names what a host passes; each room is its own subpath, so a page
|
|
9
9
|
* loads the rooms it draws and no others.
|
|
10
10
|
*/
|
|
11
|
-
export { configure, where, site } from './where.js';
|
|
11
|
+
export { configure, where, site, brand } from './where.js';
|
|
12
12
|
export { Rooms, Look, useRooms, useLook, useOrg } from './host.js';
|