@alexkroman1/aai-ui 0.12.2 → 1.0.2

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 (68) hide show
  1. package/dist/_react-test-utils.d.ts +86 -0
  2. package/dist/audio.js +4 -5
  3. package/dist/build-default-client.d.ts +1 -0
  4. package/dist/{_components → components}/button.d.ts +4 -4
  5. package/dist/{_components → components}/button.js +28 -5
  6. package/dist/components/chat-view.d.ts +38 -0
  7. package/dist/components/chat-view.js +102 -0
  8. package/dist/{_components → components}/controls.d.ts +1 -1
  9. package/dist/{_components → components}/controls.js +8 -5
  10. package/dist/{_components → components}/message-list.d.ts +2 -2
  11. package/dist/components/message-list.js +149 -0
  12. package/dist/components/sidebar-layout.d.ts +21 -0
  13. package/dist/components/sidebar-layout.js +43 -0
  14. package/dist/{_components → components}/start-screen.d.ts +4 -4
  15. package/dist/{_components → components}/start-screen.js +17 -8
  16. package/dist/{_components → components}/tool-call-block.d.ts +5 -6
  17. package/dist/components/tool-call-block.js +3 -0
  18. package/dist/components/tool-config-context.d.ts +22 -0
  19. package/dist/context.d.ts +23 -0
  20. package/dist/context.js +39 -0
  21. package/dist/default-client/assets/audio-DH9LpexG.js +1 -0
  22. package/dist/default-client/assets/capture-processor-C26lSiVr.js +53 -0
  23. package/dist/default-client/assets/default-client-AeJhjHfj.js +49 -0
  24. package/dist/default-client/assets/default-client-BK1ItCvw.css +2 -0
  25. package/dist/default-client/assets/playback-processor-dSA8Im99.js +101 -0
  26. package/dist/default-client/default-client.html +15 -0
  27. package/dist/default-client.d.ts +2 -0
  28. package/dist/define-client.d.ts +72 -24
  29. package/dist/define-client.js +71 -45
  30. package/dist/hooks.d.ts +5 -0
  31. package/dist/hooks.js +55 -0
  32. package/dist/index.d.ts +15 -37
  33. package/dist/index.js +11 -18
  34. package/dist/session-core.d.ts +70 -0
  35. package/dist/session-core.js +437 -0
  36. package/dist/tool-call-block-CCuChsFm.js +131 -0
  37. package/dist/types.d.ts +19 -22
  38. package/package.json +24 -18
  39. package/styles.css +3 -33
  40. package/dist/_components/app.d.ts +0 -23
  41. package/dist/_components/app.js +0 -41
  42. package/dist/_components/chat-view.d.ts +0 -29
  43. package/dist/_components/chat-view.js +0 -61
  44. package/dist/_components/error-banner.d.ts +0 -21
  45. package/dist/_components/error-banner.js +0 -27
  46. package/dist/_components/message-bubble.d.ts +0 -23
  47. package/dist/_components/message-bubble.js +0 -35
  48. package/dist/_components/message-list.js +0 -76
  49. package/dist/_components/sidebar-layout.d.ts +0 -21
  50. package/dist/_components/sidebar-layout.js +0 -36
  51. package/dist/_components/state-indicator.d.ts +0 -29
  52. package/dist/_components/state-indicator.js +0 -37
  53. package/dist/_components/thinking-indicator.d.ts +0 -16
  54. package/dist/_components/thinking-indicator.js +0 -34
  55. package/dist/_components/tool-call-block.js +0 -118
  56. package/dist/_components/tool-icons.d.ts +0 -17
  57. package/dist/_components/tool-icons.js +0 -128
  58. package/dist/_components/transcript.d.ts +0 -25
  59. package/dist/_components/transcript.js +0 -35
  60. package/dist/client-context.d.ts +0 -33
  61. package/dist/client-context.js +0 -15
  62. package/dist/client-handler.d.ts +0 -43
  63. package/dist/session-CWB7vmqz.js +0 -429
  64. package/dist/session.d.ts +0 -69
  65. package/dist/session.js +0 -2
  66. package/dist/signals.d.ts +0 -120
  67. package/dist/signals.js +0 -161
  68. package/dist/types.test-d.d.ts +0 -7
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Test utilities for React-based component tests.
3
+ * No dependency on @preact/signals.
4
+ */
5
+ import type { SessionCore, SessionSnapshot } from "./session-core.ts";
6
+ import type { AgentState, ChatMessage, SessionError, ToolCallInfo } from "./types.ts";
7
+ /**
8
+ * Create a mock SessionCore for React component tests.
9
+ *
10
+ * Returns a `SessionCore`-compatible object with mutable snapshot. Call
11
+ * `core.update(partial)` to mutate the snapshot and notify subscribers,
12
+ * triggering React re-renders.
13
+ */
14
+ export declare function createMockSessionCore(overrides?: Partial<{
15
+ state: AgentState;
16
+ messages: ChatMessage[];
17
+ toolCalls: ToolCallInfo[];
18
+ userTranscript: string | null;
19
+ agentTranscript: string | null;
20
+ error: SessionError | null;
21
+ started: boolean;
22
+ running: boolean;
23
+ }>): SessionCore & {
24
+ update(partial: Partial<SessionSnapshot>): void;
25
+ };
26
+ /** Default voice options for tests. */
27
+ export declare function voiceOpts(overrides?: Partial<import("./audio.ts").VoiceIOOptions>): {
28
+ sttSampleRate: number;
29
+ ttsSampleRate: number;
30
+ captureWorkletSrc: string;
31
+ playbackWorkletSrc: string;
32
+ onMicData: (pcm16: ArrayBuffer) => void;
33
+ onPlaybackProgress?: (samplesPlayed: number) => void;
34
+ };
35
+ export declare class MockMessagePort {
36
+ onmessage: ((e: MessageEvent) => void) | null;
37
+ posted: unknown[];
38
+ postMessage(data: unknown, _transfer?: Transferable[]): void;
39
+ simulateMessage(data: unknown): void;
40
+ }
41
+ export declare class MockAudioNode {
42
+ connected: (MockAudioNode | MockAudioWorkletNode)[];
43
+ connect(dest: MockAudioNode | MockAudioWorkletNode): void;
44
+ disconnect(): void;
45
+ }
46
+ export declare class MockGainNode extends MockAudioNode {
47
+ gain: {
48
+ value: number;
49
+ setTargetAtTime(value: number, _startTime: number, _tc: number): void;
50
+ };
51
+ }
52
+ export declare class MockAudioWorkletNode {
53
+ port: MockMessagePort;
54
+ connected: MockAudioNode[];
55
+ name: string;
56
+ options: unknown;
57
+ constructor(_ctx: MockAudioContext, name: string, options?: unknown);
58
+ connect(dest: MockAudioNode): void;
59
+ disconnect(): void;
60
+ }
61
+ export declare class MockAudioContext {
62
+ sampleRate: number;
63
+ state: AudioContextState;
64
+ currentTime: number;
65
+ destination: MockAudioNode;
66
+ audioWorklet: {
67
+ modules: string[];
68
+ addModule(url: string): Promise<void>;
69
+ };
70
+ closed: boolean;
71
+ constructor(opts?: {
72
+ sampleRate?: number;
73
+ });
74
+ resume(): Promise<void>;
75
+ createMediaStreamSource(_stream: unknown): MockAudioNode;
76
+ createGain(): MockGainNode;
77
+ close(): Promise<void>;
78
+ }
79
+ export type AudioMockContext = {
80
+ lastContext: () => MockAudioContext;
81
+ workletNodes: () => MockAudioWorkletNode[];
82
+ };
83
+ export declare function installAudioMocks(): AudioMockContext & {
84
+ restore: () => void;
85
+ };
86
+ export declare function findWorkletNode(nodes: MockAudioWorkletNode[], name: string): MockAudioWorkletNode;
package/dist/audio.js CHANGED
@@ -45,8 +45,9 @@ async function createVoiceIO(opts) {
45
45
  });
