@alexkroman1/aai-ui 5.2.0 → 5.4.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 (42) hide show
  1. package/README.md +59 -0
  2. package/dist/{chat-view-C6N2oXjo.js → chat-view-CqRGHImR.js} +12 -6
  3. package/dist/client-config.d.ts +13 -2
  4. package/dist/components/button.d.ts +17 -11
  5. package/dist/components/button.js +9 -6
  6. package/dist/components/chat-view.d.ts +10 -4
  7. package/dist/components/chat-view.js +1 -1
  8. package/dist/components/controls.d.ts +6 -2
  9. package/dist/components/controls.js +1 -1
  10. package/dist/components/message-list.d.ts +6 -2
  11. package/dist/components/message-list.js +1 -1
  12. package/dist/components/sidebar-layout.d.ts +13 -3
  13. package/dist/components/sidebar-layout.js +13 -3
  14. package/dist/components/start-screen.d.ts +2 -0
  15. package/dist/components/start-screen.js +2 -0
  16. package/dist/components/tool-call-block.d.ts +1 -6
  17. package/dist/components/tool-call-block.js +1 -1
  18. package/dist/components/tool-config-context.d.ts +3 -3
  19. package/dist/components/url-chips.d.ts +3 -3
  20. package/dist/context.d.ts +59 -2
  21. package/dist/context.js +45 -0
  22. package/dist/{controls-Cqg7T4FN.js → controls-CoBO6sId.js} +9 -5
  23. package/dist/default-client/assets/{audio-C1U_foBG.js → audio-DXQj0wCf.js} +1 -1
  24. package/dist/default-client/assets/{capture-processor-dyyJArQs.js → capture-processor-DZg_Q6hY.js} +1 -1
  25. package/dist/default-client/assets/{index-CGZajs1V.js → index-DyY8OeVc.js} +25 -25
  26. package/dist/default-client/assets/{playback-processor-BO2TdYrv.js → playback-processor-DZlwpHHt.js} +1 -1
  27. package/dist/default-client/index.html +1 -1
  28. package/dist/define-client.d.ts +44 -9
  29. package/dist/define-client.js +22 -4
  30. package/dist/hooks.d.ts +51 -19
  31. package/dist/hooks.js +37 -7
  32. package/dist/index.d.ts +5 -5
  33. package/dist/index.js +6 -6
  34. package/dist/{message-list-CD9GxrlH.js → message-list-CdtrNi4w.js} +7 -3
  35. package/dist/{session-core-Bvga6tUS.js → session-core-DX2SIpdQ.js} +30 -4
  36. package/dist/session-core-types.d.ts +34 -9
  37. package/dist/session-core.d.ts +15 -0
  38. package/dist/session-core.js +1 -1
  39. package/dist/{tool-call-block-O9vLqoyU.js → tool-call-block-C--Bj04_.js} +4 -9
  40. package/dist/types.d.ts +23 -7
  41. package/dist/types.js +3 -0
  42. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @alexkroman1/aai-ui
2
+
3
+ The browser client for aai voice agents: React 19 components, hooks, and a
4
+ framework-agnostic session core (WebSocket + microphone + playback).
5
+
6
+ ```sh
7
+ npm i @alexkroman1/aai-ui react react-dom
8
+ ```
9
+
10
+ Every agent gets this UI for free — `aai dev` and deployed agents serve a
11
+ default client built from this package. Install it directly when the agent
12
+ has its own `client.tsx`.
13
+
14
+ ## A custom client
15
+
16
+ `client()` mounts the default chat shell with your sidebar, or replaces the
17
+ whole UI with a custom component:
18
+
19
+ ```tsx
20
+ import "@alexkroman1/aai-ui/styles.css";
21
+ import { client, useAgentState, useTheme } from "@alexkroman1/aai-ui";
22
+
23
+ type OrderView = { items: string[]; total: string };
24
+
25
+ function OrderSidebar() {
26
+ const theme = useTheme();
27
+ // Server state projected by the agent's `syncState`, pushed after every
28
+ // tool call.
29
+ const order = useAgentState<OrderView>() ?? { items: [], total: "$0.00" };
30
+ return (
31
+ <div style={{ color: theme.text }}>
32
+ {order.items.map((item) => (
33
+ <div key={item}>{item}</div>
34
+ ))}
35
+ <strong style={{ color: theme.primary }}>{order.total}</strong>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ client({ sidebar: <OrderSidebar /> });
41
+ ```
42
+
43
+ ## Hooks
44
+
45
+ Inside components rendered by `client()`:
46
+
47
+ - `useSession()` — connection state, transcript, `connect`/`disconnect`.
48
+ - `useAgentState<T>()` — the agent's `syncState` projection, live.
49
+ - `useToolResult(name, cb)` / `useToolCallStart(name, cb)` — observe tool
50
+ calls as they run (e.g. to render a card per result).
51
+ - `useEvent(name, cb)` — custom events the agent pushes with `ctx.send`.
52
+ - `useTheme()` — the resolved theme colors for custom components.
53
+
54
+ For a non-React integration, `createSessionCore()` exposes the same session
55
+ as a plain store with an immutable snapshot per change.
56
+
57
+ ## Documentation
58
+
59
+ Full API reference: <https://alexkroman.github.io/agent/>
@@ -2,8 +2,8 @@ import { useSessionSelector, useTheme } from "./context.js";
2
2
  import { a as THINKING_COLOR, r as TEXT_FAINT, t as ERROR_COLOR } from "./_colors-Bfh3-BVE.js";
