@alexkroman1/aai-ui 1.8.2 → 1.9.0

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.
Files changed (62) hide show
  1. package/dist/_colors-DJUordGv.js +19 -0
  2. package/dist/_utils-5cs73OrA.js +16 -0
  3. package/dist/_utils.d.ts +4 -0
  4. package/dist/aai-logo-B8lDmsut.js +84 -0
  5. package/dist/audio.d.ts +9 -2
  6. package/dist/audio.js +41 -24
  7. package/dist/chat-view-C1XbqDk0.js +186 -0
  8. package/dist/components/_colors.d.ts +26 -0
  9. package/dist/components/aai-logo.d.ts +7 -6
  10. package/dist/components/button.d.ts +11 -13
  11. package/dist/components/button.js +7 -16
  12. package/dist/components/chat-view.d.ts +8 -19
  13. package/dist/components/chat-view.js +2 -98
  14. package/dist/components/controls.d.ts +1 -1
  15. package/dist/components/controls.js +2 -39
  16. package/dist/components/eyebrow.d.ts +12 -0
  17. package/dist/components/message-list.d.ts +1 -1
  18. package/dist/components/message-list.js +126 -56
  19. package/dist/components/sidebar-layout.d.ts +1 -1
  20. package/dist/components/start-screen.d.ts +4 -3
  21. package/dist/components/start-screen.js +20 -14
  22. package/dist/components/text-controls.d.ts +14 -0
  23. package/dist/components/tool-call-block.d.ts +5 -3
  24. package/dist/components/tool-call-block.js +1 -1
  25. package/dist/components/url-chips.d.ts +28 -0
  26. package/dist/context.d.ts +30 -4
  27. package/dist/context.js +86 -10
  28. package/dist/controls-BngrPbOC.js +135 -0
  29. package/dist/default-client/assets/audio-Cs-6t_Wd.js +1 -0
  30. package/dist/default-client/assets/capture-processor-C19oBn4L.js +105 -0
  31. package/dist/default-client/assets/index-Bf4ZTNcx.js +73 -0
  32. package/dist/default-client/assets/index-Bzlh9i7w.css +2 -0
  33. package/dist/default-client/assets/playback-processor-BtlzAH78.js +149 -0
  34. package/dist/default-client/index.html +3 -3
  35. package/dist/define-client.d.ts +2 -2
  36. package/dist/define-client.js +18 -24
  37. package/dist/eyebrow-C6ZFuiz6.js +27 -0
  38. package/dist/hooks.js +71 -42
  39. package/dist/index.d.ts +3 -1
  40. package/dist/index.js +6 -5
  41. package/dist/session-core-D3NDaySY.js +652 -0
  42. package/dist/session-core-messages.d.ts +50 -0
  43. package/dist/session-core-types.d.ts +147 -0
  44. package/dist/session-core-upload.d.ts +16 -0
  45. package/dist/session-core.d.ts +2 -68
  46. package/dist/session-core.js +1 -455
  47. package/dist/{tool-call-block-wby_jyoY.js → tool-call-block-7f1GTG-P.js} +33 -31
  48. package/dist/types.d.ts +29 -4
  49. package/dist/types.js +10 -1
  50. package/dist/worklets/capture-processor.d.ts +2 -0
  51. package/dist/worklets/capture-processor.js +80 -25
  52. package/dist/worklets/playback-processor.d.ts +2 -0
  53. package/dist/worklets/playback-processor.js +80 -29
  54. package/package.json +18 -18
  55. package/styles.css +30 -0
  56. package/dist/_react-test-utils.d.ts +0 -86
  57. package/dist/aai-logo-BqFv6JU6.js +0 -21
  58. package/dist/default-client/assets/audio-UowwhmYo.js +0 -1
  59. package/dist/default-client/assets/capture-processor-C26lSiVr.js +0 -53
  60. package/dist/default-client/assets/index-BZqVGR2o.css +0 -2
  61. package/dist/default-client/assets/index-YW9WUhbL.js +0 -49
  62. package/dist/default-client/assets/playback-processor-dSA8Im99.js +0 -101