46
46
  mic.connect(capNode);
47
47
  const chunkSizeBytes = Math.floor(sttSampleRate * MIC_BUFFER_SECONDS) * 2;
48
- const capBufA = new Uint8Array(chunkSizeBytes * 2);
49
- const capBufB = new Uint8Array(chunkSizeBytes * 2);
48
+ const bufSize = chunkSizeBytes * 2;
49
+ const capBufA = new Uint8Array(bufSize);
50
+ const capBufB = new Uint8Array(bufSize);
50
51
  let capBuf = capBufA;
51
52
  let capOffset = 0;
52
53
  capNode.port.postMessage({ event: "start" });
@@ -56,11 +57,9 @@ async function createVoiceIO(opts) {
56
57
  capBuf.set(chunk, capOffset);
57
58
  capOffset += chunk.byteLength;
58
59
  if (capOffset >= chunkSizeBytes) {
59
- const sendBuf = capBuf;
60
- const filled = capOffset;
60
+ onMicData(capBuf.buffer.slice(0, capOffset));
61
61
  capBuf = capBuf === capBufA ? capBufB : capBufA;
62
62
  capOffset = 0;
63
- onMicData(sendBuf.buffer.slice(0, filled));
64
63
  }
65
64
  };
66
65
  let playNode = null;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import type * as preact from "preact";
