@alexkroman1/aai-ui 0.12.3 → 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 (62) 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/button.d.ts +4 -4
  5. package/dist/components/button.js +27 -4
  6. package/dist/components/chat-view.d.ts +14 -5
  7. package/dist/components/chat-view.js +64 -23
  8. package/dist/components/controls.d.ts +1 -1
  9. package/dist/components/controls.js +7 -4
  10. package/dist/components/message-list.d.ts +2 -2
  11. package/dist/components/message-list.js +100 -27
  12. package/dist/components/sidebar-layout.d.ts +7 -7
  13. package/dist/components/sidebar-layout.js +15 -8
  14. package/dist/components/start-screen.d.ts +4 -4
  15. package/dist/components/start-screen.js +16 -7
  16. package/dist/components/tool-call-block.d.ts +5 -6
  17. package/dist/components/tool-call-block.js +2 -117
  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 +10 -32
  33. package/dist/index.js +7 -14
  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 +21 -17
  39. package/styles.css +3 -33
  40. package/dist/client-context.d.ts +0 -33
  41. package/dist/client-context.js +0 -15
  42. package/dist/client-handler.d.ts +0 -43
  43. package/dist/components/app.d.ts +0 -23
  44. package/dist/components/app.js +0 -41
  45. package/dist/components/error-banner.d.ts +0 -21
  46. package/dist/components/error-banner.js +0 -27
  47. package/dist/components/message-bubble.d.ts +0 -23
  48. package/dist/components/message-bubble.js +0 -35
  49. package/dist/components/state-indicator.d.ts +0 -29
  50. package/dist/components/state-indicator.js +0 -37
  51. package/dist/components/thinking-indicator.d.ts +0 -16
  52. package/dist/components/thinking-indicator.js +0 -34
  53. package/dist/components/tool-icons.d.ts +0 -17
  54. package/dist/components/tool-icons.js +0 -128
  55. package/dist/components/transcript.d.ts +0 -25
  56. package/dist/components/transcript.js +0 -35
  57. package/dist/session-CWB7vmqz.js +0 -429
  58. package/dist/session.d.ts +0 -69
  59. package/dist/session.js +0 -2
  60. package/dist/signals.d.ts +0 -120
  61. package/dist/signals.js +0 -161
  62. 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
+ import { jsx } from "react/jsx-runtime";
3
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
  });
@@ -1,8 +1,8 @@
1
- import type * as preact from "preact";
1
+ import type { ReactNode } from "react";
2
2
  /**
3
3
  * The main chat interface for a voice agent session.
4
- * Displays a header (with title and {@link StateIndicator}), an
5
- * {@link ErrorBanner}, the {@link MessageList}, and session {@link Controls}.
4
+ * Displays a header (with optional icon, title, and state indicator), an
5
+ * inline error banner, the {@link MessageList}, and session {@link Controls}.
6
6
  *
7
7
  * Must be rendered inside a {@link SessionProvider}.
8
8
  *
@@ -20,10 +20,19 @@ import type * as preact from "preact";
20
20
  * </SidebarLayout>
21
21
  * ```
22
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.
23
30
  * @param className - Additional CSS class names applied to the root element.
24
31
  *
25
32
  * @public
26
33
  */
27
- export declare function ChatView({ className }: {
34
+ export declare function ChatView({ icon, title, className, }: {
35
+ icon?: ReactNode;
36
+ title?: string;
28
37
  className?: string;
29
- }): preact.JSX.Element;
38
+ }): ReactNode;
@@ -1,16 +1,23 @@
1
- import { useClientConfig } from "../client-context.js";
2
- import { useSession } from "../signals.js";
1
+ import { useSession, useTheme } from "../context.js";
3
2
  import { Controls } from "./controls.js";
4
- import { ErrorBanner } from "./error-banner.js";
5
3
  import { MessageList } from "./message-list.js";
6
- import { StateIndicator } from "./state-indicator.js";
7
4
  import clsx from "clsx";
8
- import { jsx, jsxs } from "preact/jsx-runtime";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
9
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
+ };
10
17
  /**
11
18
  * The main chat interface for a voice agent session.
12
- * Displays a header (with title and {@link StateIndicator}), an
13
- * {@link ErrorBanner}, the {@link MessageList}, and session {@link Controls}.
19
+ * Displays a header (with optional icon, title, and state indicator), an
20
+ * inline error banner, the {@link MessageList}, and session {@link Controls}.
14
21
  *
15
22
  * Must be rendered inside a {@link SessionProvider}.
16
23
  *
@@ -28,30 +35,64 @@ import { jsx, jsxs } from "preact/jsx-runtime";
28
35
  * </SidebarLayout>
29
36
  * ```
30
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.
31
45
  * @param className - Additional CSS class names applied to the root element.
32
46
  *
33
47
  * @public
34
48
  */
