@alexkroman1/aai-ui 0.9.2 → 0.10.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 (39) hide show
  1. package/dist/_components/app.d.ts +19 -1
  2. package/dist/_components/app.js +19 -1
  3. package/dist/_components/button.d.ts +43 -4
  4. package/dist/_components/button.js +33 -9
  5. package/dist/_components/chat-view.d.ts +25 -1
  6. package/dist/_components/chat-view.js +25 -1
  7. package/dist/_components/controls.d.ts +15 -1
  8. package/dist/_components/controls.js +15 -1
  9. package/dist/_components/error-banner.d.ts +15 -1
  10. package/dist/_components/error-banner.js +15 -1
  11. package/dist/_components/message-bubble.d.ts +19 -3
  12. package/dist/_components/message-bubble.js +17 -1
  13. package/dist/_components/message-list.d.ts +18 -1
  14. package/dist/_components/message-list.js +20 -3
  15. package/dist/_components/state-indicator.d.ts +23 -1
  16. package/dist/_components/state-indicator.js +23 -1
  17. package/dist/_components/thinking-indicator.d.ts +12 -1
  18. package/dist/_components/thinking-indicator.js +23 -11
  19. package/dist/_components/tool-call-block.d.ts +21 -1
  20. package/dist/_components/tool-call-block.js +33 -11
  21. package/dist/_components/transcript.d.ts +19 -1
  22. package/dist/_components/transcript.js +19 -1
  23. package/dist/_sqlite-stub.d.ts +3 -0
  24. package/dist/audio.js +13 -3
  25. package/dist/client-handler.d.ts +43 -0
  26. package/dist/index.d.ts +23 -6
  27. package/dist/index.js +3 -4
  28. package/dist/mount.d.ts +4 -0
  29. package/dist/mount.js +7 -4
  30. package/dist/session-D0RdWlWH.js +451 -0
  31. package/dist/session.d.ts +5 -36
  32. package/dist/session.js +1 -358
  33. package/dist/signals.d.ts +48 -0
  34. package/dist/signals.js +84 -25
  35. package/dist/types.d.ts +19 -5
  36. package/package.json +12 -10
  37. package/styles.css +2 -3
  38. package/dist/components.d.ts +0 -31
  39. package/dist/components.js +0 -17
@@ -1,6 +1,24 @@
1
1
  import type * as preact from "preact";
2
2
  import type { Reactive } from "../types.ts";