1
+ import type { ReactNode } from "react";
2
2
  /**
3
3
  * Visual style of a {@link Button}.
4
4
  *
@@ -42,9 +42,9 @@ export type ButtonSize = "default" | "lg";
42
42
  *
43
43
  * @public
44
44
  */
45
- export declare function Button({ variant, size, className, children, ...rest }: {
45
+ export declare function Button({ variant, size, className, children, style, ...rest }: {
46
46
  variant?: ButtonVariant;
47
47
  size?: ButtonSize;
48
48
  className?: string;
49
- children?: preact.ComponentChildren;
50
- } & Omit<preact.JSX.HTMLAttributes<HTMLButtonElement>, "className">): preact.JSX.Element;
49
+ children?: ReactNode;
50
+ } & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "className">): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,8 @@
1
+ import { useTheme } from "../context.js";
1
2
  import clsx from "clsx";
2
- import { jsx } from "preact/jsx-runtime";
3
- //#region _components/button.tsx
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region components/button.tsx
5
+ /** @jsxImportSource react */
4
6
  const LG_STYLE = {
5
7
  display: "grid",
6
8
  placeItems: "center",
@@ -33,11 +35,32 @@ const LG_STYLE = {
33
35
  *
34
36
  * @public
35
37
  */
36
- function Button({ variant = "default", size = "default", className, children, ...rest }) {
38
+ function Button({ variant = "default", size = "default", className, children, style, ...rest }) {
39
+ const theme = useTheme();
40
+ let variantStyle;
41
+ if (variant === "default") variantStyle = {
42
+ background: theme.primary,
43
+ color: "#fff",
44
+ borderColor: "transparent"
45
+ };
46
+ else if (variant === "secondary") variantStyle = {
47
+ background: "rgba(255,255,255,0.059)",
48
+ color: "rgba(255,255,255,0.618)",
49
+ borderColor: theme.border
50
+ };
51
+ else variantStyle = {
52
+ background: "transparent",
53
+ color: "rgba(255,255,255,0.618)",
54
+ borderColor: theme.border
55
+ };
37
56
  return /* @__PURE__ */ jsx("button", {
38
57
  type: "button",
39
- style: size === "lg" ? LG_STYLE : void 0,
40
- class: clsx(size !== "lg" && "flex items-center justify-center appearance-none m-0 h-8 px-3 py-1.5 w-fit leading-none", "rounded-aai text-sm font-medium cursor-pointer border outline-none", variant === "secondary" && "bg-aai-surface-hover text-aai-text-secondary border-aai-border", variant === "ghost" && "bg-transparent text-aai-text-secondary border-aai-border", variant === "default" && "bg-aai-primary text-white border-transparent", className),
58
+ style: {
59
+ ...size === "lg" ? LG_STYLE : void 0,
60
+ ...variantStyle,
61
+ ...style
62
+ },
63
+ className: clsx(size !== "lg" && "flex items-center justify-center appearance-none m-0 h-8 px-3 py-1.5 w-fit leading-none", "rounded-aai text-sm font-medium cursor-pointer border outline-none", className),
41
64
  ...rest,
42
65
  children
43
66
  });
@@ -0,0 +1,38 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * The main chat interface for a voice agent session.
4
+ * Displays a header (with optional icon, title, and state indicator), an
5
+ * inline error banner, the {@link MessageList}, and session {@link Controls}.
6
+ *
7
+ * Must be rendered inside a {@link SessionProvider}.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * <StartScreen icon="🍕" title="Pizza Palace">
12
+ * <ChatView />
13
+ * </StartScreen>
14
+ * ```
15
+ *
16
+ * @example Pair with a sidebar
17
+ * ```tsx
18
+ * <SidebarLayout sidebar={<RecipeCard />}>
19
+ * <ChatView className="border-l" />
20
+ * </SidebarLayout>
21
+ * ```
22
+ *
23
+ * @example Custom header icon
24
+ * ```tsx
25
+ * <ChatView icon={<img src="/logo.svg" />} title="My Agent" />
26
+ * ```
27
+ *
28
+ * @param icon - Optional element rendered before the title in the header.
29
+ * @param title - Optional title string for the header.
30
+ * @param className - Additional CSS class names applied to the root element.
31
+ *
32
+ * @public
33
+ */
34
+ export declare function ChatView({ icon, title, className, }: {
35
+ icon?: ReactNode;
36
+ title?: string;
37
+ className?: string;
38
+ }): ReactNode;
@@ -0,0 +1,102 @@
1
+ import { useSession, useTheme } from "../context.js";
2
+ import { Controls } from "./controls.js";
3
+ import { MessageList } from "./message-list.js";
4
+ import clsx from "clsx";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region components/chat-view.tsx
7
+ /** @jsxImportSource react */
8
+ const STATE_COLORS = {
9
+ disconnected: "rgba(255,255,255,0.422)",
10
+ connecting: "rgba(255,255,255,0.422)",
11
+ ready: "#7fd88f",
12
+ listening: "#56b6c2",
13
+ thinking: "#f5a742",
14
+ speaking: "#e06c75",
15
+ error: "#e06c75"
16
+ };
17
+ /**
18
+ * The main chat interface for a voice agent session.
19
+ * Displays a header (with optional icon, title, and state indicator), an
20
+ * inline error banner, the {@link MessageList}, and session {@link Controls}.
21
+ *
22
+ * Must be rendered inside a {@link SessionProvider}.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <StartScreen icon="🍕" title="Pizza Palace">
27
+ * <ChatView />
28
+ * </StartScreen>
29
+ * ```
30
+ *
31
+ * @example Pair with a sidebar
32
+ * ```tsx
33
+ * <SidebarLayout sidebar={<RecipeCard />}>
34
+ * <ChatView className="border-l" />
35
+ * </SidebarLayout>
36
+ * ```
37
+ *
38
+ * @example Custom header icon
39
+ * ```tsx
40
+ * <ChatView icon={<img src="/logo.svg" />} title="My Agent" />
41
+ * ```
42
+ *
43
+ * @param icon - Optional element rendered before the title in the header.
44
+ * @param title - Optional title string for the header.
45
+ * @param className - Additional CSS class names applied to the root element.
46
+ *
47
+ * @public
48
+ */
49
+ function ChatView({ icon, title, className }) {
50
+ const session = useSession();
51
+ const theme = useTheme();
52
+ return /* @__PURE__ */ jsxs("div", {
53
+ className: clsx("flex flex-col h-screen max-w-130 mx-auto font-aai text-sm", className),
54
+ style: {
55
+ background: theme.bg,
56
+ color: theme.text
57
+ },
58
+ children: [
59
+ /* @__PURE__ */ jsxs("div", {
60
+ className: "flex items-center gap-3 px-4 py-3 border-b shrink-0",
61
+ style: { borderColor: theme.border },
62
+ children: [
63
+ icon,
64
+ title ? /* @__PURE__ */ jsx("span", {
65
+ className: "text-sm font-semibold",
66
+ style: { color: theme.primary },
67
+ children: title
68
+ }) : !icon && /* @__PURE__ */ jsx("pre", {
69
+ className: "font-aai-mono text-[10px] leading-[1.1] font-bold m-0",
70
+ style: { color: theme.primary },
71
+ children: "▄▀█ ▄▀█ █\n█▀█ █▀█ █"
72
+ }),
73
+ /* @__PURE__ */ jsx("div", {
74
+ className: "ml-auto",
75
+ children: /* @__PURE__ */ jsxs("div", {
76
+ className: "inline-flex items-center justify-center gap-1.5 text-[13px] font-medium leading-[130%] capitalize",
77
+ style: { color: "rgba(255,255,255,0.284)" },
78
+ "data-state": session.state,
79
+ children: [/* @__PURE__ */ jsx("div", {
80
+ className: "w-2 h-2 rounded-full",
81
+ style: { background: STATE_COLORS[session.state] }
82
+ }), session.state]
83
+ })
84
+ })
85
+ ]
86
+ }),
87
+ session.error && /* @__PURE__ */ jsx("div", {
88
+ className: "mx-4 mt-3 px-3 py-2 rounded-aai border text-[13px] leading-[130%]",
89
+ style: {
90
+ borderColor: "rgba(224,108,117,0.4)",
91
+ background: "rgba(224,108,117,0.08)",
92
+ color: "#e06c75"
93
+ },
94
+ children: session.error.message
95
+ }),
96
+ /* @__PURE__ */ jsx(MessageList, {}),
97
+ /* @__PURE__ */ jsx(Controls, {})
98
+ ]
99
+ });
100
+ }
101
+ //#endregion
102
+ export { ChatView };
@@ -15,4 +15,4 @@
15
15
  */
16
16
  export declare function Controls({ className }: {
17
17
  className?: string;
18
- }): import("preact").JSX.Element;
18
+ }): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +1,9 @@
1
- import { useSession } from "../signals.js";
1
+ import { useSession, useTheme } from "../context.js";
2
2
  import { Button } from "./button.js";
3
3
  import clsx from "clsx";
4
- import { jsx, jsxs } from "preact/jsx-runtime";
5
- //#region _components/controls.tsx
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region components/controls.tsx
6
+ /** @jsxImportSource react */
6
7
  /**
7
8
  * Session control buttons: **Stop / Resume** and **New Conversation**.
8
9
  *
@@ -20,12 +21,14 @@ import { jsx, jsxs } from "preact/jsx-runtime";
20
21
  */
21
22
  function Controls({ className }) {
22
23
  const { running, toggle, reset } = useSession();
24
+ const theme = useTheme();
23
25
  return /* @__PURE__ */ jsxs("div", {
24
- class: clsx("flex gap-2 px-4 py-3 border-t border-aai-border shrink-0", className),
26
+ className: clsx("flex gap-2 px-4 py-3 border-t shrink-0", className),
27
+ style: { borderColor: theme.border },
25
28
  children: [/* @__PURE__ */ jsx(Button, {
26
29
  variant: "secondary",
27
30
  onClick: toggle,
28
- children: running.value ? "Stop" : "Resume"
31
+ children: running ? "Stop" : "Resume"
29
32
  }), /* @__PURE__ */ jsx(Button, {
30
33
  variant: "ghost",
31
34
  onClick: reset,
@@ -3,7 +3,7 @@
3
3
  * streaming agent utterance, and a thinking indicator.
4
4
  *
5
5
  * Messages and tool calls are interleaved in the correct order. The list
6
- * auto-scrolls to the latest content via {@link useAutoScroll}.
6
+ * auto-scrolls to the latest content.
7
7
  *
8
8
  * Must be rendered inside a {@link SessionProvider}.
9
9
  *
@@ -18,4 +18,4 @@
18
18
  */
19
19
  export declare function MessageList({ className }: {
20
20
  className?: string;
21
- }): import("preact").JSX.Element;
21
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,149 @@
1
+ import { useSession, useTheme } from "../context.js";
2
+ import { t as ToolCallBlock } from "../tool-call-block-CCuChsFm.js";
3
+ import clsx from "clsx";
4
+ import { useEffect, useMemo, useRef } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region components/message-list.tsx
7
+ /** @jsxImportSource react */
8
+ const DOT_STYLES = [
9
+ 0,
10
+ .16,
11
+ .32
12
+ ].map((delay) => ({
13
+ animation: "aai-bounce 1.4s infinite ease-in-out both",
14
+ animationDelay: `${delay}s`
15
+ }));
16
+ /** Animated three-dot "thinking" indicator. */
17
+ function ThinkingDots() {
18
+ return /* @__PURE__ */ jsx("div", {
19
+ className: "flex items-center gap-2 text-sm font-medium min-h-5",
20
+ style: { color: "rgba(255,255,255,0.422)" },
21
+ children: DOT_STYLES.map((style, i) => /* @__PURE__ */ jsx("div", {
22
+ className: "w-1.5 h-1.5 rounded-full",
23
+ style: {
24
+ ...style,
25
+ background: "rgba(255,255,255,0.422)"
26
+ }
27
+ }, i))
28
+ });
29
+ }
30
+ /** Renders a single chat message as a styled bubble. */
31
+ function MessageBubble({ message, theme }) {
32
+ if (message.role === "user") return /* @__PURE__ */ jsx("div", {
33
+ className: "flex flex-col w-full items-end",
34
+ 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%]",
36
+ style: {
37
+ background: "rgba(255,255,255,0.031)",
38
+ borderColor: theme.border,
39
+ color: theme.text
40
+ },
41
+ children: message.content
42
+ })
43
+ });
44
+ return /* @__PURE__ */ jsx("div", {
45
+ className: "whitespace-pre-wrap wrap-break-word text-sm font-normal leading-[150%]",
46
+ style: { color: theme.text },
47
+ children: message.content
48
+ });
49
+ }
50
+ function useAutoScroll() {
51
+ const ref = useRef(null);
52
+ useEffect(() => {
53
+ ref.current?.scrollIntoView({
54
+ behavior: "smooth",
55
+ block: "end"
56
+ });
57
+ });
58
+ return ref;
59
+ }
60
+ /**
61
+ * Scrollable list of all chat messages, tool-call blocks, live transcript,
62
+ * streaming agent utterance, and a thinking indicator.
63
+ *
64
+ * Messages and tool calls are interleaved in the correct order. The list
65
+ * auto-scrolls to the latest content.
66
+ *
67
+ * Must be rendered inside a {@link SessionProvider}.
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * <MessageList className="flex-1" />
72
+ * ```
73
+ *
74
+ * @param className - Additional CSS class names applied to the scroll container.
75
+ *
76
+ * @public
77
+ */
78
+ function MessageList({ className }) {
79
+ const session = useSession();
80
+ const theme = useTheme();
81
+ const scrollRef = useAutoScroll();
82
+ const showThinking = useMemo(() => {
83
+ if (session.state !== "thinking") return false;
84
+ const last = session.toolCalls.at(-1);
85
+ if (last?.status === "pending") return false;
86
+ const lastMsg = session.messages.at(-1);
87
+ return !lastMsg || lastMsg.role === "user" || Boolean(last);
88
+ }, [
89
+ session.state,
90
+ session.toolCalls,
91
+ session.messages
92
+ ]);
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;
115
+ return /* @__PURE__ */ jsx("div", {
116
+ role: "log",
117
+ className: clsx("flex-1 overflow-y-auto [scrollbar-width:none]", className),
118
+ style: { background: theme.surface },
119
+ children: /* @__PURE__ */ jsxs("div", {
120
+ className: "flex flex-col gap-4.5 p-4",
121
+ children: [
122
+ items,
123
+ session.agentTranscript && /* @__PURE__ */ jsx(MessageBubble, {
124
+ message: {
125
+ role: "assistant",
126
+ content: session.agentTranscript
127
+ },
128
+ theme
129
+ }),
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
+ })
141
+ }),
142
+ showThinking && /* @__PURE__ */ jsx(ThinkingDots, {}),
143
+ /* @__PURE__ */ jsx("div", { ref: scrollRef })
144
+ ]
145
+ })
146
+ });
147
+ }
148
+ //#endregion
149
+ export { MessageList };
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * A two-column layout with a fixed-width sidebar and a flexible main area.
4
+ * Commonly used to pair a custom sidebar (cart, dashboard) with `<ChatView />`.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * <SidebarLayout sidebar={<OrderPanel />}>
9
+ * <ChatView />
10
+ * </SidebarLayout>
11
+ * ```
12
+ *
13
+ * @public
14
+ */
15
+ export declare function SidebarLayout({ sidebar, children, sidebarWidth, sidebarPosition, className, }: {
16
+ sidebar: ReactNode;
17
+ children: ReactNode;
18
+ sidebarWidth?: string | undefined;
19
+ sidebarPosition?: "left" | "right" | undefined;
20
+ className?: string;
21
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,43 @@
1
+ import { useTheme } from "../context.js";
2
+ import clsx from "clsx";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region components/sidebar-layout.tsx
5
+ /** @jsxImportSource react */
6
+ /**
7
+ * A two-column layout with a fixed-width sidebar and a flexible main area.
8
+ * Commonly used to pair a custom sidebar (cart, dashboard) with `<ChatView />`.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <SidebarLayout sidebar={<OrderPanel />}>
13
+ * <ChatView />
14
+ * </SidebarLayout>
15
+ * ```
16
+ *
17
+ * @public
18
+ */
19
+ function SidebarLayout({ sidebar, children, sidebarWidth = "18rem", sidebarPosition = "left", className }) {
20
+ const theme = useTheme();
21
+ const sidebarEl = /* @__PURE__ */ jsx("div", {
22
+ className: "shrink-0 flex flex-col overflow-y-auto",
23
+ style: {
24
+ width: sidebarWidth,
25
+ ...sidebarPosition === "left" ? { borderRight: `1px solid ${theme.border}` } : { borderLeft: `1px solid ${theme.border}` }
26
+ },
27
+ children: sidebar
28
+ });
29
+ return /* @__PURE__ */ jsxs("div", {
30
+ className: clsx("flex h-screen", className),
31
+ style: { background: theme.bg },
32
+ children: [
33
+ sidebarPosition === "left" && sidebarEl,
34
+ /* @__PURE__ */ jsx("div", {
35
+ className: "flex-1 min-w-0",
36
+ children
37
+ }),
38
+ sidebarPosition === "right" && sidebarEl
39
+ ]
40
+ });
41
+ }
42
+ //#endregion
43
+ export { SidebarLayout };
@@ -1,4 +1,4 @@
1
- import type { ComponentChildren } from "preact";
1
+ import type { ReactNode } from "react";
2
2
  /**
3
3
  * A centered start screen with icon, title, subtitle, and a start button.
4
4
  * Renders `children` (the main app) once the session has started.
@@ -17,10 +17,10 @@ import type { ComponentChildren } from "preact";
17
17
  * @public
18
18
  */
19
19
  export declare function StartScreen({ children, icon, title, subtitle, buttonText, className, }: {
20
- children: ComponentChildren;
21
- icon?: ComponentChildren | undefined;
20
+ children: ReactNode;
21
+ icon?: ReactNode | undefined;
22
22
  title?: string | undefined;
23
23
  subtitle?: string | undefined;
24
24
  buttonText?: string | undefined;
25
25
  className?: string | undefined;
26
- }): import("preact").JSX.Element;
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;
@@ -1,8 +1,9 @@
1
- import { useSession } from "../signals.js";
1
+ import { useSession, useTheme } from "../context.js";
2
2
  import { Button } from "./button.js";
3
3
  import clsx from "clsx";
4
- import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
5
- //#region _components/start-screen.tsx
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region components/start-screen.tsx
6
+ /** @jsxImportSource react */
6
7
  /**
7
8
  * A centered start screen with icon, title, subtitle, and a start button.
8
9
  * Renders `children` (the main app) once the session has started.
@@ -22,19 +23,27 @@ import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
22
23
  */
23
24
  function StartScreen({ children, icon, title, subtitle, buttonText = "Start", className }) {
24
25
  const { started, start } = useSession();
25
- if (started.value) return /* @__PURE__ */ jsx(Fragment, { children });
26
+ const theme = useTheme();
27
+ if (started) return children;
26
28
  return /* @__PURE__ */ jsx("div", {
27
- class: clsx("flex items-center justify-center h-screen bg-aai-bg font-aai", className),
29
+ className: clsx("flex items-center justify-center h-screen font-aai", className),
30
+ style: { background: theme.bg },
28
31
  children: /* @__PURE__ */ jsxs("div", {
29
- class: "flex flex-col items-center gap-4 bg-aai-surface border border-aai-border rounded-lg px-12 py-10 max-w-sm text-center",
32
+ className: "flex flex-col items-center gap-4 border rounded-lg px-12 py-10 max-w-sm text-center",
33
+ style: {
34
+ background: theme.surface,
35
+ borderColor: theme.border
36
+ },
30
37
  children: [
31
38
  icon,
32
39
  title && /* @__PURE__ */ jsx("h1", {
33
- class: "font-semibold text-aai-primary m-0",
40
+ className: "font-semibold m-0",
41
+ style: { color: theme.primary },
34
42
  children: title
35
43
  }),
36
44
  subtitle && /* @__PURE__ */ jsx("p", {
37
- class: "text-sm text-aai-text-muted m-0",
45
+ className: "text-sm m-0",
46
+ style: { color: "rgba(255,255,255,0.284)" },
38
47
  children: subtitle
39
48
  }),
40
49
  /* @__PURE__ */ jsx(Button, {