@@ -1,99 +1,3 @@
1
- import { useSession, useTheme } from "../context.js";
2
- import { t as AaiLogo } from "../aai-logo-BqFv6JU6.js";
3
- import { Controls } from "./controls.js";
4
- import { MessageList } from "./message-list.js";
5
- import clsx from "clsx";
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
- //#region components/chat-view.tsx
8
- /** @jsxImportSource react */
9
- const STATE_COLORS = {
10
- disconnected: "rgba(255,255,255,0.422)",
11
- connecting: "rgba(255,255,255,0.422)",
12
- ready: "#7fd88f",
13
- listening: "#56b6c2",
14
- thinking: "#f5a742",
15
- speaking: "#e06c75",
16
- error: "#e06c75"
17
- };
18
- /**
19
- * The main chat interface for a voice agent session.
20
- * Displays a header (with optional icon, title, and state indicator), an
21
- * inline error banner, the {@link MessageList}, and session {@link Controls}.
22
- *
23
- * Must be rendered inside a {@link SessionProvider}.
24
- *
25
- * @example
26
- * ```tsx
27
- * <StartScreen icon="🍕" title="Pizza Palace">
28
- * <ChatView />
29
- * </StartScreen>
30
- * ```
31
- *
32
- * @example Pair with a sidebar
33
- * ```tsx
34
- * <SidebarLayout sidebar={<RecipeCard />}>
35
- * <ChatView className="border-l" />
36
- * </SidebarLayout>
37
- * ```
38
- *
39
- * @example Custom header icon
40
- * ```tsx
41
- * <ChatView icon={<img src="/logo.svg" />} title="My Agent" />
42
- * ```
43
- *
44
- * @param icon - Optional element rendered before the title in the header.
45
- * @param title - Optional title string for the header.
46
- * @param className - Additional CSS class names applied to the root element.
47
- *
48
- * @public
49
- */
50
- function ChatView({ icon, title, className }) {
51
- const session = useSession();
52
- const theme = useTheme();
53
- return /* @__PURE__ */ jsxs("div", {
54
- className: clsx("flex flex-col h-screen max-w-130 mx-auto font-aai text-sm", className),
55
- style: {
56
- background: theme.bg,
57
- color: theme.text
58
- },
59
- children: [
60
- /* @__PURE__ */ jsxs("div", {
61
- className: "flex items-center gap-3 px-4 py-3 border-b shrink-0",
62
- style: { borderColor: theme.border },
63
- children: [
64
- icon,
65
- title ? /* @__PURE__ */ jsx("span", {
66
- className: "text-sm font-semibold",
67
- style: { color: theme.primary },
68
- children: title
69
- }) : !icon && /* @__PURE__ */ jsx(AaiLogo, {}),
70
- /* @__PURE__ */ jsx("div", {
71
- className: "ml-auto",
72
- children: /* @__PURE__ */ jsxs("div", {
73
- className: "inline-flex items-center justify-center gap-1.5 text-[13px] font-medium leading-[130%] capitalize",
74
- style: { color: "rgba(255,255,255,0.284)" },
75
- "data-state": session.state,
76
- children: [/* @__PURE__ */ jsx("div", {
77
- className: "w-2 h-2 rounded-full",
78
- style: { background: STATE_COLORS[session.state] }
79
- }), session.state]
80
- })
81
- })
82
- ]
83
- }),
84
- session.error && /* @__PURE__ */ jsx("div", {
85
- className: "mx-4 mt-3 px-3 py-2 rounded-aai border text-[13px] leading-[130%]",
86
- style: {
87
- borderColor: "rgba(224,108,117,0.4)",
88
- background: "rgba(224,108,117,0.08)",
89
- color: "#e06c75"
90
- },
91
- children: session.error.message
92
- }),
93
- /* @__PURE__ */ jsx(MessageList, {}),
94
- /* @__PURE__ */ jsx(Controls, {})
95
- ]
96
- });
97
- }
98
- //#endregion
1
+ import "../context.js";
2
+ import { t as ChatView } from "../chat-view-C1XbqDk0.js";
99
3
  export { ChatView };
