@hanzo/rooms 0.1.24 → 0.1.25
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 +31 -20
- package/dist/Settings.js +10 -13
- package/dist/Shell.js +4 -2
- package/dist/Vibe.js +8 -7
- package/dist/enso.d.ts +7 -5
- package/dist/enso.js +3 -3
- package/dist/lib/limits.d.ts +2 -2
- package/dist/lib/limits.js +2 -2
- package/dist/lib/plans.d.ts +0 -2
- package/dist/lib/plans.js +0 -11
- package/package.json +5 -5
package/dist/Chat.js
CHANGED
|
@@ -44,7 +44,8 @@ import { Anchor, Box, Button, Text, Tooltip, TooltipContent, TooltipTrigger, Vie
|
|
|
44
44
|
import { Control } from "@hanzo/composer";
|
|
45
45
|
import { useIam, useIamToken } from "@hanzo/iam/react";
|
|
46
46
|
import { hasSession, org } from "./lib/session.js";
|
|
47
|
-
import { chats,
|
|
47
|
+
import { chats, house, useLimits } from "./lib/limits.js";
|
|
48
|
+
import { parseModels } from "@hanzo/ui/models/catalog";
|
|
48
49
|
import { LimitedBanner } from "@hanzo/ui/product/LimitedBanner";
|
|
49
50
|
import { WEB } from "./lib/web.js";
|
|
50
51
|
import { useHydrated } from "./lib/hydrated.js";
|
|
@@ -63,7 +64,6 @@ import { useStarred } from "./stars.js";
|
|
|
63
64
|
import { useModel } from "./model.js";
|
|
64
65
|
import { Enso, nameOf, useEffort } from "./enso.js";
|
|
65
66
|
import { checkoutUrl } from './lib/pay.js';
|
|
66
|
-
import { onFree } from './lib/plans.js';
|
|
67
67
|
import { Upgrade, useFree } from './Upgrade.js';
|
|
68
68
|
import { enter, LOGIN } from './lib/destination.js';
|
|
69
69
|
import { first } from './lib/first.js';
|
|
@@ -518,15 +518,22 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
518
518
|
// outputs and names no text (`chats`) is neither offered nor fallen back to.
|
|
519
519
|
const { models: catalog } = useModels();
|
|
520
520
|
const models = useMemo(() => catalog.filter(chats), [catalog]);
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const
|
|
521
|
+
// EVERY MODEL THE GATEWAY LISTS is offered here, through the one picker, and
|
|
522
|
+
// read from the catalog's own fields (`parseModels`): its class, its family.
|
|
523
|
+
// A premium model is picked for THIS conversation and never remembered as
|
|
524
|
+
// the default, so the next chat starts on Enso again; any other pick is the
|
|
525
|
+
// reader's default. A preference kept from before for a premium model is
|
|
526
|
+
// not a default either, so the house router is asked for instead. An agent's
|
|
527
|
+
// own model is sent as it is named; the gateway says which model answered
|
|
528
|
+
// (`servedBy`).
|
|
529
|
+
const listed = useMemo(() => parseModels(catalog), [catalog]);
|
|
530
|
+
const rowOf = (id) => listed.find((m) => m.id === id);
|
|
531
|
+
const [picked, setPicked] = useState(null);
|
|
532
|
+
const wanted = picked ?? (preferred && rowOf(preferred)?.class !== "premium" ? preferred : ENSO);
|
|
533
|
+
// A model the catalog lists is sent as picked, whatever it answers with; only
|
|
534
|
+
// a name the catalog does not list falls to one that converses (`served`).
|
|
535
|
+
const sent = anonymous ? FREE : wanted;
|
|
536
|
+
const model = persona?.model || (rowOf(sent) ? sent : served(models, sent));
|
|
530
537
|
// THE PLAN'S USAGE (lib/limits.ts), kept current by every chat answer's
|
|
531
538
|
// headers and read again whenever a send settles, below. The room draws no
|
|
532
539
|
// bars and no near note — those live on Settings → Usage. It says something
|
|
@@ -535,15 +542,19 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
535
542
|
const { limits, notice, reload: reread } = useLimits(!anonymous, org(), (id) => nameOf(catalog, id));
|
|
536
543
|
const held = Boolean(limits?.plan);
|
|
537
544
|
const pause = notice && (notice.fallback || notice.refused) ? notice : null;
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
// the
|
|
542
|
-
//
|
|
543
|
-
// `isFree` still drives the free-lane notice.
|
|
545
|
+
// EVERY MODEL IS CHOSEN, never asked for: one the plan does not cover still
|
|
546
|
+
// picks, and the gateway answers it with the policy's refusal or from Enso,
|
|
547
|
+
// which the room then shows. A turn the gateway refuses `plan_required`
|
|
548
|
+
// opens the upgrade, titled with the model. `isFree` still drives the
|
|
549
|
+
// free-lane notice.
|
|
544
550
|
const free = useFree(!anonymous) && !held;
|
|
545
551
|
const [ask, setAsk] = useState(null);
|
|
546
|
-
const choose = (id) =>
|
|
552
|
+
const choose = (id) => {
|
|
553
|
+
if (rowOf(id)?.class === "premium")
|
|
554
|
+
return setPicked(id);
|
|
555
|
+
setPicked(null);
|
|
556
|
+
setPreferred(id);
|
|
557
|
+
};
|
|
547
558
|
// KEEPING THE CONVERSATION is the reader's account, not this pane's choice:
|
|
548
559
|
// the store is per-org, so a signed-in reader has somewhere for it to go and
|
|
549
560
|
// an anonymous one does not. `thread` names the conversation being continued
|
|
@@ -560,7 +571,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
560
571
|
// lookups the platform's assistant uses, run inside the completion by the
|
|
561
572
|
// client's fetch (lib/web.ts), so a question about the weather or the news
|
|
562
573
|
// is answered from what the web says now.
|
|
563
|
-
params: { reasoning_effort: effort, ...(!anonymous &&
|
|
574
|
+
params: { reasoning_effort: effort, ...(!anonymous && house(rowOf(model)?.family) ? { tools: WEB } : {}) },
|
|
564
575
|
...(open ? { thread: open } : {}),
|
|
565
576
|
onError: (e) => {
|
|
566
577
|
const needs = planRequired(e);
|
|
@@ -1204,7 +1215,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1204
1215
|
: talk.state === "listening"
|
|
1205
1216
|
? "Listening — speak, and Hanzo answers aloud."
|
|
1206
1217
|
: "Connecting…" })) : null] })) : undefined,
|
|
1207
|
-
send: (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Enso, { effort: effort, onEffort: setEffort, model: wanted, onModel: choose, models:
|
|
1218
|
+
send: (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Enso, { effort: effort, onEffort: setEffort, model: wanted, onModel: choose, models: listed, limits: limits }), _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: {
|
|
1208
1219
|
idle: "Talk with Hanzo",
|
|
1209
1220
|
listening: "In conversation — click to hang up",
|
|
1210
1221
|
speaking: "Hanzo is speaking — talk to interrupt",
|
package/dist/Settings.js
CHANGED
|
@@ -30,7 +30,8 @@ import { tap } from './lib/tap.js';
|
|
|
30
30
|
import { useIam, useIamIdentity, useOrganizations } from '@hanzo/iam/react';
|
|
31
31
|
import { useSignOut } from './lib/signout.js';
|
|
32
32
|
import { useModels } from '@hanzo/ai/react';
|
|
33
|
-
import {
|
|
33
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
34
|
+
import { parseModels } from '@hanzo/ui/models/catalog';
|
|
34
35
|
import { UsagePanel } from '@hanzo/usage/panel';
|
|
35
36
|
import { Catalog } from './Directory.js';
|
|
36
37
|
import { useModel } from './model.js';
|
|
@@ -38,7 +39,7 @@ import { payPage } from './lib/pay.js';
|
|
|
38
39
|
import { planName } from './lib/plans.js';
|
|
39
40
|
import { api } from './lib/api.js';
|
|
40
41
|
import { renewal, useSubscription, useTier } from './lib/tier.js';
|
|
41
|
-
import {
|
|
42
|
+
import { spendShown, useLimits } from './lib/limits.js';
|
|
42
43
|
import { PlanUsage } from '@hanzo/ui/product/PlanUsage';
|
|
43
44
|
import { Look } from './look.js';
|
|
44
45
|
import { ProviderMark } from './ProviderMark.js';
|
|
@@ -169,20 +170,16 @@ function Pane({ id, note }) {
|
|
|
169
170
|
return _jsx(MemorySettings, {});
|
|
170
171
|
return (_jsx(Text, { fontSize: "$3", color: "$soft", children: note ?? 'Nothing to configure here yet.' }));
|
|
171
172
|
}
|
|
172
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* One default in one place; agents may still bring their own model. A premium
|
|
175
|
+
* model is never a default: it is picked per conversation, in the composer, so
|
|
176
|
+
* this picker offers the rest of the catalog.
|
|
177
|
+
*/
|
|
173
178
|
function Model() {
|
|
174
179
|
const { models: served } = useModels();
|
|
175
|
-
|
|
176
|
-
const models = useMemo(() => served.filter((m) => sku(m.id)), [served]);
|
|
180
|
+
const models = useMemo(() => parseModels(served).filter((m) => m.class !== 'premium'), [served]);
|
|
177
181
|
const [model, choose] = useModel();
|
|
178
|
-
return (_jsxs(YStack, { gap: "$3", maxW: 420, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Used for new chats. An agent with its own model keeps that choice." }), _jsx(XStack, { self: "flex-start", children: _jsx(
|
|
179
|
-
.filter((m) => !m.id.includes('openrouter'))
|
|
180
|
-
.slice(0, 8)
|
|
181
|
-
.map((m) => {
|
|
182
|
-
const isSel = model === m.id;
|
|
183
|
-
const p = m.id.startsWith('zen') ? 'zen' : 'enso';
|
|
184
|
-
return (_jsx(Box, { render: "button", onClick: () => choose(m.id), px: "$3", py: "$2", rounded: "$2", borderWidth: 1, borderColor: isSel ? '$ink' : '$borderColor', bg: isSel ? '$raised' : 'transparent', hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsxs(XStack, { items: "center", gap: "$2.5", children: [_jsx(ProviderMark, { provider: p, size: 16 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", flex: 1, children: m.id }), isSel ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "Active" })) : null] }) }, m.id));
|
|
185
|
-
}) })] }));
|
|
182
|
+
return (_jsxs(YStack, { gap: "$3", maxW: 420, children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: "Used for new chats. An agent with its own model keeps that choice. Premium models are picked per conversation in the composer." }), _jsx(XStack, { self: "flex-start", children: _jsx(ModelPicker, { models: models, scope: "chat", value: model, onChange: choose, size: "sm" }) })] }));
|
|
186
183
|
}
|
|
187
184
|
/**
|
|
188
185
|
* WHAT THE GATEWAY SERVES, read from the same `/v1/models` the rooms read.
|
package/dist/Shell.js
CHANGED
|
@@ -36,7 +36,8 @@ import { hasSession, superAdmin } from "./lib/session.js";
|
|
|
36
36
|
import { enter, LOGIN } from "./lib/destination.js";
|
|
37
37
|
import { address, sharedWithMe } from "./lib/share.js";
|
|
38
38
|
import { list } from "./lib/list.js";
|
|
39
|
-
import {
|
|
39
|
+
import { ModelPicker } from "@hanzo/ui/models";
|
|
40
|
+
import { parseModels } from "@hanzo/ui/models/catalog";
|
|
40
41
|
import { mark } from "./lib/mark.js";
|
|
41
42
|
import { useOpen } from "./open.js";
|
|
42
43
|
import { useSpan } from "./span.js";
|
|
@@ -667,6 +668,7 @@ function Bots({ show, owner, filter = "", onPick, navigate, }) {
|
|
|
667
668
|
const [wrong, setWrong] = useState(null);
|
|
668
669
|
const picking = useRef(null);
|
|
669
670
|
const { models } = useModels();
|
|
671
|
+
const listed = useMemo(() => parseModels(models), [models]);
|
|
670
672
|
const allPeopleMap = new Map();
|
|
671
673
|
for (const p of (Array.isArray(storedPeople) ? storedPeople : []))
|
|
672
674
|
if (p?.id)
|
|
@@ -776,7 +778,7 @@ function Bots({ show, owner, filter = "", onPick, navigate, }) {
|
|
|
776
778
|
setEmoji(e.target.value);
|
|
777
779
|
if (e.target.value)
|
|
778
780
|
setAvatar("");
|
|
779
|
-
}, placeholder: "\uD83D\uDE42", "aria-label": "Emoji" }), ...FIELD, width: 44, text: "center", px: 4 }),
|
|
781
|
+
}, placeholder: "\uD83D\uDE42", "aria-label": "Emoji" }), ...FIELD, width: 44, text: "center", px: 4 }), _jsx(YStack, { flex: 1, minW: 0, children: _jsx(ModelPicker, { models: listed, scope: "chat", value: model, onChange: setModel, size: "sm" }) }), _jsx(Box, { render: "button", onClick: () => {
|
|
780
782
|
if (!busy)
|
|
781
783
|
void add();
|
|
782
784
|
}, "aria-disabled": busy || !name.trim(), opacity: busy || !name.trim() ? 0.5 : 1, px: "$2.5", py: "$1.5", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: "$raised" }, children: _jsx(Text, { fontSize: "$1", color: "$ink", children: busy ? "Adding" : "Add" }) })] }), wrong ? (_jsx(Text, { fontSize: "$1", color: BAD, numberOfLines: 2, children: wrong })) : null] })) : null, open ? (_jsxs(YStack, { pl: "$4", gap: "$0.5", children: [broke ? (_jsx(Note, { children: say(broke, "this org's roster") })) : waiting && empty ? (_jsx(Note, { children: "Reading the roster." })) : empty ? (_jsx(Note, { children: show === "agents" ? "No agents yet. Add one with +." : "No one here yet. Invite a teammate." })) : null, folk.map((person) => {
|
package/dist/Vibe.js
CHANGED
|
@@ -26,9 +26,10 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
26
26
|
import { useRooms } from './host.js';
|
|
27
27
|
import { ArrowUp, Globe, Monitor, RefreshCw, Smartphone, Sparkles, Tablet, UserPlus, Users, } from 'lucide-react';
|
|
28
28
|
import { View, XStack, YStack, Text } from '@hanzo/gui';
|
|
29
|
-
import { Button
|
|
29
|
+
import { Button } from '@hanzo/ui';
|
|
30
30
|
import { AiProvider, useAgents, useModels, usePeople } from '@hanzo/ai/react';
|
|
31
|
-
import {
|
|
31
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
32
|
+
import { parseModels } from '@hanzo/ui/models/catalog';
|
|
32
33
|
import { useIam, useOrganizations } from '@hanzo/iam/react';
|
|
33
34
|
import { hasSession } from './lib/session.js';
|
|
34
35
|
import { useHydrated } from './lib/hydrated.js';
|
|
@@ -122,9 +123,9 @@ function VibeWorkspaceInner() {
|
|
|
122
123
|
function Vibe() {
|
|
123
124
|
const { Link } = useRooms();
|
|
124
125
|
const { user } = useIam();
|
|
125
|
-
//
|
|
126
|
-
const { models:
|
|
127
|
-
const models = useMemo(() =>
|
|
126
|
+
// Every model the gateway lists, through the one picker.
|
|
127
|
+
const { models: catalog } = useModels();
|
|
128
|
+
const models = useMemo(() => parseModels(catalog), [catalog]);
|
|
128
129
|
const [activeTabMobile, setActiveTabMobile] = useState('stream');
|
|
129
130
|
// The frame shows what the bar says. It starts empty because a run publishes
|
|
130
131
|
// no URL — the 202 names a session, not an address — so this room has nothing
|
|
@@ -136,7 +137,7 @@ function Vibe() {
|
|
|
136
137
|
// The house name for the lane, resolved against what the gateway serves —
|
|
137
138
|
// `served` falls to a model that answers rather than sending one nobody has.
|
|
138
139
|
const [wanted, setWanted] = useState(user ? ENSO : FREE);
|
|
139
|
-
const model = served(models, wanted);
|
|
140
|
+
const model = models.some((m) => m.id === wanted) ? wanted : served(models, wanted);
|
|
140
141
|
const [busy, setBusy] = useState(false);
|
|
141
142
|
const [turns, setTurns] = useState([]);
|
|
142
143
|
const bottom = useRef(null);
|
|
@@ -168,7 +169,7 @@ function Vibe() {
|
|
|
168
169
|
};
|
|
169
170
|
return (_jsxs(YStack, { "data-vibe": true, flex: 1, minH: 0, height: "100vh", width: "100%", maxW: "100%", overflowX: "auto", overflowY: "hidden", bg: "$background", pb: 56, children: [_jsxs(XStack, { height: 52, items: "center", justify: "space-between", px: "$3.5", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$background", z: 30, shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2.5", children: [_jsx(TopOrgSelector, {}), _jsxs(XStack, { display: "none", $md: { display: 'flex' }, items: "center", gap: "$2", py: 4, px: 10, rounded: 20, bg: "$panel", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(View, { width: 7, height: 7, rounded: 4, bg: busy ? 'var(--state-online)' : '$faint' }), _jsx(Text, { fontSize: 11, color: "$quiet", fontWeight: "500", children: busy ? 'Starting a run' : 'Idle' })] })] }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(XStack, { $md: { display: 'none' }, bg: "$panel", rounded: 8, p: 2, gap: 2, borderWidth: 1, borderColor: "$borderColor", children: ['stream', 'preview', 'team'].map((tab) => (_jsx(Button, { size: "sm", px: "$2", variant: activeTabMobile === tab ? 'secondary' : 'ghost', color: activeTabMobile === tab ? '$ink' : '$soft', "aria-pressed": activeTabMobile === tab, onClick: () => setActiveTabMobile(tab), children: tab === 'stream' ? 'Stream' : tab === 'preview' ? 'Preview' : 'Team' }, tab))) }), _jsx(Button, { asChild: true, variant: "primary", size: "sm", children: _jsxs(Link, { href: invite(), children: [_jsx(UserPlus, { size: 13 }), "Invite"] }) })] })] }), _jsxs(View, { flex: 1, minH: 0, width: "100%", height: "100%", overflow: "hidden", display: "grid", gridTemplateRows: "100%", gridTemplateColumns: "100%", $md: { gridTemplateColumns: 'minmax(320px, 380px) minmax(0, 1fr)' }, $lg: { gridTemplateColumns: 'minmax(340px, 400px) minmax(360px, 1fr) minmax(300px, 340px)' }, $xl: { gridTemplateColumns: 'minmax(380px, 440px) minmax(480px, 1fr) minmax(340px, 380px)' }, children: [_jsxs(YStack, { height: "100%", borderRightWidth: 1, borderColor: "$borderColor", bg: "$background", minW: 0, display: activeTabMobile === 'stream' ? 'flex' : 'none', $md: { display: 'flex' }, children: [_jsxs(XStack, { height: 42, items: "center", gap: "$2", px: "$3.5", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$background", children: [_jsx(Sparkles, { size: 14, color: "var(--foreground)" }), _jsx(Text, { fontSize: 12, fontWeight: "600", color: "$ink", children: "Runs" }), turns.length > 0 && (_jsx(View, { px: 6, py: 2, rounded: 4, bg: "$hover", children: _jsx(Text, { fontSize: 10, color: "$soft", children: turns.length }) }))] }), _jsxs(YStack, { flex: 1, overflowY: "auto", overflowX: "hidden", p: "$3.5", gap: "$4", children: [turns.length === 0 ? (_jsx(Quiet, { children: "Nothing has been sent from this room yet. Describe a task below and the run it opens appears here." })) : null, turns.map((turn) => (_jsxs(YStack, { gap: "$2.5", children: [_jsxs(XStack, { gap: "$2.5", children: [_jsx(View, { width: 24, height: 24, rounded: 12, bg: "$raised", items: "center", justify: "center", shrink: 0, children: _jsx(Text, { fontSize: 10, fontWeight: "700", color: "$ink", children: turn.who.charAt(0).toUpperCase() }) }), _jsxs(YStack, { flex: 1, gap: "$1", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Text, { fontSize: 12, fontWeight: "700", color: "$ink", children: turn.who }), _jsx(Text, { fontSize: 11, color: "$soft", children: turn.time }), _jsx(View, { px: 5, py: 1, rounded: 4, bg: "$hover", children: _jsx(Text, { fontSize: 10, color: "$soft", fontFamily: "$mono", children: turn.model }) })] }), _jsx(Text, { fontSize: 12, color: "$quiet", lineHeight: 18, children: turn.prompt })] })] }), _jsx(YStack, { ml: "$4", gap: "$1.5", children: turn.refused ? (_jsx(Text, { fontSize: 11, color: "$ink", lineHeight: 16, children: turn.refused })) : !turn.run ? (_jsx(Quiet, { children: "Starting." })) : (_jsxs(YStack, { p: "$2", rounded: 8, bg: "$panel", borderWidth: 1, borderColor: "$borderColor", gap: "$1", children: [_jsx(Text, { fontSize: 11, color: "$quiet", fontFamily: "$mono", children: turn.run.sessionId }), _jsxs(Text, { fontSize: 11, color: "$soft", children: [turn.run.routed
|
|
170
171
|
? `Running on ${turn.run.targetId}`
|
|
171
|
-
: 'Running in a sandbox', turn.run.branch ? ` · ${turn.run.branch}` : ''] }), _jsx(Text, { render: _jsx(Link, { href: dev(turn.run.sessionId) }), color: "$ink", fontSize: "$1", children: "Watch it work" })] })) })] }, turn.id))), _jsx("div", { ref: bottom })] }), _jsxs(YStack, { p: "$3", borderTopWidth: 1, borderColor: "$borderColor", bg: "$background", gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [
|
|
172
|
+
: 'Running in a sandbox', turn.run.branch ? ` · ${turn.run.branch}` : ''] }), _jsx(Text, { render: _jsx(Link, { href: dev(turn.run.sessionId) }), color: "$ink", fontSize: "$1", children: "Watch it work" })] })) })] }, turn.id))), _jsx("div", { ref: bottom })] }), _jsxs(YStack, { p: "$3", borderTopWidth: 1, borderColor: "$borderColor", bg: "$background", gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(ModelPicker, { models: models, scope: "chat", value: model, onChange: setWanted, size: "sm", quiet: true }), _jsx(Text, { fontSize: 10, color: "$faint", children: "Press Enter to send" })] }), _jsxs(YStack, { bg: "$panel", borderWidth: 1, borderColor: "var(--border-control)", rounded: 12, overflow: "hidden", children: [_jsx(Text, { render: _jsx("textarea", { value: agentInput, onChange: (e) => setAgentInput(e.target.value), onKeyDown: (e) => {
|
|
172
173
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
173
174
|
e.preventDefault();
|
|
174
175
|
send();
|
package/dist/enso.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ModelCatalogEntry, type PauseSource } from '@hanzo/ui/models';
|
|
1
2
|
/**
|
|
2
3
|
* How hard to think — and, one press inside, what thinks.
|
|
3
4
|
*
|
|
@@ -45,7 +46,7 @@ export type EffortId = (typeof EFFORTS)[number]['id'];
|
|
|
45
46
|
*/
|
|
46
47
|
export declare function useEffort(): readonly [EffortId, (next: EffortId) => void];
|
|
47
48
|
/** The name a served model goes by: its row's label, its name, or its id made readable. */
|
|
48
|
-
export declare function nameOf(models: {
|
|
49
|
+
export declare function nameOf(models: readonly {
|
|
49
50
|
id: string;
|
|
50
51
|
}[], id: string): string;
|
|
51
52
|
export interface EnsoProps {
|
|
@@ -55,9 +56,10 @@ export interface EnsoProps {
|
|
|
55
56
|
/** The override. `models` empty hides it rather than offering an empty menu. */
|
|
56
57
|
model?: string;
|
|
57
58
|
onModel?: (id: string) => void;
|
|
58
|
-
models
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
/** The catalog, as `parseModels` reads `GET /v1/models`: every model is offered. */
|
|
60
|
+
models?: readonly ModelCatalogEntry[];
|
|
61
|
+
/** The payer's plan usage: a paused model says so and still picks. */
|
|
62
|
+
limits?: PauseSource | null;
|
|
61
63
|
disabled?: boolean;
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
@@ -67,4 +69,4 @@ export interface EnsoProps {
|
|
|
67
69
|
* (`ENSO`, or `FREE` on the free route), and the model itself once one is
|
|
68
70
|
* picked. Beside the name it states the effort where the surface sends one.
|
|
69
71
|
*/
|
|
70
|
-
export declare function Enso({ effort, onEffort, model, onModel, models, disabled }: EnsoProps): import("react").JSX.Element;
|
|
72
|
+
export declare function Enso({ effort, onEffort, model, onModel, models, limits, disabled }: EnsoProps): import("react").JSX.Element;
|
package/dist/enso.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
4
|
import { Check, ChevronDown } from 'lucide-react';
|
|
5
5
|
import { Box, Popover, PopoverContent, PopoverTrigger, Separator, Text, XStack, YStack, } from '@hanzo/ui';
|
|
6
|
-
import {
|
|
6
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
7
7
|
import { ProviderMark } from './ProviderMark.js';
|
|
8
8
|
import { ENSO, FREE } from './lib/ai.js';
|
|
9
9
|
import { modelName } from './lib/models.js';
|
|
@@ -90,7 +90,7 @@ export function nameOf(models, id) {
|
|
|
90
90
|
* (`ENSO`, or `FREE` on the free route), and the model itself once one is
|
|
91
91
|
* picked. Beside the name it states the effort where the surface sends one.
|
|
92
92
|
*/
|
|
93
|
-
export function Enso({ effort, onEffort, model, onModel, models = [], disabled }) {
|
|
93
|
+
export function Enso({ effort, onEffort, model, onModel, models = [], limits, disabled }) {
|
|
94
94
|
const [open, setOpen] = useState(false);
|
|
95
95
|
const chosen = EFFORTS.find((e) => e.id === effort);
|
|
96
96
|
const pace = onEffort && chosen ? chosen.label : null;
|
|
@@ -105,7 +105,7 @@ export function Enso({ effort, onEffort, model, onModel, models = [], disabled }
|
|
|
105
105
|
onEffort(e.id);
|
|
106
106
|
setOpen(false);
|
|
107
107
|
}, px: "$2", py: "$2", rounded: "$3", width: "100%", hoverStyle: { bg: '$hover' }, "aria-pressed": e.id === effort, children: _jsxs(XStack, { items: "center", gap: "$2", width: "100%", children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(Text, { fontSize: "$2", color: "$ink", text: "left", children: e.label }), _jsx(Text, { fontSize: "$1", color: "$soft", text: "left", children: e.note })] }), e.id === effort ? _jsx(Check, { size: 14, "aria-hidden": true }) : null] }) }, e.id)))
|
|
108
|
-
: null, onModel && models.length ? (_jsxs(_Fragment, { children: [onEffort ? _jsx(Separator, { my: "$1" }) : null, _jsxs(YStack, { px: "$2", pt: "$1", pb: "$2", gap: "$2", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: "Enso picks the model. Override it for this account:" }), _jsx(
|
|
108
|
+
: null, onModel && models.length ? (_jsxs(_Fragment, { children: [onEffort ? _jsx(Separator, { my: "$1" }) : null, _jsxs(YStack, { px: "$2", pt: "$1", pb: "$2", gap: "$2", children: [_jsx(Text, { fontSize: "$1", color: "$soft", children: "Enso picks the model. Override it for this account:" }), _jsx(ModelPicker, { models: models, limits: limits, value: model, onChange: (id) => {
|
|
109
109
|
onModel(id);
|
|
110
110
|
setOpen(false);
|
|
111
111
|
}, size: "sm" })] })] })) : null] }) })] }));
|
package/dist/lib/limits.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ export declare const useLimits: (enabled?: boolean, org?: string | null, name?:
|
|
|
20
20
|
* plan, or could not be read. A plan holder sees usage as shares, never money.
|
|
21
21
|
*/
|
|
22
22
|
export declare const spendShown: ({ limits, answered }: Pick<UseLimits, 'limits' | 'answered'>) => boolean;
|
|
23
|
-
/** Whether a
|
|
24
|
-
export declare const
|
|
23
|
+
/** Whether a catalog family is one of Hanzo's chat families, Enso or Zen, whose turns carry the live web. */
|
|
24
|
+
export declare const house: (family: string | undefined) => boolean;
|
|
25
25
|
/**
|
|
26
26
|
* Whether a model can answer a chat turn: it names no outputs, or names text
|
|
27
27
|
* among them. `zen-scribe` (a transcript) and `zen-voice-mini` (audio) are
|
package/dist/lib/limits.js
CHANGED
|
@@ -28,8 +28,8 @@ export const useLimits = (enabled = true, org = null, name) => useHeld(enabled ?
|
|
|
28
28
|
* plan, or could not be read. A plan holder sees usage as shares, never money.
|
|
29
29
|
*/
|
|
30
30
|
export const spendShown = ({ limits, answered }) => answered && !limits?.plan;
|
|
31
|
-
/** Whether a
|
|
32
|
-
export const
|
|
31
|
+
/** Whether a catalog family is one of Hanzo's chat families, Enso or Zen, whose turns carry the live web. */
|
|
32
|
+
export const house = (family) => family === 'enso' || family === 'zen';
|
|
33
33
|
/**
|
|
34
34
|
* Whether a model can answer a chat turn: it names no outputs, or names text
|
|
35
35
|
* among them. `zen-scribe` (a transcript) and `zen-voice-mini` (audio) are
|
package/dist/lib/plans.d.ts
CHANGED
|
@@ -150,8 +150,6 @@ export declare function charge(plan: SubscriptionPlan, interval: Interval): numb
|
|
|
150
150
|
export declare function quote(plan: SubscriptionPlan, interval: Interval): string | null;
|
|
151
151
|
/** What a year saves against twelve months, as a whole percent: 1 − year ÷ (12 × month). Null where it saves nothing. */
|
|
152
152
|
export declare function saving(plan: SubscriptionPlan): number | null;
|
|
153
|
-
/** Whether the Free plan runs a model: one its row names, or any `:free` route. */
|
|
154
|
-
export declare function onFree(model: string): boolean;
|
|
155
153
|
/**
|
|
156
154
|
* The catalog's own name for a plan slug — "max-20x" reads as "Max 20x" —
|
|
157
155
|
* or null where the slug is empty or the catalog does not carry it.
|
package/dist/lib/plans.js
CHANGED
|
@@ -222,17 +222,6 @@ export function saving(plan) {
|
|
|
222
222
|
return null;
|
|
223
223
|
return Math.round((1 - year / (12 * month)) * 100);
|
|
224
224
|
}
|
|
225
|
-
/**
|
|
226
|
-
* The models the Free plan runs, as @hanzo/plans states them: `ai.models` on
|
|
227
|
-
* the `free` row. Read on first ask and not at import, so a page that only
|
|
228
|
-
* formats money or names the pay page carries none of the catalogue.
|
|
229
|
-
*/
|
|
230
|
-
let freeModels = null;
|
|
231
|
-
/** Whether the Free plan runs a model: one its row names, or any `:free` route. */
|
|
232
|
-
export function onFree(model) {
|
|
233
|
-
freeModels ??= new Set(subscriptionPlans.find((p) => p.id === "free")?.entitlements?.["ai.models"] ?? []);
|
|
234
|
-
return freeModels.has(model) || model.endsWith(":free");
|
|
235
|
-
}
|
|
236
225
|
/**
|
|
237
226
|
* The catalog's own name for a plan slug — "max-20x" reads as "Max 20x" —
|
|
238
227
|
* or null where the slug is empty or the catalog does not carry it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
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",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@hanzo/iam": ">=0.21.28",
|
|
57
57
|
"@hanzo/logo": ">=1.0.26",
|
|
58
58
|
"@hanzo/plans": ">=1.8.9",
|
|
59
|
-
"@hanzo/ui": ">=8.27.
|
|
59
|
+
"@hanzo/ui": ">=8.27.67",
|
|
60
60
|
"@hanzo/usage": ">=0.1.18",
|
|
61
61
|
"@hanzo/voice": ">=0.1.16",
|
|
62
62
|
"@hanzogui/shell": "^8.3.20",
|
|
@@ -66,16 +66,16 @@
|
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@hanzo/ai": "0.6.20",
|
|
69
|
-
"@hanzo/appearance": "0.2.
|
|
69
|
+
"@hanzo/appearance": "0.2.8",
|
|
70
70
|
"@hanzo/build": "0.2.34",
|
|
71
71
|
"@hanzo/composer": "0.2.0",
|
|
72
|
-
"@hanzo/design": "0.5.
|
|
72
|
+
"@hanzo/design": "0.5.34",
|
|
73
73
|
"@hanzo/event": "0.3.56",
|
|
74
74
|
"@hanzo/gui": "8.3.5",
|
|
75
75
|
"@hanzo/iam": "0.21.28",
|
|
76
76
|
"@hanzo/logo": "1.0.26",
|
|
77
77
|
"@hanzo/plans": "1.8.9",
|
|
78
|
-
"@hanzo/ui": "8.27.
|
|
78
|
+
"@hanzo/ui": "8.27.67",
|
|
79
79
|
"@hanzo/usage": "0.1.18",
|
|
80
80
|
"@hanzo/voice": "0.1.16",
|
|
81
81
|
"@hanzogui/config": "8.3.5",
|