3
3
  import { t as AaiLogo } from "./aai-logo-B8lDmsut.js";
4
4
  import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
5
- import { t as Controls } from "./controls-Cqg7T4FN.js";
6
- import { t as MessageList } from "./message-list-CD9GxrlH.js";
5
+ import { t as Controls } from "./controls-CoBO6sId.js";
6
+ import { t as MessageList } from "./message-list-CdtrNi4w.js";
7
7
  import clsx from "clsx";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region components/console-shell.tsx
@@ -93,13 +93,19 @@ const PULSING_STATES = /* @__PURE__ */ new Set(["listening", "speaking"]);
93
93
  * (logo + live-status eyebrow), the conversation on a raised white card,
94
94
  * and the session controls beneath it.
95
95
  *
96
- * Must be rendered inside a {@link SessionProvider}.
96
+ * Must be rendered inside a `SessionProvider`.
97
97
  *
98
98
  * @example
99
99
  * ```tsx
100
- * <StartScreen icon="🍕" title="Pizza Palace">
101
- * <ChatView />
102
- * </StartScreen>
100
+ * import { ChatView, StartScreen } from "@alexkroman1/aai-ui";
101
+ *
102
+ * function App() {
103
+ * return (
104
+ * <StartScreen icon="🍕" title="Pizza Palace">
105
+ * <ChatView />
106
+ * </StartScreen>
107
+ * );
108
+ * }
103
109
  * ```
104
110
  *
105
111
  * @param icon - Optional element rendered in place of the logo in the header.
@@ -13,8 +13,13 @@
13
13
  * named no sessionUrl" (`{}`). See its doc comment.
14
14
  */
15
15
  import { type ClientConfigResponse } from "@alexkroman1/aai/protocol";
16
+ /** @internal Re-exported for the sibling modules; the SDK's `/protocol` subpath is the canonical home. */
16
17
  export type { ClientConfigResponse } from "@alexkroman1/aai/protocol";
17
- /** Resolve a relative endpoint path against the agent's base URL. */
18
+ /**
19
+ * Resolve a relative endpoint path against the agent's base URL.
20
+ *
21
+ * @internal
22
+ */
18
23
  export declare function buildAgentUrl(platformUrl: string, endpointPath: string): URL;
19
24
  /**
20
25
  * Fetch the agent's client config, reporting `null` when the lookup did not
@@ -29,7 +34,13 @@ export declare function buildAgentUrl(platformUrl: string, endpointPath: string)
29
34
  * platform's `/:slug/websocket` — a WebSocket redirect browsers don't
30
35
  * follow, so every retry failed with no re-brokering even after the agent
31
36
  * recovered.
37
+ *
38
+ * @internal
32
39
  */
33
40
  export declare function loadClientConfig(platformUrl: string, fetchFn?: typeof globalThis.fetch): Promise<ClientConfigResponse | null>;
34
- /** Fetch the agent's client config; any failure yields the agent default. */
41
+ /**
42
+ * Fetch the agent's client config; any failure yields the agent default.
43
+ *
44
+ * @internal
45
+ */
35
46
  export declare function fetchClientConfig(platformUrl: string, fetchFn?: typeof globalThis.fetch): Promise<ClientConfigResponse>;
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import type { ButtonHTMLAttributes, ReactNode } from "react";
2
2
  /**
3
3
  * Visual style of a {@link Button} (design-system "website refresh":
4
4
  * rectangular, ALL-CAPS, tracked labels).
@@ -6,15 +6,19 @@ import type { ReactNode } from "react";
6
6
  * - `"default"` — Primary filled button (indigo background).
7
7
  * - `"secondary"` — Outlined primary (transparent background, primary border).
8
8
  * - `"ghost"` — Raised neutral (surface background with hairline border).
9
+ *
10
+ * @public
9
11
  */