@@ -15,4 +15,4 @@
15
15
  */
16
16
  export declare function Controls({ className }: {
17
17
  className?: string;
18
- }): import("react/jsx-runtime").JSX.Element;
18
+ }): import("react").JSX.Element;
@@ -1,40 +1,3 @@
1
- import { useSession, useTheme } from "../context.js";
2
- import { Button } from "./button.js";
3
- import clsx from "clsx";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
- //#region components/controls.tsx
6
- /** @jsxImportSource react */
7
- /**
8
- * Session control buttons: **Stop / Resume** and **New Conversation**.
9
- *
10
- * Reads session state from {@link useSession}. Must be rendered inside a
11
- * {@link SessionProvider}.
12
- *
13
- * @example
14
- * ```tsx
15
- * <Controls className="justify-end" />
16
- * ```
17
- *
18
- * @param className - Additional CSS class names applied to the container.
19
- *
20
- * @public
21
- */
22
- function Controls({ className }) {
23
- const { running, toggle, reset } = useSession();
24
- const theme = useTheme();
25
- return /* @__PURE__ */ jsxs("div", {
26
- className: clsx("flex gap-2 px-4 py-3 border-t shrink-0", className),
27
- style: { borderColor: theme.border },
28
- children: [/* @__PURE__ */ jsx(Button, {
29
- variant: "secondary",
30
- onClick: toggle,
31
- children: running ? "Stop" : "Resume"
32
- }), /* @__PURE__ */ jsx(Button, {
33
- variant: "ghost",
34
- onClick: reset,
35
- children: "New Conversation"
36
- })]
37
- });
38
- }
39
- //#endregion
1
+ import "../context.js";
2
+ import { t as Controls } from "../controls-BngrPbOC.js";
40
3
  export { Controls };
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * Eyebrow label — the design system's small outlined pill with ALL-CAPS,
4
+ * tracked text ("VOICE AGENT", "LISTENING", …). Used as a section label on
5
+ * the start screen and as the live status chip in the chat header.
6
+ *
7
+ * @internal
8
+ */
9
+ export declare function Eyebrow({ children, className, style, ...rest }: {
10
+ children: ReactNode;
11
+ className?: string | undefined;
12
+ } & Omit<React.HTMLAttributes<HTMLSpanElement>, "className">): ReactNode;
@@ -18,4 +18,4 @@
18
18
  */
19
19
  export declare function MessageList({ className }: {
20
20
  className?: string;
21
- }): import("react/jsx-runtime").JSX.Element;
21
+ }): import("react").JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { useSession, useTheme } from "../context.js";
2
- import { t as ToolCallBlock } from "../tool-call-block-wby_jyoY.js";
2
+ import { a as primaryTint, i as TEXT_MUTED, r as TEXT_FAINT } from "../_colors-DJUordGv.js";
3
+ import { t as ToolCallBlock } from "../tool-call-block-7f1GTG-P.js";
3
4
  import clsx from "clsx";