35
- function ChatView({ className }) {
36
- const { session } = useSession();
37
- const { title } = useClientConfig();
49
+ function ChatView({ icon, title, className }) {
50
+ const session = useSession();
51
+ const theme = useTheme();
38
52
  return /* @__PURE__ */ jsxs("div", {
39
- class: clsx("flex flex-col h-screen max-w-130 mx-auto bg-aai-bg text-aai-text font-aai text-sm", className),
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
+ },
40
58
  children: [
41
59
  /* @__PURE__ */ jsxs("div", {
42
- class: "flex items-center gap-3 px-4 py-3 border-b border-aai-border shrink-0",
43
- children: [title ? /* @__PURE__ */ jsx("span", {
44
- class: "text-sm font-semibold text-aai-primary",
45
- children: title
46
- }) : /* @__PURE__ */ jsx("pre", {
47
- class: "font-aai-mono text-[10px] leading-[1.1] font-bold text-aai-primary m-0",
48
- children: "▄▀█ ▄▀█ █\n█▀█ █▀█ █"
49
- }), /* @__PURE__ */ jsx("div", {
50
- class: "ml-auto",
51
- children: /* @__PURE__ */ jsx(StateIndicator, { state: session.state })
52
- })]
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
53
95
  }),
54
- /* @__PURE__ */ jsx(ErrorBanner, { error: session.error }),
55
96
  /* @__PURE__ */ jsx(MessageList, {}),
56
97
  /* @__PURE__ */ jsx(Controls, {})
57
98
  ]
@@ -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";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
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;
@@ -1,18 +1,68 @@
1
- import { useAutoScroll, useSession } from "../signals.js";
2
- import { MessageBubble } from "./message-bubble.js";
3
- import { ThinkingIndicator } from "./thinking-indicator.js";
4
- import { ToolCallBlock } from "./tool-call-block.js";
5
- import { Transcript } from "./transcript.js";
1
+ import { useSession, useTheme } from "../context.js";
2
+ import { t as ToolCallBlock } from "../tool-call-block-CCuChsFm.js";
6
3
  import clsx from "clsx";
7
- import { useComputed } from "@preact/signals";
8
- import { jsx, jsxs } from "preact/jsx-runtime";
4
+ import { useEffect, useMemo, useRef } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
9
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
+ }
10
60
  /**
11
61
  * Scrollable list of all chat messages, tool-call blocks, live transcript,
12
62
  * streaming agent utterance, and a thinking indicator.
13
63
  *
14
64
  * Messages and tool calls are interleaved in the correct order. The list
15
- * auto-scrolls to the latest content via {@link useAutoScroll}.
65
+ * auto-scrolls to the latest content.
16
66
  *
17
67
  * Must be rendered inside a {@link SessionProvider}.
18
68
  *
@@ -26,47 +76,70 @@ import { jsx, jsxs } from "preact/jsx-runtime";
26
76
  * @public
27
77
  */
28
78
  function MessageList({ className }) {
29
- const { session } = useSession();
79
+ const session = useSession();
80
+ const theme = useTheme();
30
81
  const scrollRef = useAutoScroll();
31
- const showThinking = useComputed(() => {
32
- if (session.state.value !== "thinking") return false;
33
- const last = session.toolCalls.value.at(-1);
82
+ const showThinking = useMemo(() => {
83
+ if (session.state !== "thinking") return false;
84
+ const last = session.toolCalls.at(-1);
34
85
  if (last?.status === "pending") return false;
35
- const lastMsg = session.messages.value.at(-1);
86
+ const lastMsg = session.messages.at(-1);
36
87
  return !lastMsg || lastMsg.role === "user" || Boolean(last);
37
- });
38
- const messages = session.messages.value;
39
- const toolCalls = session.toolCalls.value;
88
+ }, [
89
+ session.state,
90
+ session.toolCalls,
91
+ session.messages
92
+ ]);
93
+ const { messages, toolCalls } = session;
40
94
  const items = [];
41
95
  let tci = 0;
42
96
  for (const [i, msg] of messages.entries()) {
43
- items.push(/* @__PURE__ */ jsx(MessageBubble, { message: msg }, `msg-${i}`));
97
+ items.push(/* @__PURE__ */ jsx(MessageBubble, {
98
+ message: msg,
99
+ theme
100
+ }, `msg-${i}`));
44
101
  let tc = toolCalls[tci];
45
102
  while (tc && tc.afterMessageIndex <= i) {
46
- items.push(/* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.toolCallId));
103
+ items.push(/* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.callId));
47
104
  tci++;
48
105
  tc = toolCalls[tci];
49
106
  }
50
107
  }
51
108
  let tc = toolCalls[tci];
52
109
  while (tc) {
53
- items.push(/* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.toolCallId));
110
+ items.push(/* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.callId));
54
111
  tci++;
55
112
  tc = toolCalls[tci];
56
113
  }
114
+ const userTranscript = session.userTranscript;
57
115
  return /* @__PURE__ */ jsx("div", {
58
116
  role: "log",
59
- class: clsx("flex-1 overflow-y-auto [scrollbar-width:none] bg-aai-surface", className),
117
+ className: clsx("flex-1 overflow-y-auto [scrollbar-width:none]", className),
118
+ style: { background: theme.surface },
60
119
  children: /* @__PURE__ */ jsxs("div", {
61
- class: "flex flex-col gap-4.5 p-4",
120
+ className: "flex flex-col gap-4.5 p-4",
62
121
  children: [
63
122
  items,
64
- session.agentUtterance.value && /* @__PURE__ */ jsx(MessageBubble, { message: {
65
- role: "assistant",
66
- content: session.agentUtterance.value
67
- } }),
68
- /* @__PURE__ */ jsx(Transcript, { userUtterance: session.userUtterance }),
69
- showThinking.value && /* @__PURE__ */ jsx(ThinkingIndicator, {}),
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, {}),
70
143
  /* @__PURE__ */ jsx("div", { ref: scrollRef })
71
144
  ]
72
145
  })
@@ -1,4 +1,4 @@
1
- import type { ComponentChildren } from "preact";
1
+ import type { ReactNode } from "react";
2
2
  /**
3
3
  * A two-column layout with a fixed-width sidebar and a flexible main area.
4
4
  * Commonly used to pair a custom sidebar (cart, dashboard) with `<ChatView />`.
@@ -12,10 +12,10 @@ import type { ComponentChildren } from "preact";
12
12
  *
13
13
  * @public
14
14
  */
15
- export declare function SidebarLayout({ sidebar, children, width, side, className, }: {
16
- sidebar: ComponentChildren;
17
- children: ComponentChildren;
18
- width?: string | undefined;
19
- side?: "left" | "right" | undefined;
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
20
  className?: string;
21
- }): import("preact").JSX.Element;
21
+ }): 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, jsxs } from "preact/jsx-runtime";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
3
4
  //#region components/sidebar-layout.tsx
5
+ /** @jsxImportSource react */
4
6
  /**
5
7
  * A two-column layout with a fixed-width sidebar and a flexible main area.
6
8
  * Commonly used to pair a custom sidebar (cart, dashboard) with `<ChatView />`.
@@ -14,21 +16,26 @@ import { jsx, jsxs } from "preact/jsx-runtime";
14
16
  *
15
17
  * @public
16
18
  */
17
- function SidebarLayout({ sidebar, children, width = "20rem", side = "left", className }) {
19
+ function SidebarLayout({ sidebar, children, sidebarWidth = "18rem", sidebarPosition = "left", className }) {
20
+ const theme = useTheme();
18
21
  const sidebarEl = /* @__PURE__ */ jsx("div", {
19
- class: clsx("flex-shrink-0 flex flex-col overflow-y-auto", side === "left" ? "border-r border-aai-border" : "border-l border-aai-border"),
20
- style: { width },
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
+ },
21
27
  children: sidebar
22
28
  });
23
29
  return /* @__PURE__ */ jsxs("div", {
24
- class: clsx("flex h-screen bg-aai-bg", className),
30
+ className: clsx("flex h-screen", className),
31
+ style: { background: theme.bg },
25
32
  children: [
26
- side === "left" && sidebarEl,
33
+ sidebarPosition === "left" && sidebarEl,
27
34
  /* @__PURE__ */ jsx("div", {
28
- class: "flex-1 min-w-0",
35
+ className: "flex-1 min-w-0",
29
36
  children
30
37
  }),
31
- side === "right" && sidebarEl
38
+ sidebarPosition === "right" && sidebarEl
32
39
  ]
33
40
  });
34
41
  }
@@ -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;