10
- type ButtonVariant = "default" | "secondary" | "ghost";
12
+ export type ButtonVariant = "default" | "secondary" | "ghost";
11
13
  /**
12
14
  * Size preset for a {@link Button}.
13
15
  *
14
16
  * - `"default"` — Compact control (height 36 px).
15
17
  * - `"lg"` — Primary CTA (height 44 px, generous padding).
18
+ *
19
+ * @public
16
20
  */
17
- type ButtonSize = "default" | "lg";
21
+ export type ButtonSize = "default" | "lg";
18
22
  /**
19
23
  * A styled button with variant and size presets.
20
24
  *
@@ -23,13 +27,16 @@ type ButtonSize = "default" | "lg";
23
27
  *
24
28
  * @example
25
29
  * ```tsx
26
- * <Button variant="secondary" onClick={handleClick}>
27
- * Stop
28
- * </Button>
30
+ * import { Button } from "@alexkroman1/aai-ui";
29
31
  *
30
- * <Button size="lg" className="w-full">
31
- * Start Conversation
32
- * </Button>
32
+ * function Actions({ onStop }: { onStop: () => void }) {
33
+ * return (
34
+ * <>
35
+ * <Button variant="secondary" onClick={onStop}>Stop</Button>
36
+ * <Button size="lg" className="w-full">Start Conversation</Button>
37
+ * </>
38
+ * );
39
+ * }
33
40
  * ```
34
41
  *
35
42
  * @param variant - Visual style (`"default"` | `"secondary"` | `"ghost"`). Defaults to `"default"`.
@@ -44,5 +51,4 @@ export declare function Button({ variant, size, className, children, style, ...r
44
51
  size?: ButtonSize;
45
52
  className?: string;
46
53
  children?: ReactNode;
47
- } & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "className">): import("react").JSX.Element;
48
- export {};
54
+ } & Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className">): import("react").JSX.Element;
@@ -11,13 +11,16 @@ import { jsx } from "react/jsx-runtime";
11
11
  *
12
12
  * @example
13
13
  * ```tsx
14
- * <Button variant="secondary" onClick={handleClick}>
15
- * Stop
16
- * </Button>
14
+ * import { Button } from "@alexkroman1/aai-ui";
17
15
  *
18
- * <Button size="lg" className="w-full">
19
- * Start Conversation
20
- * </Button>
16
+ * function Actions({ onStop }: { onStop: () => void }) {
17
+ * return (
18
+ * <>
19
+ * <Button variant="secondary" onClick={onStop}>Stop</Button>
20
+ * <Button size="lg" className="w-full">Start Conversation</Button>
21
+ * </>
22
+ * );
23
+ * }
21
24
  * ```
22
25
  *
23
26
  * @param variant - Visual style (`"default"` | `"secondary"` | `"ghost"`). Defaults to `"default"`.
@@ -6,13 +6,19 @@ import type { ReactNode } from "react";
6
6
  * (logo + live-status eyebrow), the conversation on a raised white card,
7
7
  * and the session controls beneath it.
8
8
  *
9
- * Must be rendered inside a {@link SessionProvider}.
9
+ * Must be rendered inside a `SessionProvider`.
10
10
  *
11
11
  * @example
12
12
  * ```tsx
13
- * <StartScreen icon="🍕" title="Pizza Palace">
14
- * <ChatView />
15
- * </StartScreen>
13
+ * import { ChatView, StartScreen } from "@alexkroman1/aai-ui";
14
+ *
15
+ * function App() {
16
+ * return (
17
+ * <StartScreen icon="🍕" title="Pizza Palace">
18
+ * <ChatView />
19
+ * </StartScreen>
20
+ * );
21
+ * }
16
22
  * ```
17
23
  *
18
24
  * @param icon - Optional element rendered in place of the logo in the header.
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as ChatView } from "../chat-view-C6N2oXjo.js";
2
+ import { t as ChatView } from "../chat-view-CqRGHImR.js";
3
3
  export { ChatView };
@@ -2,11 +2,15 @@
2
2
  * Session control buttons: **Stop / Resume** and **New Conversation**.
3
3
  *
4
4
  * Reads session state from {@link useSession}. Must be rendered inside a
5
- * {@link SessionProvider}.
5
+ * `SessionProvider`.
6
6
  *
7
7
  * @example
8
8
  * ```tsx
9
- * <Controls className="justify-end" />
9
+ * import { Controls } from "@alexkroman1/aai-ui";
10
+ *
11
+ * function Footer() {
12
+ * return <Controls className="justify-end" />;
13
+ * }
10
14
  * ```
11
15
  *
12
16
  * @param className - Additional CSS class names applied to the container.
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as Controls } from "../controls-Cqg7T4FN.js";
2
+ import { t as Controls } from "../controls-CoBO6sId.js";
3
3
  export { Controls };
@@ -5,11 +5,15 @@
5
5
  * Messages and tool calls are interleaved in the correct order. The list
6
6
  * auto-scrolls to the latest content.
7
7
  *
8
- * Must be rendered inside a {@link SessionProvider}.
8
+ * Must be rendered inside a `SessionProvider`.
9
9
  *
10
10
  * @example
11
11
  * ```tsx
12
- * <MessageList className="flex-1" />
12
+ * import { MessageList } from "@alexkroman1/aai-ui";
13
+ *
14
+ * function Conversation() {
15
+ * return <MessageList className="flex-1" />;
16
+ * }
13
17
  * ```
14
18
  *
15
19
  * @param className - Additional CSS class names applied to the scroll container.
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as MessageList } from "../message-list-CD9GxrlH.js";
2
+ import { t as MessageList } from "../message-list-CdtrNi4w.js";
3
3
  export { MessageList };
@@ -5,9 +5,19 @@ import type { ReactNode } from "react";
5
5
  *
6
6
  * @example
7
7
  * ```tsx
8
- * <SidebarLayout sidebar={<OrderPanel />}>
9
- * <ChatView />
10
- * </SidebarLayout>
8
+ * import { ChatView, SidebarLayout } from "@alexkroman1/aai-ui";
9
+ *
10
+ * function OrderPanel() {
11
+ * return <div>Cart</div>;
12
+ * }
13
+ *
14
+ * function App() {
15
+ * return (
16
+ * <SidebarLayout sidebar={<OrderPanel />}>
17
+ * <ChatView />
18
+ * </SidebarLayout>
19
+ * );
20
+ * }
11
21
  * ```
12
22
  *
13
23
  * @public
@@ -9,9 +9,19 @@ import { jsx, jsxs } from "react/jsx-runtime";
9
9
  *
10
10
  * @example
11
11
  * ```tsx
12
- * <SidebarLayout sidebar={<OrderPanel />}>
13
- * <ChatView />
14
- * </SidebarLayout>
12
+ * import { ChatView, SidebarLayout } from "@alexkroman1/aai-ui";
13
+ *
14
+ * function OrderPanel() {
15
+ * return <div>Cart</div>;
16
+ * }
17
+ *
18
+ * function App() {
19
+ * return (
20
+ * <SidebarLayout sidebar={<OrderPanel />}>
21
+ * <ChatView />
22
+ * </SidebarLayout>
23
+ * );
24
+ * }
15
25
  * ```
16
26
  *
17
27
  * @public
@@ -6,6 +6,8 @@ import type { ReactNode } from "react";
6
6
  *
7
7
  * @example
8
8
  * ```tsx
9
+ * import { ChatView, StartScreen } from "@alexkroman1/aai-ui";
10
+ *
9
11
  * function MyAgent() {
10
12
  * return (
11
13
  * <StartScreen icon="🍕" title="Pizza Palace" subtitle="Voice-powered ordering">
@@ -14,6 +14,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
14
14
  *
15
15
  * @example
16
16
  * ```tsx
17
+ * import { ChatView, StartScreen } from "@alexkroman1/aai-ui";
18
+ *
17
19
  * function MyAgent() {
18
20
  * return (
19
21
  * <StartScreen icon="🍕" title="Pizza Palace" subtitle="Voice-powered ordering">
@@ -16,15 +16,10 @@ import type { ToolCallInfo } from "../types.ts";
16
16
  * snapshots and rows are keyed on the stable `callId`, so a list update only
17
17
  * re-renders the rows whose tool call actually changed.
18
18
  *
19
- * @example
20
- * ```tsx
21
- * <ToolCallBlock toolCall={toolCall} />
22
- * ```
23
- *
24
19
  * @param toolCall - The tool call to render (see {@link ToolCallInfo}).
25
20
  * @param className - Additional CSS class names.
26
21
  *
27
- * @public
22
+ * @internal Not exported from the package — rendered by `MessageList`.
28
23
  */
