@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,2 @@
1
+ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-sm:24rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html,body{margin:0;padding:0}}@layer components;@layer utilities{.static{position:static}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing) * 0)}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-flex{display:inline-flex}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-4{height:calc(var(--spacing) * 4)}.h-8{height:calc(var(--spacing) * 8)}.h-screen{height:100vh}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-5{min-height:calc(var(--spacing) * 5)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-4{width:calc(var(--spacing) * 4)}.w-fit{width:fit-content}.w-full{width:100%}.max-w-130{max-width:calc(var(--spacing) * 130)}.max-w-\[min\(82\%\,64ch\)\]{max-width:min(82%,64ch)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.cursor-pointer{cursor:pointer}.appearance-none{appearance:none}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-4\.5{gap:calc(var(--spacing) * 4.5)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-12{padding-inline:calc(var(--spacing) * 12)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-10{padding-block:calc(var(--spacing) * 10)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[13px\]{font-size:13px}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-\[1\.1\]{--tw-leading:1.1;line-height:1.1}.leading-\[130\%\]{--tw-leading:130%;line-height:130%}.leading-\[150\%\]{--tw-leading:150%;line-height:150%}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.wrap-break-word{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.capitalize{text-transform:capitalize}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\[scrollbar-width\:none\]{scrollbar-width:none}}@keyframes aai-bounce{0%,80%,to{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}@keyframes aai-shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.tool-shimmer{-webkit-text-fill-color:transparent;background:linear-gradient(90deg,currentColor 25%,#0000 50%,currentColor 75%) 0 0/200% 100%;-webkit-background-clip:text;background-clip:text;animation:2s infinite aai-shimmer}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
@@ -0,0 +1,101 @@
1
+ var e=new Blob([`
2
+ class PlaybackProcessor extends AudioWorkletProcessor {
3
+ constructor(options) {
4
+ super();
5
+ this.interrupted = false;
6
+ this.isDone = false;
7
+ this.playing = false;
8
+ const rate = options.processorOptions?.sampleRate ?? 24000;
9
+ // Wait for ~400ms of audio before starting.
10
+ // If 'done' arrives first (short utterance), start immediately.
11
+ this.jitterSamples = Math.floor(rate * 0.4);
12
+ // Carry-over byte for split samples across chunks
13
+ this.carry = null;
14
+ // Float32 sample buffer — 60s at the context sample rate
15
+ this.samples = new Float32Array(rate * 60);
16
+ this.writePos = 0;
17
+ this.readPos = 0;
18
+ // Report playback position every ~50ms for word-level text sync
19
+ this.progressInterval = Math.floor(rate * 0.05);
20
+ this.samplesSinceProgress = 0;
21
+
22
+ this.port.onmessage = (e) => {
23
+ const d = e.data;
24
+ if (d.event === 'write') {
25
+ this.ingestBytes(d.buffer);
26
+ } else if (d.event === 'interrupt') {
27
+ this.interrupted = true;
28
+ } else if (d.event === 'done') {
29
+ this.isDone = true;
30
+ }
31
+ };
32
+ }
33
+
34
+ ingestBytes(uint8) {
35
+ let bytes = uint8;
36
+
37
+ if (this.carry !== null) {
38
+ const merged = new Uint8Array(1 + bytes.length);
39
+ merged[0] = this.carry;
40
+ merged.set(bytes, 1);
41
+ bytes = merged;
42
+ this.carry = null;
43
+ }
44
+
45
+ if (bytes.length % 2 !== 0) {
46
+ this.carry = bytes[bytes.length - 1];
47
+ bytes = bytes.subarray(0, bytes.length - 1);
48
+ }
49
+
50
+ if (bytes.length === 0) return;
51
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);
52
+ const numSamples = bytes.length / 2;
53
+ for (let i = 0; i < numSamples; i++) {
54
+ this.samples[this.writePos++] = view.getInt16(i * 2, true) / 0x8000;
55
+ }
56
+ }
57
+
58
+ process(inputs, outputs) {
59
+ const out = outputs[0][0];
60
+ if (this.interrupted) {
61
+ this.port.postMessage({ event: 'stop' });
62
+ return false;
63
+ }
64
+
65
+ const avail = this.writePos - this.readPos;
66
+
67
+ // Wait for jitter buffer to fill, unless done (short utterance)
68
+ if (!this.playing) {
69
+ if (avail >= this.jitterSamples || this.isDone) {
70
+ this.playing = true;
71
+ } else {
72
+ out.fill(0);
73
+ return true;
74
+ }
75
+ }
76
+
77
+ if (avail > 0) {
78
+ const n = Math.min(avail, out.length);
79
+ out.set(this.samples.subarray(this.readPos, this.readPos + n));
80
+ this.readPos += n;
81
+ this.samplesSinceProgress += n;
82
+ if (this.samplesSinceProgress >= this.progressInterval) {
83
+ this.port.postMessage({ event: 'progress', readPos: this.readPos });
84
+ this.samplesSinceProgress = 0;
85
+ }
86
+ out.fill(0, n);
87
+ return true;
88
+ }
89
+
90
+ // No data: output silence, stop only when done
91
+ out.fill(0);
92
+ if (this.isDone) {
93
+ this.port.postMessage({ event: 'stop' });
94
+ return false;
95
+ }
96
+ return true;
97
+ }
98
+ }
99
+
100
+ registerProcessor('playback-processor', PlaybackProcessor);
101
+ `],{type:`application/javascript`}),t=URL.createObjectURL(e);export{t as default};
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
+ <title>aai</title>
7
+ <link rel="icon" href="data:," />
8
+ <style>html, body { background: #101010; margin: 0; }</style>
9
+ <script type="module" crossorigin src="./assets/default-client-AeJhjHfj.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/default-client-BK1ItCvw.css">
11
+ </head>
12
+ <body>
13
+ <main id="app"></main>
14
+ </body>
15
+ </html>
@@ -0,0 +1,2 @@
1
+ /** @jsxImportSource react */
2
+ import "./styles.css";
@@ -1,41 +1,73 @@
1
- import type { ComponentType } from "preact";
2
- import { type ClientTheme } from "./client-context.ts";
3
- import { type VoiceSession, type WebSocketConstructor } from "./session.ts";
4
- import { type SessionSignals } from "./signals.ts";
1
+ /** @jsxImportSource react */
2
+ import { type ComponentType } from "react";
3
+ import { type ToolDisplayConfig } from "./components/tool-config-context.ts";
4
+ import { type SessionCore, type WebSocketConstructor } from "./session-core.ts";
5
+ import type { ClientTheme } from "./types.ts";
5
6
  /**
6
- * Options for {@link defineClient}.
7
+ * Base options shared by both client tiers.
7
8
  *
8
9
  * @public
9
10
  */
10
- export type ClientOptions = {
11
+ type BaseOptions = {
11
12
  /** CSS selector or DOM element to render into. Defaults to `"#app"`. */
12
13
  target?: string | HTMLElement;
13
14
  /** Base URL of the AAI platform server. Derived from `location.href` by default. */
14
15
  platformUrl?: string;
15
- /** Agent title shown in the header and start screen. */
16
- title?: string;
17
16
  /** Theme color overrides. */
18
17
  theme?: ClientTheme;
19
18
  /** Called when the server sends a session ID. Store it for reconnection. */
20
- onSessionId?: ((sessionId: string) => void) | undefined;
19
+ onSessionId?: (sessionId: string) => void;
21
20
  /** Session ID from a previous connection for resuming persisted state. */
22
- resumeSessionId?: string | undefined;
23
- /** WebSocket constructor override. Passed through to VoiceSessionOptions. */
24
- WebSocket?: WebSocketConstructor | undefined;
21
+ resumeSessionId?: string;
22
+ /** WebSocket constructor override. Passed through to session options. */
23
+ WebSocket?: WebSocketConstructor;
25
24
  };
26
25
  /**
27
- * Handle returned by {@link defineClient} for cleanup.
26
+ * Tier 1: Config-only options. Renders the default shell (StartScreen + ChatView).
27
+ *
28
+ * @public
29
+ */
30
+ type ConfigTier = BaseOptions & {
31
+ component?: never;
32
+ /** Agent name shown in the header and start screen. */
33
+ name?: string;
34
+ /** Optional sidebar component rendered alongside the chat view. */
35
+ sidebar?: ComponentType;
36
+ /** CSS width of the sidebar. Defaults to `"18rem"`. */
37
+ sidebarWidth?: string;
38
+ /** Tool display config: icon and label overrides keyed by tool name. */
39
+ tools?: ToolDisplayConfig;
40
+ };
41
+ /**
42
+ * Tier 2: Custom component. Renders the provided component inside the providers.
43
+ *
44
+ * @public
45
+ */
46
+ type ComponentTier = BaseOptions & {
47
+ /** Full custom component to render instead of the default shell. */
48
+ component: ComponentType;
49
+ name?: never;
50
+ sidebar?: never;
51
+ sidebarWidth?: never;
52
+ tools?: never;
53
+ };
54
+ /**
55
+ * Configuration passed to {@link client}.
56
+ *
57
+ * @public
58
+ */
59
+ export type ClientConfig = ConfigTier | ComponentTier;
60
+ /**
61
+ * Handle returned by {@link client} for cleanup.
28
62
  *
29
63
  * Implements `Disposable` so it can be used with `using`.
30
64
  *
31
65
  * @public
32
66
  */
33
67
  export type ClientHandle = {
34
- /** The underlying voice session. */
35
- session: VoiceSession;
36
- /** Reactive session controls for the mounted UI. */
37
- signals: SessionSignals;
38
- /** Unmount the UI, remove injected styles, and disconnect the session. */
68
+ /** The underlying session core. */
69
+ session: SessionCore;
70
+ /** Unmount the UI and disconnect the session. */
39
71
  dispose(): void;
40
72
  /** Alias for `dispose` for use with `using`. */
41
73
  [Symbol.dispose](): void;
@@ -43,15 +75,31 @@ export type ClientHandle = {
43
75
  /**
44
76
  * Define and mount a client UI for a voice agent.
45
77
  *
46
- * Creates a {@link VoiceSession}, wraps it in
47
- * {@link SessionSignals}, and renders the component
48
- * inside a {@link SessionProvider}.
78
+ * **Tier 1 (config-only):** Pass options without `component` to get the
79
+ * default shell (StartScreen + ChatView, optional sidebar).
80
+ *
81
+ * **Tier 2 (custom component):** Pass `component` to render a fully custom
82
+ * root component inside the providers.
83
+ *
84
+ * @example Tier 1
85
+ * ```tsx
86
+ * client({
87
+ * name: "Pizza Ordering",
88
+ * theme: { bg: "#1a1a1a", primary: "#e55" },
89
+ * sidebar: OrderPanel,
90
+ * tools: { add_pizza: { icon: "🍕", label: "Adding pizza" } },
91
+ * });
92
+ * ```
93
+ *
94
+ * @example Tier 2
95
+ * ```tsx
96
+ * client({ component: MyCustomApp });
97
+ * ```
49
98
  *
50
- * @param Component - The Preact component to render.
51
- * @param options - Client options (target element, platform URL, theme).
52
99
  * @returns A {@link ClientHandle} for cleanup.
53
100
  * @throws If the target element is not found in the DOM.
54
101
  *
55
102
  * @public
56
103
  */
57
- export declare function defineClient(Component: ComponentType<any>, options?: ClientOptions): ClientHandle;
104
+ export declare function client(config: ClientConfig): ClientHandle;
105
+ export {};
@@ -1,10 +1,15 @@
1
- import { ClientConfigProvider } from "./client-context.js";
2
- import { SessionProvider, createSessionControls } from "./signals.js";
3
- import { t as createVoiceSession } from "./session-CWB7vmqz.js";
4
- import { render } from "preact";
5
- import { batch, signal } from "@preact/signals";
6
- import { jsx } from "preact/jsx-runtime";
1
+ import { SessionProvider, ThemeProvider } from "./context.js";
2
+ import { n as ToolConfigContext } from "./tool-call-block-CCuChsFm.js";
3
+ import { ChatView } from "./components/chat-view.js";
4
+ import { SidebarLayout } from "./components/sidebar-layout.js";
5
+ import { StartScreen } from "./components/start-screen.js";
6
+ import { createSessionCore } from "./session-core.js";
7
+ import { createElement } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ import { flushSync } from "react-dom";
10
+ import { createRoot } from "react-dom/client";
7
11
  //#region define-client.tsx
12
+ /** @jsxImportSource react */
8
13
  function resolveContainer(target = "#app") {
9
14
  if (typeof target !== "string") return target;
10
15
  const el = document.querySelector(target);
@@ -12,57 +17,78 @@ function resolveContainer(target = "#app") {
12
17
  return el;
13
18
  }
14
19
  /**
20
+ * Default shell rendered in config tier.
21
+ * Wraps StartScreen → (SidebarLayout →) ChatView.
22
+ */
23
+ function DefaultShell({ name, Sidebar, sidebarWidth }) {
24
+ const chat = /* @__PURE__ */ jsx(ChatView, { ...name !== void 0 ? { title: name } : {} });
25
+ const inner = Sidebar ? /* @__PURE__ */ jsx(SidebarLayout, {
26
+ sidebar: /* @__PURE__ */ jsx(Sidebar, {}),
27
+ ...sidebarWidth !== void 0 ? { sidebarWidth } : {},
28
+ children: chat
29
+ }) : chat;
30
+ return /* @__PURE__ */ jsx(StartScreen, {
31
+ ...name !== void 0 ? { title: name } : {},
32
+ children: inner
33
+ });
34
+ }
35
+ /**
15
36
  * Define and mount a client UI for a voice agent.
16
37
  *
17
- * Creates a {@link VoiceSession}, wraps it in
18
- * {@link SessionSignals}, and renders the component
19
- * inside a {@link SessionProvider}.
38
+ * **Tier 1 (config-only):** Pass options without `component` to get the
39
+ * default shell (StartScreen + ChatView, optional sidebar).
40
+ *
41
+ * **Tier 2 (custom component):** Pass `component` to render a fully custom
42
+ * root component inside the providers.
43
+ *
44
+ * @example Tier 1
45
+ * ```tsx
46
+ * client({
47
+ * name: "Pizza Ordering",
48
+ * theme: { bg: "#1a1a1a", primary: "#e55" },
49
+ * sidebar: OrderPanel,
50
+ * tools: { add_pizza: { icon: "🍕", label: "Adding pizza" } },
51
+ * });
52
+ * ```
53
+ *
54
+ * @example Tier 2
55
+ * ```tsx
56
+ * client({ component: MyCustomApp });
57
+ * ```
20
58
  *
21
- * @param Component - The Preact component to render.
22
- * @param options - Client options (target element, platform URL, theme).
23
59
  * @returns A {@link ClientHandle} for cleanup.
24
60
  * @throws If the target element is not found in the DOM.
25
61
  *
26
62
  * @public
27
63
  */
28
- function defineClient(Component, options) {
29
- const container = resolveContainer(options?.target);
30
- const session = createVoiceSession({
31
- platformUrl: options?.platformUrl ?? globalThis.location.origin + globalThis.location.pathname,
32
- reactiveFactory: signal,
33
- batch,
34
- onSessionId: options?.onSessionId,
35
- resumeSessionId: options?.resumeSessionId,
36
- ...options?.WebSocket ? { WebSocket: options.WebSocket } : {}
64
+ function client(config) {
65
+ const container = resolveContainer(config.target);
66
+ const session = createSessionCore({
67
+ platformUrl: config.platformUrl ?? globalThis.location.origin + globalThis.location.pathname,
68
+ onSessionId: config.onSessionId,
69
+ resumeSessionId: config.resumeSessionId,
70
+ ...config.WebSocket ? { WebSocket: config.WebSocket } : {}
37
71
  });
38
- const signals = createSessionControls(session);
39
- const clientConfig = {
40
- title: options?.title,
41
- theme: options?.theme
42
- };
43
- if (options?.theme) {
44
- const t = options.theme;
45
- const el = container;
46
- if (t.bg) el.style.setProperty("--color-aai-bg", t.bg);
47
- if (t.primary) el.style.setProperty("--color-aai-primary", t.primary);
48
- if (t.text) el.style.setProperty("--color-aai-text", t.text);
49
- if (t.surface) el.style.setProperty("--color-aai-surface", t.surface);
50
- if (t.border) el.style.setProperty("--color-aai-border", t.border);
72
+ let RootComponent;
73
+ if ("component" in config && config.component) RootComponent = config.component;
74
+ else {
75
+ const { name, sidebar: Sidebar, sidebarWidth } = config;
76
+ RootComponent = () => createElement(DefaultShell, {
77
+ ...name !== void 0 ? { name } : {},
78
+ ...Sidebar !== void 0 ? { Sidebar } : {},
79
+ ...sidebarWidth !== void 0 ? { sidebarWidth } : {}
80
+ });
51
81
  }
52
- render(/* @__PURE__ */ jsx(ClientConfigProvider, {
53
- value: clientConfig,
54
- children: /* @__PURE__ */ jsx(SessionProvider, {
55
- value: signals,
56
- children: /* @__PURE__ */ jsx(Component, {})
57
- })
58
- }), container);
82
+ const toolConfig = "tools" in config && config.tools ? config.tools : {};
83
+ const root = createRoot(container);
84
+ flushSync(() => {
85
+ root.render(createElement(ToolConfigContext.Provider, { value: toolConfig }, createElement(ThemeProvider, config.theme !== void 0 ? { value: config.theme } : {}, createElement(SessionProvider, { value: session }, createElement(RootComponent)))));
86
+ });
59
87
  const handle = {
60
88
  session,
61
- signals,
62
89
  dispose() {
63
- render(null, container);
64
- signals.dispose();
65
- session.disconnect();
90
+ root.unmount();
91
+ session[Symbol.dispose]();
66
92
  },
67
93
  [Symbol.dispose]() {
68
94
  handle.dispose();
@@ -71,4 +97,4 @@ function defineClient(Component, options) {
71
97
  return handle;
72
98
  }
73
99
  //#endregion
74
- export { defineClient };
100
+ export { client };
@@ -0,0 +1,5 @@
1
+ import type { ToolCallInfo } from "./types.ts";
2
+ export declare function useToolResult<R = unknown>(toolName: string, callback: (result: R, toolCall: ToolCallInfo) => void): void;
3
+ export declare function useToolResult(callback: (name: string, result: unknown, toolCall: ToolCallInfo) => void): void;
4
+ export declare function useToolCallStart(toolName: string, callback: (toolCall: ToolCallInfo) => void): void;
5
+ export declare function useToolCallStart(callback: (toolCall: ToolCallInfo) => void): void;
package/dist/hooks.js ADDED
@@ -0,0 +1,55 @@
1
+ import { useSession } from "./context.js";
2
+ import { useEffect, useRef } from "react";
3
+ //#region hooks.ts
4
+ function tryParseJSON(str) {
5
+ if (!str) return str;
6
+ try {
7
+ return JSON.parse(str);
8
+ } catch {
9
+ return str;
10
+ }
11
+ }
12
+ /**
13
+ * Shared effect loop: fires `onNew` once per tool call that passes `shouldProcess`.
14
+ * Clears seen IDs when toolCalls is empty (session reset).
15
+ */
16
+ function processToolCalls(toolCalls, seen, shouldProcess, onNew) {
17
+ if (toolCalls.length === 0) {
18
+ seen.clear();
19
+ return;
20
+ }
21
+ for (const tc of toolCalls) {
22
+ if (!shouldProcess(tc)) continue;
23
+ if (seen.has(tc.callId)) continue;
24
+ seen.add(tc.callId);
25
+ onNew(tc);
26
+ }
27
+ }
28
+ function useToolResult(...args) {
29
+ const filterName = typeof args[0] === "string" ? args[0] : null;
30
+ const callback = typeof args[0] === "string" ? args[1] : args[0];
31
+ const session = useSession();
32
+ const seenRef = useRef(/* @__PURE__ */ new Set());
33
+ const callbackRef = useRef(callback);
34
+ callbackRef.current = callback;
35
+ useEffect(() => {
36
+ processToolCalls(session.toolCalls, seenRef.current, (tc) => tc.status === "done" && (!filterName || tc.name === filterName), (tc) => {
37
+ const parsed = tryParseJSON(tc.result);
38
+ if (filterName) callbackRef.current(parsed, tc);
39
+ else callbackRef.current(tc.name, parsed, tc);
40
+ });
41
+ }, [session.toolCalls, filterName]);
42
+ }
43
+ function useToolCallStart(...args) {
44
+ const filterName = typeof args[0] === "string" ? args[0] : null;
45
+ const callback = typeof args[0] === "string" ? args[1] : args[0];
46
+ const session = useSession();
47
+ const seenRef = useRef(/* @__PURE__ */ new Set());
48
+ const callbackRef = useRef(callback);
49
+ callbackRef.current = callback;
50
+ useEffect(() => {
51
+ processToolCalls(session.toolCalls, seenRef.current, (tc) => tc.status === "pending" && (!filterName || tc.name === filterName), (tc) => callbackRef.current(tc));
52
+ }, [session.toolCalls, filterName]);
53
+ }
54
+ //#endregion
55
+ export { useToolCallStart, useToolResult };
package/dist/index.d.ts CHANGED
@@ -1,37 +1,15 @@
1
- /**
2
- * Browser client library for AAI voice agents.
3
- *
4
- * Provides WebSocket session management, audio capture/playback,
5
- * and Preact UI components. For a narrower import without the Preact
6
- * dependency, use `@aai/ui/session`.
7
- *
8
- * @example
9
- * ```tsx
10
- * import { App, defineClient } from "@aai/ui";
11
- *
12
- * defineClient(App, { target: "#app" });
13
- * ```
14
- */
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 { ClientConfig, ClientTheme } from "./client-context.ts";
30
- export { useClientConfig } from "./client-context.ts";
31
- export type { ClientHandle, ClientOptions } from "./define-client.tsx";
32
- export { defineClient } from "./define-client.tsx";
33
- export type { VoiceSession } from "./session.ts";
34
- export { createVoiceSession } from "./session.ts";
35
- export type { SessionSignals } from "./signals.ts";
36
- export { createSessionControls, SessionProvider, useAutoScroll, useSession, useToolCallStart, useToolResult, } from "./signals.ts";
37
- export type { AgentState, ChatMessage, Reactive, SessionError, SessionErrorCode, ToolCallInfo, VoiceSessionOptions, } from "./types.ts";
1
+ export { Button } from "./components/button.tsx";
2
+ export { ChatView } from "./components/chat-view.tsx";
3
+ export { MessageList } from "./components/message-list.tsx";
4
+ export { SidebarLayout } from "./components/sidebar-layout.tsx";
5
+ export { StartScreen } from "./components/start-screen.tsx";
6
+ export type { ToolDisplayConfig } from "./components/tool-config-context.ts";
7
+ export { ToolConfigContext } from "./components/tool-config-context.ts";
8
+ export type { Session } from "./context.ts";
9
+ export { SessionProvider, ThemeProvider, useSession, useTheme } from "./context.ts";
10
+ export type { ClientConfig, ClientHandle, } from "./define-client.tsx";
11
+ export { client } from "./define-client.tsx";
12
+ export { useToolCallStart, useToolResult } from "./hooks.ts";
13
+ export type { SessionCore, SessionCoreOptions, SessionSnapshot, } from "./session-core.ts";
14
+ export { createSessionCore } from "./session-core.ts";
15
+ export type { AgentState, ChatMessage, ClientTheme, SessionError, SessionErrorCode, ToolCallInfo, VoiceSessionOptions, WebSocketConstructor, } from "./types.ts";
package/dist/index.js CHANGED
@@ -1,18 +1,11 @@
1
- import { useClientConfig } from "./client-context.js";
2
- import { SessionProvider, createSessionControls, useAutoScroll, useSession, useToolCallStart, useToolResult } from "./signals.js";
3
- import { Button } from "./_components/button.js";
4
- import { Controls } from "./_components/controls.js";
5
- import { ErrorBanner } from "./_components/error-banner.js";
6
- import { MessageBubble } from "./_components/message-bubble.js";
7
- import { ThinkingIndicator } from "./_components/thinking-indicator.js";
8
- import { ToolCallBlock } from "./_components/tool-call-block.js";
9
- import { Transcript } from "./_components/transcript.js";
10
- import { MessageList } from "./_components/message-list.js";
11
- import { StateIndicator } from "./_components/state-indicator.js";
12
- import { ChatView } from "./_components/chat-view.js";
13
- import { StartScreen } from "./_components/start-screen.js";
14
- import { App } from "./_components/app.js";
15
- import { SidebarLayout } from "./_components/sidebar-layout.js";
16
- import { t as createVoiceSession } from "./session-CWB7vmqz.js";
17
- import { defineClient } from "./define-client.js";
18
- export { App, Button, ChatView, Controls, ErrorBanner, MessageBubble, MessageList, SessionProvider, SidebarLayout, StartScreen, StateIndicator, ThinkingIndicator, ToolCallBlock, Transcript, createSessionControls, createVoiceSession, defineClient, useAutoScroll, useClientConfig, useSession, useToolCallStart, useToolResult };
1
+ import { SessionProvider, ThemeProvider, useSession, useTheme } from "./context.js";
2
+ import { Button } from "./components/button.js";
3
+ import { n as ToolConfigContext } from "./tool-call-block-CCuChsFm.js";
4
+ import { MessageList } from "./components/message-list.js";
5
+ import { ChatView } from "./components/chat-view.js";
6
+ import { SidebarLayout } from "./components/sidebar-layout.js";
7
+ import { StartScreen } from "./components/start-screen.js";
8
+ import { createSessionCore } from "./session-core.js";
9
+ import { client } from "./define-client.js";
10
+ import { useToolCallStart, useToolResult } from "./hooks.js";
11
+ export { Button, ChatView, MessageList, SessionProvider, SidebarLayout, StartScreen, ThemeProvider, ToolConfigContext, client, createSessionCore, useSession, useTheme, useToolCallStart, useToolResult };
@@ -0,0 +1,70 @@
1
+ import type { AgentState, ChatMessage, SessionError, ToolCallInfo, VoiceSessionOptions } from "./types.ts";
2
+ export type { AgentState, ChatMessage, SessionError, SessionErrorCode, ToolCallInfo, VoiceSessionOptions, WebSocketConstructor, } from "./types.ts";
3
+ /**
4
+ * Immutable snapshot of the session state.
5
+ *
6
+ * Consumers (e.g. React hooks via `useSyncExternalStore`) read this to render.
7
+ * A new object reference is created on every state change.
8
+ *
9
+ * @public
10
+ */
11
+ export type SessionSnapshot = {
12
+ readonly state: AgentState;
13
+ readonly messages: ChatMessage[];
14
+ readonly toolCalls: ToolCallInfo[];
15
+ readonly userTranscript: string | null;
16
+ readonly agentTranscript: string | null;
17
+ readonly error: SessionError | null;
18
+ readonly started: boolean;
19
+ readonly running: boolean;
20
+ };
21
+ /**
22
+ * A framework-agnostic voice session that manages WebSocket communication,
23
+ * audio capture/playback, and agent state transitions.
24
+ *
25
+ * Uses a subscribe/getSnapshot pattern (compatible with React's
26
+ * `useSyncExternalStore`). Implements `Disposable` for resource cleanup.
27
+ *
28
+ * @public
29
+ */
30
+ export type SessionCore = {
31
+ /** Return the current immutable state snapshot. */
32
+ getSnapshot(): SessionSnapshot;
33
+ /** Subscribe to state changes. Returns an unsubscribe function. */
34
+ subscribe(callback: () => void): () => void;
35
+ /**
36
+ * Open a WebSocket connection to the server and begin audio capture.
37
+ * @param options - Optional. `signal` is an AbortSignal that, when aborted, disconnects the session.
38
+ */
39
+ connect(options?: {
40
+ signal?: AbortSignal;
41
+ }): void;
42
+ /** Cancel the current agent turn and discard in-flight TTS audio. */
43
+ cancel(): void;
44
+ /** Clear messages, transcript, and error state without disconnecting. */
45
+ resetState(): void;
46
+ /** Reset the session: clear state and reconnect. */
47
+ reset(): void;
48
+ /** Close the WebSocket and release all audio resources. */
49
+ disconnect(): void;
50
+ /** Start the session for the first time (sets `started` and `running`). */
51
+ start(): void;
52
+ /** Toggle between connected and disconnected states. */
53
+ toggle(): void;
54
+ /** Alias for `disconnect` for use with `using`. */
55
+ [Symbol.dispose](): void;
56
+ };
57
+ export type SessionCoreOptions = VoiceSessionOptions;
58
+ /**
59
+ * Create a framework-agnostic voice session core that connects to an AAI
60
+ * server via WebSocket.
61
+ *
62
+ * Uses a subscribe/getSnapshot pattern for state management, compatible with
63
+ * React's `useSyncExternalStore` and other external store integrations.
64
+ *
65
+ * @param options - Session configuration including the platform server URL.
66
+ * @returns A {@link SessionCore} handle for controlling the session.
67
+ *
68
+ * @public
69
+ */
70
+ export declare function createSessionCore(options: SessionCoreOptions): SessionCore;