4
5
  import { useEffect, useMemo, useRef } from "react";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -17,47 +18,143 @@ const DOT_STYLES = [
17
18
  function ThinkingDots() {
18
19
  return /* @__PURE__ */ jsx("div", {
19
20
  className: "flex items-center gap-2 text-sm font-medium min-h-5",
20
- style: { color: "rgba(255,255,255,0.422)" },
21
+ style: { color: TEXT_MUTED },
21
22
  children: DOT_STYLES.map((style, i) => /* @__PURE__ */ jsx("div", {
22
23
  className: "w-1.5 h-1.5 rounded-full",
23
24
  style: {
24
25
  ...style,
25
- background: "rgba(255,255,255,0.422)"
26
+ background: TEXT_MUTED
26
27
  }
27
28
  }, i))
28
29
  });
29
30
  }
30
- /** Renders a single chat message as a styled bubble. */
31
- function MessageBubble({ message, theme }) {
32
- if (message.role === "user") return /* @__PURE__ */ jsx("div", {
31
+ /**
32
+ * Right-aligned user bubble — a primary-tinted card (the design system's
33
+ * indigo-50 fill with an indigo-100 edge, derived from the theme primary so
34
+ * custom themes stay coherent). Shared by finalized messages and the live
35
+ * transcript.
36
+ */
37
+ function UserBubble({ theme, color, children }) {
38
+ return /* @__PURE__ */ jsx("div", {
33
39
  className: "flex flex-col w-full items-end",
34
40
  children: /* @__PURE__ */ jsx("div", {
35
- className: "max-w-[min(82%,64ch)] border px-3 py-2 rounded-aai whitespace-pre-wrap wrap-break-word text-sm font-normal leading-[150%]",
41
+ className: "max-w-[min(78%,64ch)] border px-3.5 py-2.5 rounded-md whitespace-pre-wrap wrap-break-word text-[15px] font-normal leading-[22px]",
36
42
  style: {
37
- background: "rgba(255,255,255,0.031)",
38
- borderColor: theme.border,
39
- color: theme.text
43
+ background: primaryTint(theme.primary, theme.surface, 7),
44
+ borderColor: primaryTint(theme.primary, theme.surface, 16),
45
+ color
40
46
  },
41
- children: message.content
47
+ children
42
48
  })
43
49
  });
44
- return /* @__PURE__ */ jsx("div", {
45
- className: "whitespace-pre-wrap wrap-break-word text-sm font-normal leading-[150%]",
46
- style: { color: theme.text },
50
+ }
51
+ /** Renders a single chat message: labeled agent prose, or a user bubble. */
52
+ function MessageBubble({ message, theme }) {
53
+ if (message.role === "user") return /* @__PURE__ */ jsx(UserBubble, {
54
+ theme,
55
+ color: theme.text,
47
56
  children: message.content
48
57
  });
58
+ return /* @__PURE__ */ jsxs("div", {
59
+ className: "flex flex-col gap-1 max-w-[82%]",
60
+ children: [/* @__PURE__ */ jsx("span", {
61
+ className: "text-[10px] font-medium tracking-[1.2px] uppercase leading-none",
62
+ style: { color: TEXT_FAINT },
63
+ children: "Agent"
64
+ }), /* @__PURE__ */ jsx("div", {
65
+ className: "whitespace-pre-wrap wrap-break-word text-[15px] font-normal leading-[23px]",
66
+ style: { color: theme.text },
67
+ children: message.content
68
+ })]
69
+ });
49
70
  }
50
- function useAutoScroll() {
71
+ /**
72
+ * Smooth-scroll to the anchor whenever the content version advances.
73
+ *
74
+ * The scroll runs inside `requestAnimationFrame` and is deduped per frame:
75
+ * several snapshot updates in one frame (transcript + message + tool call)
76
+ * trigger a single scroll after layout instead of one forced layout each.
77
+ */
78
+ function useAutoScroll(contentVersion) {
51
79
  const ref = useRef(null);
80
+ const scheduledRef = useRef(false);
52
81
  useEffect(() => {
53
- ref.current?.scrollIntoView({
54
- behavior: "smooth",
55
- block: "end"
82
+ if (contentVersion === 0 || scheduledRef.current) return;
83
+ scheduledRef.current = true;
84
+ requestAnimationFrame(() => {
85
+ scheduledRef.current = false;
86
+ ref.current?.scrollIntoView({
87
+ behavior: "smooth",
88
+ block: "end"
89
+ });
56
90
  });
57
- });
91
+ }, [contentVersion]);
58
92
  return ref;
59
93
  }
60
94
  /**
95
+ * Interleave messages and tool calls into render items, ordered by insertion
96
+ * time. Each tool call renders immediately after its anchor message
97
+ * (`afterMessageId`); tool calls whose anchor slid out of the retained window
98
+ * (or that were inserted before any message existed) render first.
99
+ */
100
+ function interleave(messages, toolCalls, renderMessage, renderToolCall) {
101
+ const items = [];
102
+ let tci = 0;
103
+ const pushToolCallsThrough = (maxAfterId) => {
104
+ let tc = toolCalls[tci];
105
+ while (tc && tc.afterMessageId <= maxAfterId) {
106
+ items.push(renderToolCall(tc));
107
+ tci++;
108
+ tc = toolCalls[tci];
109
+ }
110
+ };
111
+ const firstMessage = messages[0];
112
+ if (firstMessage) pushToolCallsThrough(firstMessage.id - 1);
113
+ for (const msg of messages) {
114
+ items.push(renderMessage(msg));
115
+ pushToolCallsThrough(msg.id);
116
+ }
117
+ pushToolCallsThrough(Number.POSITIVE_INFINITY);
118
+ return items;
119
+ }
120
+ /**
121
+ * Build the interleaved row elements, reusing each row's element object across
122
+ * renders while its inputs are unchanged. Returning the identical element
123
+ * reference lets React bail out of re-rendering that row entirely — the same
124
+ * effect as wrapping the row components in `memo()` — so appending one message
125
+ * re-renders one row, not the whole capped list. Rows are keyed on stable ids
126
+ * (`ChatMessage.id`, `ToolCallInfo.callId`), which survive the sliding
127
+ * 200-message window; message and tool-call objects are referentially stable
128
+ * across snapshots, making the identity checks below sufficient.
129
+ */
130
+ function useChatItems(messages, toolCalls, theme) {
131
+ const cacheRef = useRef(/* @__PURE__ */ new Map());
132
+ return useMemo(() => {
133
+ const prev = cacheRef.current;
134
+ const next = /* @__PURE__ */ new Map();
135
+ const rowFor = (key, data, make) => {
136
+ const hit = prev.get(key);
137
+ const element = hit && hit.data === data && hit.theme === theme ? hit.element : make();
138
+ next.set(key, {
139
+ data,
140
+ theme,
141
+ element
142
+ });
143
+ return element;
144
+ };
145
+ const items = interleave(messages, toolCalls, (msg) => rowFor(`m${msg.id}`, msg, () => /* @__PURE__ */ jsx(MessageBubble, {
146
+ message: msg,
147
+ theme
148
+ }, msg.id)), (tc) => rowFor(`t${tc.callId}`, tc, () => /* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.callId)));
149
+ cacheRef.current = next;
150
+ return items;
151
+ }, [
152
+ messages,
153
+ toolCalls,
154
+ theme
155
+ ]);
156
+ }
157
+ /**
61
158
  * Scrollable list of all chat messages, tool-call blocks, live transcript,
62
159
  * streaming agent utterance, and a thinking indicator.
63
160
  *
@@ -78,7 +175,6 @@ function useAutoScroll() {
78
175
  function MessageList({ className }) {
79
176
  const session = useSession();
80
177
  const theme = useTheme();
81
- const scrollRef = useAutoScroll();
82
178
  const showThinking = useMemo(() => {
83
179
  if (session.state !== "thinking") return false;
84
180
  const last = session.toolCalls.at(-1);
@@ -90,54 +186,28 @@ function MessageList({ className }) {
90
186
  session.toolCalls,
91
187
  session.messages
92
188
  ]);
93
- const { messages, toolCalls } = session;
94
- const items = [];
95
- let tci = 0;
96
- for (const [i, msg] of messages.entries()) {
97
- items.push(/* @__PURE__ */ jsx(MessageBubble, {
98
- message: msg,
99
- theme
100
- }, `msg-${i}`));
101
- let tc = toolCalls[tci];
102
- while (tc && tc.afterMessageIndex <= i) {
103
- items.push(/* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.callId));
104
- tci++;
105
- tc = toolCalls[tci];
106
- }
107
- }
108
- let tc = toolCalls[tci];
109
- while (tc) {
110
- items.push(/* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.callId));
111
- tci++;
112
- tc = toolCalls[tci];
113
- }
114
- const userTranscript = session.userTranscript;
189
+ const { messages, toolCalls, userTranscript, agentTranscript } = session;
190
+ const scrollRef = useAutoScroll(session.contentVersion);
191
+ const items = useChatItems(messages, toolCalls, theme);
115
192
  return /* @__PURE__ */ jsx("div", {
116
193
  role: "log",
117
194
  className: clsx("flex-1 overflow-y-auto [scrollbar-width:none]", className),
118
195
  style: { background: theme.surface },
119
196
  children: /* @__PURE__ */ jsxs("div", {
120
- className: "flex flex-col gap-4.5 p-4",
197
+ className: "flex flex-col gap-4 p-7",
121
198
  children: [
122
199
  items,
123
- session.agentTranscript && /* @__PURE__ */ jsx(MessageBubble, {
200
+ agentTranscript && /* @__PURE__ */ jsx(MessageBubble, {
124
201
  message: {
125
202
  role: "assistant",
126
- content: session.agentTranscript
203
+ content: agentTranscript
127
204
  },
128
205
  theme
129
206
  }),
130
- userTranscript !== null && /* @__PURE__ */ jsx("div", {
131
- className: "flex flex-col items-end w-full",
132
- children: /* @__PURE__ */ jsx("div", {
133
- className: "max-w-[min(82%,64ch)] whitespace-pre-wrap wrap-break-word text-sm leading-[150%] border px-3 py-2 rounded-aai ml-auto",
134
- style: {
135
- color: "rgba(255,255,255,0.284)",
136
- background: "rgba(255,255,255,0.031)",
137
- borderColor: theme.border
138
- },
139
- children: userTranscript || /* @__PURE__ */ jsx(ThinkingDots, {})
140
- })
207
+ userTranscript !== null && /* @__PURE__ */ jsx(UserBubble, {
208
+ theme,
209
+ color: "#6F6A60",
210
+ children: userTranscript ? userTranscript : /* @__PURE__ */ jsx(ThinkingDots, {})
141
211
  }),
142
212
  showThinking && /* @__PURE__ */ jsx(ThinkingDots, {}),
143
213
  /* @__PURE__ */ jsx("div", { ref: scrollRef })
@@ -18,4 +18,4 @@ export declare function SidebarLayout({ sidebar, children, sidebarWidth, sidebar
18
18
  sidebarWidth?: string | undefined;
19
19
  sidebarPosition?: "left" | "right" | undefined;
20
20
  className?: string;
21
- }): import("react/jsx-runtime").JSX.Element;
21
+ }): import("react").JSX.Element;
@@ -1,7 +1,8 @@
1
1
  import type { ReactNode } from "react";
2
2
  /**
3
- * A centered start screen with icon, title, subtitle, and a start button.
4
- * Renders `children` (the main app) once the session has started.
3
+ * A centered start screen: a white card on the cream page with the logo, an
4
+ * eyebrow label, a serif title, subtitle, and the start CTA. Renders
5
+ * `children` (the main app) once the session has started.
5
6
  *
6
7
  * @example
7
8
  * ```tsx
@@ -23,4 +24,4 @@ export declare function StartScreen({ children, icon, title, subtitle, buttonTex
23
24
  subtitle?: string | undefined;
24
25
  buttonText?: string | undefined;
25
26
  className?: string | undefined;
26
- }): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
27
+ }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
@@ -1,13 +1,16 @@
1
- import { useSession, useTheme } from "../context.js";
1
+ import { useSessionCore, useSessionSelector, useTheme } from "../context.js";
2
2
  import { Button } from "./button.js";
3
- import { t as AaiLogo } from "../aai-logo-BqFv6JU6.js";
3
+ import "../_colors-DJUordGv.js";
4
+ import { t as AaiLogo } from "../aai-logo-B8lDmsut.js";
5
+ import { t as Eyebrow } from "../eyebrow-C6ZFuiz6.js";
4
6
  import clsx from "clsx";
5
7
  import { jsx, jsxs } from "react/jsx-runtime";
6
8
  //#region components/start-screen.tsx
7
9
  /** @jsxImportSource react */
8
10
  /**
9
- * A centered start screen with icon, title, subtitle, and a start button.
10
- * Renders `children` (the main app) once the session has started.
11
+ * A centered start screen: a white card on the cream page with the logo, an
12
+ * eyebrow label, a serif title, subtitle, and the start CTA. Renders
13
+ * `children` (the main app) once the session has started.
11
14
  *
12
15
  * @example
13
16
  * ```tsx
@@ -22,34 +25,37 @@ import { jsx, jsxs } from "react/jsx-runtime";
22
25
  *
23
26
  * @public
24
27
  */
25
- function StartScreen({ children, icon, title, subtitle, buttonText = "Start", className }) {
26
- const { started, start } = useSession();
28
+ function StartScreen({ children, icon, title, subtitle, buttonText = "Start Conversation", className }) {
29
+ const started = useSessionSelector((s) => s.started);
30
+ const { start } = useSessionCore();
27
31
  const theme = useTheme();
28
32
  if (started) return children;
29
33
  return /* @__PURE__ */ jsx("div", {
30
34
  className: clsx("flex items-center justify-center h-screen font-aai", className),
31
35
  style: { background: theme.bg },
32
36
  children: /* @__PURE__ */ jsxs("div", {
33
- className: "flex flex-col items-center gap-4 border rounded-lg px-12 py-10 max-w-sm text-center",
37
+ className: "flex flex-col items-center gap-5 border rounded-xl px-10 py-12 sm:px-16 sm:py-14 max-w-105 text-center",
34
38
  style: {
35
39
  background: theme.surface,
36
- borderColor: theme.border
40
+ borderColor: theme.border,
41
+ boxShadow: "0 4px 12px -2px rgb(20 18 12 / 0.08)"
37
42
  },
38
43
  children: [
39
- icon ?? (!title && /* @__PURE__ */ jsx(AaiLogo, { size: 20 })),
44
+ icon ?? /* @__PURE__ */ jsx(AaiLogo, { size: 24 }),
45
+ /* @__PURE__ */ jsx(Eyebrow, { children: "Voice Agent" }),
40
46
  title && /* @__PURE__ */ jsx("h1", {
41
- className: "font-semibold m-0",
42
- style: { color: theme.primary },
47
+ className: "font-aai-serif font-normal text-[32px] leading-[1.15] tracking-[-0.2px] m-0 text-balance",
48
+ style: { color: theme.text },
43
49
  children: title
44
50
  }),
45
51
  subtitle && /* @__PURE__ */ jsx("p", {
46
- className: "text-sm m-0",
47
- style: { color: "rgba(255,255,255,0.284)" },
52
+ className: "text-[15px] leading-[22px] m-0 max-w-75",
53
+ style: { color: "#57534B" },
48
54
  children: subtitle
49
55
  }),
50
56
  /* @__PURE__ */ jsx(Button, {
51
57
  size: "lg",
52
- className: "mt-2 w-full",
58
+ className: "mt-2",
53
59
  onClick: start,
54
60
  children: buttonText
55
61
  })
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Controls for a text-only session (`tts: none()`): a **record** toggle that
3
+ * streams the microphone while active, an **upload** button that transcribes
4
+ * an audio file, and **New Conversation**. Replies arrive as text in the
5
+ * {@link MessageList} — there is no playback side.
6
+ *
7
+ * Rendered automatically by {@link ChatView} when the server declares the
8
+ * session text-only; custom clients can compose it directly.
9
+ *
10
+ * @public
11
+ */
12
+ export declare function TextControls({ className }: {
13
+ className?: string | undefined;
14
+ }): import("react").JSX.Element;
@@ -1,14 +1,16 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { ToolCallInfo } from "../types.ts";
3
3
  /**
4
- * Renders a tool invocation with an optional icon/emoji, title, subtitle, and a
5
- * collapsible result viewer.
4
+ * Renders a tool invocation as the design system's console row: a small
5
+ * outlined "TOOL" chip (or the tool's configured icon), the tool name in
6
+ * mono, a truncated args preview, and a rotating chevron that expands the
7
+ * formatted result.
6
8
  *
7
9
  * Tool display is configured via `ToolConfigContext`. If no config is found
8
10
  * for a tool name, the raw tool name is shown as the title.
9
11
  *
10
12
  * While the tool call is pending a shimmer animation is shown. Once
11
- * complete, clicking the block expands the formatted JSON result.
13
+ * complete, clicking the row expands the formatted JSON result.
12
14
  *
13
15
  * @example
14
16
  * ```tsx
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as ToolCallBlock } from "../tool-call-block-wby_jyoY.js";
2
+ import { t as ToolCallBlock } from "../tool-call-block-7f1GTG-P.js";
3
3
  export { ToolCallBlock };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The session's shareable UI URL.
3
+ *
4
+ * @public
5
+ */
6
+ export declare function UiUrlChip({ className }: {
7
+ className?: string | undefined;
8
+ }): import("react").JSX.Element;
9
+ /**
10
+ * The session's programmatic WebSocket endpoint — the URL a script or backend
11
+ * can connect to directly instead of using this UI.
12
+ *
13
+ * @public
14
+ */
15
+ export declare function ApiUrlChip({ className }: {
16
+ className?: string | undefined;
17
+ }): import("react").JSX.Element;
18
+ /**
19
+ * The UI and API URLs side by side. They answer the same question — "how do I
20
+ * reach this agent?" — so they belong together and each needs its label to be
21
+ * told apart. Rendered by the default shell in every session mode (S2S,
22
+ * pipeline, text-only).
23
+ *
24
+ * @public
25
+ */
26
+ export declare function SessionUrlChips({ className }: {
27
+ className?: string | undefined;
28
+ }): import("react").JSX.Element;
package/dist/context.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { SessionCore, SessionSnapshot } from "./session-core.ts";
3
3
  import type { ClientTheme } from "./types.ts";
4
- declare const DEFAULT_THEME: Required<ClientTheme>;
5
4
  export declare function SessionProvider({ value, children }: {
6
5
  value: SessionCore;
7
6
  children?: ReactNode;
@@ -13,11 +12,38 @@ export type Session = SessionSnapshot & {
13
12
  reset(): void;
14
13
  disconnect(): void;
15
14
  toggle(): void;
15
+ startRecording(): void;
16
+ stopRecording(): void;
17
+ sendAudioFile(file: Blob): Promise<void>;
16
18
  };
19
+ /**
20
+ * Return the raw {@link SessionCore} from context without subscribing to
21
+ * snapshot changes. Useful for accessing stable methods (`start`, `toggle`,
22
+ * `reset`, …) from components that select narrow state via
23
+ * {@link useSessionSelector}.
24
+ *
25
+ * Not part of the package's public export surface — internal to aai-ui
26
+ * components.
27
+ */
28
+ export declare function useSessionCore(): SessionCore;
17
29
  export declare function useSession(): Session;
18
- export declare function ThemeProvider({ value, children }: {
19
- value?: ClientTheme;
30
+ /**
31
+ * Subscribe to a narrow slice of the session snapshot.
32
+ *
33
+ * Unlike {@link useSession} — which re-renders the component on *every*
34
+ * snapshot change — this only triggers a re-render when the selected value
35
+ * changes (per `isEqual`, default `Object.is`). Use it for components that
36
+ * read a single field, e.g. `useSessionSelector((s) => s.running)`.
37
+ *
38
+ * The selector must be pure. It may run on every snapshot change, so keep it
39
+ * cheap; when it returns a derived object, pass a custom `isEqual` to avoid
40
+ * re-renders on referentially-new-but-equal results.
41
+ *
42
+ * @public
43
+ */
44
+ export declare function useSessionSelector<T>(selector: (snapshot: SessionSnapshot) => T, isEqual?: (a: T, b: T) => boolean): T;
45
+ export declare function ThemeProvider({ value, children, }: {
46
+ value?: ClientTheme | undefined;
20
47
  children?: ReactNode;
21
48
  }): import("react").FunctionComponentElement<import("react").ProviderProps<Required<ClientTheme>>>;
22
49
  export declare function useTheme(): Required<ClientTheme>;
23
- export { DEFAULT_THEME };