3
- /** @public */
3
+ /**
4
+ * Live speech-to-text transcript shown while the user is speaking.
5
+ * Returns `null` when there is no active utterance.
6
+ *
7
+ * Displays a {@link ThinkingIndicator} while waiting for the first
8
+ * transcription text (i.e., `userUtterance` is `""`).
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const { session } = useSession();
13
+ * <Transcript userUtterance={session.userUtterance} />
14
+ * ```
15
+ *
16
+ * @param userUtterance - Reactive string (`null` when idle, `""` when
17
+ * listening but no text yet, or the transcript text).
18
+ * @param className - Additional CSS class names.
19
+ *
20
+ * @public
21
+ */
4
22
  export declare function Transcript({ userUtterance, className, }: {
5
23
  userUtterance: Reactive<string | null>;
6
24
  className?: string;
@@ -2,7 +2,25 @@ import { ThinkingIndicator } from "./thinking-indicator.js";
2
2
  import clsx from "clsx";
3
3
  import { jsx } from "preact/jsx-runtime";
4
4
  //#region _components/transcript.tsx
5
- /** @public */
5
+ /**
6
+ * Live speech-to-text transcript shown while the user is speaking.
7
+ * Returns `null` when there is no active utterance.
8
+ *
9
+ * Displays a {@link ThinkingIndicator} while waiting for the first
10
+ * transcription text (i.e., `userUtterance` is `""`).
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * const { session } = useSession();
15
+ * <Transcript userUtterance={session.userUtterance} />
16
+ * ```
17
+ *
18
+ * @param userUtterance - Reactive string (`null` when idle, `""` when
19
+ * listening but no text yet, or the transcript text).
20
+ * @param className - Additional CSS class names.
21
+ *
22
+ * @public
23
+ */
6
24
  function Transcript({ userUtterance, className }) {
7
25
  if (userUtterance.value === null) return null;
8
26
  return /* @__PURE__ */ jsx("div", {
@@ -0,0 +1,3 @@
1
+ export declare class DatabaseSync {
2
+ constructor();
3
+ }
package/dist/audio.js CHANGED
@@ -29,7 +29,9 @@ async function createVoiceIO(opts) {
29
29
  await Promise.all([ctx.audioWorklet.addModule(captureWorkletSrc), ctx.audioWorklet.addModule(playbackWorkletSrc)]);
30
30
  } catch (err) {
31
31
  for (const t of stream.getTracks()) t.stop();
32
- await ctx.close().catch(() => {});
32
+ await ctx.close().catch((err) => {
33
+ console.warn("AudioContext close failed:", err);
34
+ });
33
35
  throw err;
34
36
  }
35
37
  const mic = ctx.createMediaStreamSource(stream);
@@ -43,7 +45,9 @@ async function createVoiceIO(opts) {
43
45
  });
44
46
  mic.connect(capNode);
45
47
  const chunkSizeBytes = Math.floor(sttSampleRate * MIC_BUFFER_SECONDS) * 2;
46
- const capBuf = new Uint8Array(chunkSizeBytes * 2);
48
+ const capBufA = new Uint8Array(chunkSizeBytes * 2);
49
+ const capBufB = new Uint8Array(chunkSizeBytes * 2);
50
+ let capBuf = capBufA;
47
51
  let capOffset = 0;
48
52
  capNode.port.postMessage({ event: "start" });
49
53
  capNode.port.onmessage = (e) => {
@@ -52,8 +56,11 @@ async function createVoiceIO(opts) {
52
56
  capBuf.set(chunk, capOffset);
53
57
  capOffset += chunk.byteLength;
54
58
  if (capOffset >= chunkSizeBytes) {
55
- onMicData(capBuf.slice(0, capOffset).buffer);
59
+ const sendBuf = capBuf;
60
+ const filled = capOffset;
61
+ capBuf = capBuf === capBufA ? capBufB : capBufA;
56
62
  capOffset = 0;
63
+ onMicData(sendBuf.buffer.slice(0, filled));
57
64
  }
58
65
  };
59
66
  let playNode = null;
@@ -98,6 +105,9 @@ async function createVoiceIO(opts) {
98
105
  if (lifecycle.signal.aborted) return;
99
106
  lifecycle.abort();
100
107
  capNode.port.postMessage({ event: "stop" });
108
+ mic.disconnect();
109
+ capNode.disconnect();
110
+ if (playNode) playNode.disconnect();
101
111
  for (const t of stream.getTracks()) t.stop();
102
112
  await ctx.close().catch(() => {});
103
113
  },
@@ -0,0 +1,43 @@
1
+ import type { ReadyConfig } from "@alexkroman1/aai/protocol";
2
+ import type { VoiceIO } from "./audio.ts";
3
+ import type { AgentState, ChatMessage, Reactive, SessionError, ToolCallInfo } from "./types.ts";
4
+ type BatchFn = (fn: () => void) => void;
5
+ /**
6
+ * Handles server→client messages and updates reactive Preact signals
7
+ * accordingly (state transitions, transcripts, messages, audio playback).
8
+ *
9
+ * @internal Exported for testing only.
10
+ */
11
+ export declare class ClientHandler {
12
+ #private;
13
+ constructor(opts: {
14
+ state: Reactive<AgentState>;
15
+ messages: Reactive<ChatMessage[]>;
16
+ toolCalls: Reactive<ToolCallInfo[]>;
17
+ userUtterance: Reactive<string | null>;
18
+ agentUtterance: Reactive<string | null>;
19
+ error: Reactive<SessionError | null>;
20
+ voiceIO: () => VoiceIO | null;
21
+ batch: BatchFn;
22
+ });
23
+ /** Single entry point for all server→client session events. */
24
+ event(e: import("@alexkroman1/aai/protocol").ClientEvent): void;
25
+ /** Enqueue a PCM16 audio chunk for playback. Transitions state to `"speaking"` on the first chunk. */
26
+ playAudioChunk(chunk: Uint8Array): void;
27
+ /**
28
+ * Signal that the server has finished sending audio for this turn.
29
+ * Waits for the audio queue to drain, then transitions state to `"listening"`.
30
+ * Uses the `#generation` counter to discard stale completions from interrupted turns.
31
+ */
32
+ playAudioDone(): void;
33
+ /**
34
+ * Dispatch an incoming WebSocket message (text or binary).
35
+ *
36
+ * Returns the parsed config if the message is a `config` message,
37
+ * otherwise `null`.
38
+ */
39
+ handleMessage(data: string | ArrayBuffer): (ReadyConfig & {
40
+ sessionId?: string;
41
+ }) | null;
42
+ }
43
+ export {};
package/dist/index.d.ts CHANGED
@@ -2,10 +2,8 @@
2
2
  * Browser client library for AAI voice agents.
3
3
  *
4
4
  * Provides WebSocket session management, audio capture/playback,
5
- * and Preact UI components. For narrower imports, use the sub-path exports:
6
- *
7
- * - `@aai/ui/session` — WebSocket session only (no Preact dependency)
8
- * - `@aai/ui/components` — Preact components, mount helpers, and signals
5
+ * and Preact UI components. For a narrower import without the Preact
6
+ * dependency, use `@aai/ui/session`.
9
7
  *
10
8
  * @example
11
9
  * ```tsx
@@ -14,7 +12,26 @@
14
12
  * mount(App, { target: "#app" });
15
13
  * ```
16
14
  */
17
- export * from "./components.ts";
15
+ export { App } from "./_components/app.tsx";
16
+ export type { ButtonSize, ButtonVariant } from "./_components/button.tsx";
17
+ export { Button } from "./_components/button.tsx";
18
+ export { ChatView } from "./_components/chat-view.tsx";
19
+ export { Controls } from "./_components/controls.tsx";
20
+ export { ErrorBanner } from "./_components/error-banner.tsx";
21
+ export { MessageBubble } from "./_components/message-bubble.tsx";
22
+ export { MessageList } from "./_components/message-list.tsx";
23
+ export { SidebarLayout } from "./_components/sidebar-layout.tsx";
24
+ export { StartScreen } from "./_components/start-screen.tsx";
25
+ export { StateIndicator } from "./_components/state-indicator.tsx";
26
+ export { ThinkingIndicator } from "./_components/thinking-indicator.tsx";
27
+ export { ToolCallBlock } from "./_components/tool-call-block.tsx";
28
+ export { Transcript } from "./_components/transcript.tsx";
29
+ export type { MountHandle, MountOptions } from "./mount.tsx";
30
+ export { mount } from "./mount.tsx";
31
+ export type { MountConfig, MountTheme } from "./mount-context.ts";
32
+ export { useMountConfig } from "./mount-context.ts";
18
33
  export type { VoiceSession } from "./session.ts";
19
34
  export { createVoiceSession } from "./session.ts";
20
- export type { AgentState, Message, Reactive, SessionError, SessionErrorCode, SessionOptions, ToolCallInfo, } from "./types.ts";
35
+ export type { SessionSignals } from "./signals.ts";
36
+ export { createSessionControls, SessionProvider, useAutoScroll, useSession, useToolCallStart, useToolCallUpdate, useToolResult, } from "./signals.ts";
37
+ export type { AgentState, ChatMessage, Reactive, SessionError, SessionErrorCode, ToolCallInfo, VoiceSessionOptions, } from "./types.ts";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useMountConfig } from "./mount-context.js";
2
- import { SessionProvider, createSessionControls, useAutoScroll, useSession, useToolResult } from "./signals.js";
2
+ import { SessionProvider, createSessionControls, useAutoScroll, useSession, useToolCallStart, useToolCallUpdate, useToolResult } from "./signals.js";
3
3
  import { Button } from "./_components/button.js";
4
4
  import { Controls } from "./_components/controls.js";
5
5
  import { ErrorBanner } from "./_components/error-banner.js";
@@ -13,7 +13,6 @@ import { ChatView } from "./_components/chat-view.js";
13
13
  import { StartScreen } from "./_components/start-screen.js";
14
14
  import { App } from "./_components/app.js";
15
15
  import { SidebarLayout } from "./_components/sidebar-layout.js";
16
- import { createVoiceSession } from "./session.js";
16
+ import { t as createVoiceSession } from "./session-D0RdWlWH.js";
17
17
  import { mount } from "./mount.js";
18
- import "./components.js";
19
- export { App, Button, ChatView, Controls, ErrorBanner, MessageBubble, MessageList, SessionProvider, SidebarLayout, StartScreen, StateIndicator, ThinkingIndicator, ToolCallBlock, Transcript, createSessionControls, createVoiceSession, mount, useAutoScroll, useMountConfig, useSession, useToolResult };
18
+ export { App, Button, ChatView, Controls, ErrorBanner, MessageBubble, MessageList, SessionProvider, SidebarLayout, StartScreen, StateIndicator, ThinkingIndicator, ToolCallBlock, Transcript, createSessionControls, createVoiceSession, mount, useAutoScroll, useMountConfig, useSession, useToolCallStart, useToolCallUpdate, useToolResult };
package/dist/mount.d.ts CHANGED
@@ -16,6 +16,10 @@ export type MountOptions = {
16
16
  title?: string;
17
17
  /** Theme color overrides. */
18
18
  theme?: MountTheme;
19
+ /** Called when the server sends a session ID. Store it for reconnection. */
20
+ onSessionId?: ((sessionId: string) => void) | undefined;
21
+ /** Session ID from a previous connection for resuming persisted state. */
22
+ resumeSessionId?: string | undefined;
19
23
  };
20
24
  /**
21
25
  * Handle returned by {@link mount} for cleanup.
package/dist/mount.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { MountConfigProvider } from "./mount-context.js";
2
2
  import { SessionProvider, createSessionControls } from "./signals.js";
3
- import { createVoiceSession } from "./session.js";
3
+ import { t as createVoiceSession } from "./session-D0RdWlWH.js";
4
4
  import { render } from "preact";
5
5
  import { batch, signal } from "@preact/signals";
6
6
  import { jsx } from "preact/jsx-runtime";
7
7
  //#region mount.tsx
8
8
  function resolveContainer(target = "#app") {
9
- const el = typeof target === "string" ? document.querySelector(target) : target;
9
+ if (typeof target !== "string") return target;
10
+ const el = document.querySelector(target);
10
11
  if (!el) throw new Error(`Element not found: ${target}`);
11
12
  return el;
12
13
  }
@@ -28,8 +29,10 @@ function mount(Component, options) {
28
29
  const container = resolveContainer(options?.target);
29
30
  const session = createVoiceSession({
30
31
  platformUrl: options?.platformUrl ?? globalThis.location.origin + globalThis.location.pathname,
31
- signal,
32
- batch
32
+ reactiveFactory: signal,
33
+ batch,
34
+ onSessionId: options?.onSessionId,
35
+ resumeSessionId: options?.resumeSessionId
33
36
  });
34
37
  const signals = createSessionControls(session);
35
38
  const mountConfig = {