29
24
  export declare const ToolCallBlock: import("react").MemoExoticComponent<({ toolCall, className, }: {
30
25
  toolCall: ToolCallInfo;
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as ToolCallBlock } from "../tool-call-block-O9vLqoyU.js";
2
+ import { t as ToolCallBlock } from "../tool-call-block-C--Bj04_.js";
3
3
  export { ToolCallBlock };
@@ -8,10 +8,10 @@ export type ToolDisplayConfig = Record<string, {
8
8
  label?: string;
9
9
  }>;
10
10
  /**
11
- * Context for tool display configuration.
12
- * Provided by `client` or manually via `ToolConfigContext.Provider`.
11
+ * Context for tool display configuration. Installed by `client()` from
12
+ * `ClientConfig.tools`; the built-in components read it via `useToolConfig`.
13
13
  *
14
- * @public
14
+ * @internal
15
15
  */
16
16
  export declare const ToolConfigContext: import("react").Context<ToolDisplayConfig>;
17
17
  /**
@@ -2,7 +2,7 @@
2
2
  * The session's shareable UI URL — the page this UI is served from, what
3
3
  * you'd send someone to talk to the agent.
4
4
  *
5
- * @public
5
+ * @internal
6
6
  */
7
7
  export declare function UiUrlChip({ className }: {
8
8
  className?: string | undefined;
@@ -11,7 +11,7 @@ export declare function UiUrlChip({ className }: {
11
11
  * The session's programmatic WebSocket endpoint — the URL a script or backend
12
12
  * can connect to directly instead of using this UI.
13
13
  *
14
- * @public
14
+ * @internal
15
15
  */
16
16
  export declare function ApiUrlChip({ className }: {
17
17
  className?: string | undefined;
@@ -22,7 +22,7 @@ export declare function ApiUrlChip({ className }: {
22
22
  * told apart. Rendered by the default shell in every session mode (S2S,
23
23
  * pipeline).
24
24
  *
25
- * @public
25
+ * @internal
26
26
  */
27
27
  export declare function SessionUrlChips({ className }: {
28
28
  className?: string | undefined;
package/dist/context.d.ts CHANGED
@@ -1,12 +1,31 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { SessionCore, SessionSnapshot } from "./session-core-types.ts";
3
3
  import type { ClientTheme } from "./types.ts";
4
+ /**
5
+ * Provides the {@link SessionCore} the session hooks read. `client()`
6
+ * installs it automatically; a custom tree only needs it when bypassing
7
+ * `client()` and mounting React itself.
8
+ *
9
+ * @internal
10
+ */
4
11
  export declare function SessionProvider({ value, children }: {
5
12
  value: SessionCore;
6
13
  children?: ReactNode;
7
14
  }): import("react").FunctionComponentElement<import("react").ProviderProps<SessionCore | null>>;
8
- /** The session snapshot merged with the core's control methods. Method
9
- * signatures come from {@link SessionCore} one source of truth. */
15
+ /**
16
+ * What {@link useSession} returns: the live {@link SessionSnapshot} fields
17
+ * (`state`, `messages`, `toolCalls`, `agentState`, live transcripts, `error`,
18
+ * `apiUrl`, `started`/`running`/`recording`, …) merged with the session's
19
+ * control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
20
+ * `cancel`).
21
+ *
22
+ * Note there is no text-send method — sessions are voice-only; the only
23
+ * client→server inputs are audio and the control methods above.
24
+ *
25
+ * Method signatures come from {@link SessionCore} — one source of truth.
26
+ *
27
+ * @public
28
+ */
10
29
  export type Session = SessionSnapshot & Pick<SessionCore, "start" | "cancel" | "resetState" | "reset" | "disconnect" | "toggle">;
11
30
  /**
12
31
  * Return the raw {@link SessionCore} from context without subscribing to
@@ -18,6 +37,30 @@ export type Session = SessionSnapshot & Pick<SessionCore, "start" | "cancel" | "
18
37
  * components.
19
38
  */
20
39
  export declare function useSessionCore(): SessionCore;
40
+ /**
41
+ * Return the live {@link Session}: the current snapshot fields plus the
42
+ * control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
43
+ * `cancel`).
44
+ *
45
+ * Throws if used outside the provider `client()` installs (the error names
46
+ * `<SessionProvider>` — you only mount that yourself when bypassing
47
+ * `client()`). Re-renders the component on *every* snapshot change; for a
48
+ * component that reads one field, prefer {@link useSessionSelector} for a
49
+ * targeted subscription.
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * import { useSession } from "@alexkroman1/aai-ui";
54
+ *
55
+ * function Controls() {
56
+ * const session = useSession();
57
+ * if (!session.started) return <button onClick={session.start}>Start</button>;
58
+ * return <button onClick={session.toggle}>{session.running ? "Pause" : "Resume"}</button>;
59
+ * }
60
+ * ```
61
+ *
62
+ * @public
63
+ */
21
64
  export declare function useSession(): Session;
22
65
  /**
23
66
  * Subscribe to a narrow slice of the session snapshot.
@@ -34,8 +77,22 @@ export declare function useSession(): Session;
34
77
  * @public
35
78
  */
36
79
  export declare function useSessionSelector<T>(selector: (snapshot: SessionSnapshot) => T, isEqual?: (a: T, b: T) => boolean): T;
80
+ /**
81
+ * Provides the theme the components read via `useTheme`. `client()` installs
82
+ * it automatically (from `ClientConfig.theme`); a custom tree only needs it
83
+ * when bypassing `client()` and mounting React itself.
84
+ *
85
+ * @internal
86
+ */
37
87
  export declare function ThemeProvider({ value, children, }: {
38
88
  value?: ClientTheme | undefined;
39
89
  children?: ReactNode;
40
90
  }): import("react").FunctionComponentElement<import("react").ProviderProps<Required<ClientTheme>>>;
91
+ /**
92
+ * Read the resolved theme (every {@link ClientTheme} field filled with its
93
+ * default) from the nearest theme context. Returns the default theme when no
94
+ * provider is present, so components can call it unconditionally.
95
+ *
96
+ * @public
97
+ */
41
98
  export declare function useTheme(): Required<ClientTheme>;
package/dist/context.js CHANGED
@@ -9,6 +9,13 @@ const DEFAULT_THEME = {
9
9
  border: "#DCD7CC"
10
10
  };
11
11
  const SessionCtx = createContext(null);
12
+ /**
13
+ * Provides the {@link SessionCore} the session hooks read. `client()`
14
+ * installs it automatically; a custom tree only needs it when bypassing
15
+ * `client()` and mounting React itself.
16
+ *
17
+ * @internal
18
+ */
12
19
  function SessionProvider({ value, children }) {
13
20
  return createElement(SessionCtx.Provider, { value }, children);
14
21
  }
@@ -26,6 +33,30 @@ function useSessionCore() {
26
33
  if (!core) throw new Error("Session hooks must be used within <SessionProvider>");
27
34
  return core;
28
35
  }
36
+ /**
37
+ * Return the live {@link Session}: the current snapshot fields plus the
38
+ * control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
39
+ * `cancel`).
40
+ *
41
+ * Throws if used outside the provider `client()` installs (the error names
42
+ * `<SessionProvider>` — you only mount that yourself when bypassing
43
+ * `client()`). Re-renders the component on *every* snapshot change; for a
44
+ * component that reads one field, prefer {@link useSessionSelector} for a
45
+ * targeted subscription.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * import { useSession } from "@alexkroman1/aai-ui";
50
+ *
51
+ * function Controls() {
52
+ * const session = useSession();
53
+ * if (!session.started) return <button onClick={session.start}>Start</button>;
54
+ * return <button onClick={session.toggle}>{session.running ? "Pause" : "Resume"}</button>;
55
+ * }
56
+ * ```
57
+ *
58
+ * @public
59
+ */
29
60
  function useSession() {
30
61
  const core = useSessionCore();
31
62
  const snapshot = useSyncExternalStore(core.subscribe, core.getSnapshot);
@@ -58,6 +89,13 @@ function useSessionSelector(selector, isEqual = Object.is) {
58
89
  return useSyncExternalStoreWithSelector(core.subscribe, core.getSnapshot, core.getSnapshot, selector, isEqual);
59
90
  }
60
91
  const ThemeCtx = createContext(DEFAULT_THEME);
92
+ /**
93
+ * Provides the theme the components read via `useTheme`. `client()` installs
94
+ * it automatically (from `ClientConfig.theme`); a custom tree only needs it
95
+ * when bypassing `client()` and mounting React itself.
96
+ *
97
+ * @internal
98
+ */
61
99
  function ThemeProvider({ value, children }) {
62
100
  const merged = useMemo(() => value ? {
63
101
  ...DEFAULT_THEME,
@@ -92,6 +130,13 @@ function usePageBackground(bg) {
92
130
  };
93
131
  }, [bg]);
94
132
  }
133
+ /**
134
+ * Read the resolved theme (every {@link ClientTheme} field filled with its
135
+ * default) from the nearest theme context. Returns the default theme when no
136
+ * provider is present, so components can call it unconditionally.
137
+ *
138
+ * @public
139
+ */
95
140
  function useTheme() {
96
141
  return useContext(ThemeCtx);
97
142
  }
@@ -55,7 +55,7 @@ function UrlChip({ label, url, hint, testId, className }) {
55
55
  * The session's shareable UI URL — the page this UI is served from, what
56
56
  * you'd send someone to talk to the agent.
57
57
  *
58
- * @public
58
+ * @internal
59
59
  */
60
60
  function UiUrlChip({ className }) {
61
61
  return /* @__PURE__ */ jsx(UrlChip, {
@@ -70,7 +70,7 @@ function UiUrlChip({ className }) {
70
70
  * The session's programmatic WebSocket endpoint — the URL a script or backend
71
71
  * can connect to directly instead of using this UI.
72
72
  *
73
- * @public
73
+ * @internal
74
74
  */
75
75
  function ApiUrlChip({ className }) {
76
76
  return /* @__PURE__ */ jsx(UrlChip, {
@@ -87,7 +87,7 @@ function ApiUrlChip({ className }) {
87
87
  * told apart. Rendered by the default shell in every session mode (S2S,
88
88
  * pipeline).
89
89
  *
90
- * @public
90
+ * @internal
91
91
  */
92
92
  function SessionUrlChips({ className }) {
93
93
  return /* @__PURE__ */ jsxs("div", {
@@ -102,11 +102,15 @@ function SessionUrlChips({ className }) {
102
102
  * Session control buttons: **Stop / Resume** and **New Conversation**.
103
103
  *
104
104
  * Reads session state from {@link useSession}. Must be rendered inside a
105
- * {@link SessionProvider}.
105
+ * `SessionProvider`.
106
106
  *
107
107
  * @example
108
108
  * ```tsx
109
- * <Controls className="justify-end" />
109
+ * import { Controls } from "@alexkroman1/aai-ui";
110
+ *
111
+ * function Footer() {
112
+ * return <Controls className="justify-end" />;
113
+ * }
110
114
  * ```
111
115
  *
112
116
  * @param className - Additional CSS class names applied to the container.
@@ -1 +1 @@
1
- import{a as e,o as t,t as n}from"./index-CGZajs1V.js";function r(e,t,n){if(e!==t)throw Error(`Browser refused the ${n} sample rate: asked for ${t} Hz, got ${e} Hz`)}function i(e){e.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{})}function a(e,t,n){let r=new AudioWorkletNode(e,`capture-processor`,{channelCount:1,channelCountMode:`explicit`}),i=null;return r.port.onmessage=e=>{let r=e.data;r.event===`chunk`&&r.buffer?t(r.buffer):r.event===`silent`?n?.():r.event===`stopped`&&(i?.(),i=null)},{node:r,start(){r.port.postMessage({event:`start`})},stop(){return new Promise(e=>{let t=setTimeout(e,250);i=()=>{clearTimeout(t),e()},r.port.postMessage({event:`stop`})})}}}async function o(o){let{sttSampleRate:s,ttsSampleRate:c,captureWorkletSrc:l,playbackWorkletSrc:u,onMicData:d,onError:f,onPlaybackStats:p,onMicSilent:m}=o,h=new AudioContext({sampleRate:c,latencyHint:`playback`}),g=s===c,_=g?h:new AudioContext({sampleRate:s,latencyHint:`interactive`});async function v(){let e=g?[h]:[h,_];await Promise.all(e.map(e=>e.close().catch(e=>{console.warn(`AudioContext close failed:`,e)})))}let y=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...n}}),b;try{[b]=await Promise.all([y,h.resume(),_.resume(),_.audioWorklet.addModule(l),h.audioWorklet.addModule(u)]),r(_.sampleRate,s,`capture`),r(h.sampleRate,c,`playback`)}catch(e){throw i(y),await v(),e}let x=_.createMediaStreamSource(b),S=a(_,d,m);x.connect(S.node),S.node.onprocessorerror=()=>{let e=Error(`Audio capture worklet crashed`);console.error(`[aai-ui]`,e.message),f?.(e)},S.start();let C=null,w=null,T=new AbortController;function E(){if(C)return C;let e=new AudioWorkletNode(h,`playback-processor`);return e.connect(h.destination),e.port.onmessage=e=>{if(e.data.event===`stop`){let t=e.data.stats;if(t&&t.concealedSamples>0&&p?.(t),e.data.reason===`interrupt`)return;w?.(),w=null}},e.onprocessorerror=()=>{let e=Error(`Audio playback worklet crashed`);console.error(`[aai-ui]`,e.message),w?.(),w=null,f?.(e)},C=e,e}let D={enqueue(e){T.signal.aborted||e.byteLength!==0&&E().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){return!C||(C.port.postMessage({event:`done`}),h.state!==`running`)?Promise.resolve():new Promise(n=>{w?.();let r=()=>{clearInterval(i),clearTimeout(a),w===r&&(w=null),n()},i=setInterval(()=>{h.state!==`running`&&r()},t),a=setTimeout(r,e);w=r})},flush(){C&&(w?.(),w=null,C.port.postMessage({event:`interrupt`}))},async close(){if(!T.signal.aborted){T.abort(),await S.stop(),x.disconnect(),S.node.disconnect(),C&&C.disconnect();for(let e of b.getTracks())e.stop();await v()}},async[Symbol.asyncDispose](){await D.close()}};return D}export{o as createVoiceIO};
1
+ import{a as e,o as t,t as n}from"./index-DyY8OeVc.js";function r(e,t,n){if(e!==t)throw Error(`Browser refused the ${n} sample rate: asked for ${t} Hz, got ${e} Hz`)}function i(e){e.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{})}function a(e,t,n){let r=new AudioWorkletNode(e,`capture-processor`,{channelCount:1,channelCountMode:`explicit`}),i=null;return r.port.onmessage=e=>{let r=e.data;r.event===`chunk`&&r.buffer?t(r.buffer):r.event===`silent`?n?.():r.event===`stopped`&&(i?.(),i=null)},{node:r,start(){r.port.postMessage({event:`start`})},stop(){return new Promise(e=>{let t=setTimeout(e,250);i=()=>{clearTimeout(t),e()},r.port.postMessage({event:`stop`})})}}}async function o(o){let{sttSampleRate:s,ttsSampleRate:c,captureWorkletSrc:l,playbackWorkletSrc:u,onMicData:d,onError:f,onPlaybackStats:p,onMicSilent:m}=o,h=new AudioContext({sampleRate:c,latencyHint:`playback`}),g=s===c,_=g?h:new AudioContext({sampleRate:s,latencyHint:`interactive`});async function v(){let e=g?[h]:[h,_];await Promise.all(e.map(e=>e.close().catch(e=>{console.warn(`AudioContext close failed:`,e)})))}let y=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...n}}),b;try{[b]=await Promise.all([y,h.resume(),_.resume(),_.audioWorklet.addModule(l),h.audioWorklet.addModule(u)]),r(_.sampleRate,s,`capture`),r(h.sampleRate,c,`playback`)}catch(e){throw i(y),await v(),e}let x=_.createMediaStreamSource(b),S=a(_,d,m);x.connect(S.node),S.node.onprocessorerror=()=>{let e=Error(`Audio capture worklet crashed`);console.error(`[aai-ui]`,e.message),f?.(e)},S.start();let C=null,w=null,T=new AbortController;function E(){if(C)return C;let e=new AudioWorkletNode(h,`playback-processor`);return e.connect(h.destination),e.port.onmessage=e=>{if(e.data.event===`stop`){let t=e.data.stats;if(t&&t.concealedSamples>0&&p?.(t),e.data.reason===`interrupt`)return;w?.(),w=null}},e.onprocessorerror=()=>{let e=Error(`Audio playback worklet crashed`);console.error(`[aai-ui]`,e.message),w?.(),w=null,f?.(e)},C=e,e}let D={enqueue(e){T.signal.aborted||e.byteLength!==0&&E().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){return!C||(C.port.postMessage({event:`done`}),h.state!==`running`)?Promise.resolve():new Promise(n=>{w?.();let r=()=>{clearInterval(i),clearTimeout(a),w===r&&(w=null),n()},i=setInterval(()=>{h.state!==`running`&&r()},t),a=setTimeout(r,e);w=r})},flush(){C&&(w?.(),w=null,C.port.postMessage({event:`interrupt`}))},async close(){if(!T.signal.aborted){T.abort(),await S.stop(),x.disconnect(),S.node.disconnect(),C&&C.disconnect();for(let e of b.getTracks())e.stop();await v()}},async[Symbol.asyncDispose](){await D.close()}};return D}export{o as createVoiceIO};
@@ -1,4 +1,4 @@
1
- import{n as e,r as t}from"./index-CGZajs1V.js";import{t as n}from"./_module-url-BX0RuRU2.js";var r=n(`
1
+ import{n as e,r as t}from"./index-DyY8OeVc.js";import{t as n}from"./_module-url-BX0RuRU2.js";var r=n(`
2
2
  class CaptureProcessor extends AudioWorkletProcessor {
3
3
  constructor(options) {
4
4
  super();