@alexkroman1/aai-ui 1.12.0 → 1.14.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.
@@ -2,7 +2,7 @@ import { useSessionCore, useSessionSelector, useTheme } from "./context.js";
2
2
  import { Button } from "./components/button.js";
3
3
  import { r as TEXT_FAINT, t as ERROR_COLOR } from "./_colors-DYX7XRTr.js";
4
4
  import { t as AaiLogo } from "./aai-logo-B8lDmsut.js";
5
- import { r as SessionUrlChips, t as Controls } from "./controls-B2EPUJDU.js";
5
+ import { r as SessionUrlChips, t as Controls } from "./controls-BbZcmnJf.js";
6
6
  import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
7
7
  import { MessageList } from "./components/message-list.js";
8
8
  import clsx from "clsx";
@@ -96,7 +96,12 @@ const TextControls = memo(function TextControls({ className }) {
96
96
  //#region components/chat-view.tsx
97
97
  /** @jsxImportSource react */
98
98
  const PULSING_STATES = /* @__PURE__ */ new Set(["listening", "speaking"]);
99
- /** Indicator dot color per state, on the light refresh palette. */
99
+ /**
100
+ * Indicator dot color per state, on the light refresh palette. Shared with
101
+ * the sync-transport chat shell's status eyebrow.
102
+ *
103
+ * @internal
104
+ */
100
105
  function stateColor(state, primary) {
101
106
  switch (state) {
102
107
  case "listening":
@@ -185,4 +190,4 @@ function ChatView({ icon, title, className }) {
185
190
  });
186
191
  }
187
192
  //#endregion
188
- export { TextControls as n, ChatView as t };
193
+ export { stateColor as n, TextControls as r, ChatView as t };
@@ -1,4 +1,12 @@
1
1
  import type { ReactNode } from "react";
2
+ import type { AgentState } from "../types.ts";
3
+ /**
4
+ * Indicator dot color per state, on the light refresh palette. Shared with
5
+ * the sync-transport chat shell's status eyebrow.
6
+ *
7
+ * @internal
8
+ */
9
+ export declare function stateColor(state: AgentState, primary: string): string;
2
10
  /**
3
11
  * The main chat interface for a voice agent session — the design-system
4
12
  * "voice agent console": a 760px column on the cream page with a header
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as ChatView } from "../chat-view-u3yBAlig.js";
3
- export { ChatView };
2
+ import { n as stateColor, t as ChatView } from "../chat-view-gi6FccZq.js";
3
+ export { ChatView, stateColor };
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as Controls } from "../controls-B2EPUJDU.js";
2
+ import { t as Controls } from "../controls-BbZcmnJf.js";
3
3
  export { Controls };
@@ -1,3 +1,11 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * Animated three-dot "thinking" indicator. Shared with the sync-transport
4
+ * chat shell so both defaults render the same wait state.
5
+ *
6
+ * @internal
7
+ */
8
+ export declare function ThinkingDots(): ReactNode;
1
9
  /**
2
10
  * Scrollable list of all chat messages, tool-call blocks, live transcript,
3
11
  * streaming agent utterance, and a thinking indicator.
@@ -14,7 +14,12 @@ const DOT_STYLES = [
14
14
  animation: "aai-bounce 1.4s infinite ease-in-out both",
15
15
  animationDelay: `${delay}s`
16
16
  }));
17
- /** Animated three-dot "thinking" indicator. */
17
+ /**
18
+ * Animated three-dot "thinking" indicator. Shared with the sync-transport
19
+ * chat shell so both defaults render the same wait state.
20
+ *
21
+ * @internal
22
+ */
18
23
  function ThinkingDots() {
19
24
  return /* @__PURE__ */ jsx("div", {
20
25
  className: "flex items-center gap-2 text-sm font-medium min-h-5",
@@ -222,4 +227,4 @@ const MessageList = memo(function MessageList({ className }) {
222
227
  });
223
228
  });
224
229
  //#endregion
225
- export { MessageList };
230
+ export { MessageList, ThinkingDots };
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Sync-transport chat view: mic toggle (client-side VAD), text composer,
3
- * message list, and spoken-reply playback — one HTTP request per turn.
2
+ * Sync-transport view: a hands-free VAD-endpointed conversation, transcript
3
+ * + reply output, and the endpoint each utterance is POSTed to — one HTTP
4
+ * request per turn.
4
5
  *
5
6
  * @public
6
7
  */
@@ -10,8 +11,8 @@ export declare function SyncChatView({ syncUrl, title, greeting, }: {
10
11
  /** Agent name shown in the header. */
11
12
  title?: string | undefined;
12
13
  /**
13
- * Greeting shown as the opening assistant message. Sync turns have no
14
- * session start for the server to speak it on, so it is display-only.
14
+ * Greeting shown as the card's opening line. Sync turns have no session
15
+ * start for the server to speak it on, so it is display-only.
15
16
  */
16
17
  greeting?: string | undefined;
17
18
  }): import("react").JSX.Element;
@@ -1,3 +1,21 @@
1
+ /**
2
+ * A compact labeled chip showing a URL. Click to copy.
3
+ *
4
+ * The label is what makes a pair of these readable — on its own a bare URL
5
+ * leaves you guessing whether it's the page or the socket.
6
+ *
7
+ * Exported for the sync-transport shell, which labels its HTTP endpoint
8
+ * without a session snapshot to read from.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare function UrlChip({ label, url, hint, testId, className, }: {
13
+ label: string;
14
+ url: string;
15
+ hint: string;
16
+ testId: string;
17
+ className?: string | undefined;
18
+ }): import("react").JSX.Element;
1
19
  /**
2
20
  * The session's shareable UI URL.
3
21
  *
@@ -13,6 +13,11 @@ const COPIED_FEEDBACK_MS = 1500;
13
13
  *
14
14
  * The label is what makes a pair of these readable — on its own a bare URL
15
15
  * leaves you guessing whether it's the page or the socket.
16
+ *
17
+ * Exported for the sync-transport shell, which labels its HTTP endpoint
18
+ * without a session snapshot to read from.
19
+ *
20
+ * @internal
16
21
  */
17
22
  function UrlChip({ label, url, hint, testId, className }) {
18
23
  const theme = useTheme();
@@ -135,4 +140,4 @@ const Controls = memo(function Controls({ className }) {
135
140
  });
136
141
  });
137
142
  //#endregion
138
- export { UiUrlChip as i, ApiUrlChip as n, SessionUrlChips as r, Controls as t };
143
+ export { UrlChip as a, UiUrlChip as i, ApiUrlChip as n, SessionUrlChips as r, Controls as t };
@@ -1 +1 @@
1
- import{t as e}from"./index-Dx70XGBr.js";var t=1e3,n=65e3,r=250;async function i(e,t){let n=await new OfflineAudioContext(1,1,t).decodeAudioData(e),r=Math.ceil(n.duration*t),i=new OfflineAudioContext(1,r,t),a=i.createBufferSource();a.buffer=n,a.connect(i.destination),a.start();let o=(await i.startRendering()).getChannelData(0),s=new Int16Array(o.length),c=0;for(let e of o){let t=Math.max(-1,Math.min(1,e));s[c++]=t<0?t*32768:t*32767}return s}async function a(i){let{sttSampleRate:a,ttsSampleRate:o,captureWorkletSrc:s,playbackWorkletSrc:c,onMicData:l,onError:u}=i,d=o,f=new AudioContext({sampleRate:d,latencyHint:`playback`}),p=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,voiceIsolation:!0}}),m;try{[m]=await Promise.all([p,f.resume(),f.audioWorklet.addModule(s),f.audioWorklet.addModule(c)])}catch(e){throw p.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{}),await f.close().catch(e=>{console.warn(`AudioContext close failed:`,e)}),e}let h=f.createMediaStreamSource(m),g=new AudioWorkletNode(f,`capture-processor`,{channelCount:1,channelCountMode:`explicit`,processorOptions:{contextRate:d,sttSampleRate:a,bufferSeconds:e}});h.connect(g),g.onprocessorerror=()=>{let e=Error(`Audio capture worklet crashed`);console.error(`[aai-ui]`,e.message),u?.(e)},g.port.postMessage({event:`start`});let _=null;g.port.onmessage=e=>{e.data.event===`chunk`?l(e.data.buffer):e.data.event===`stopped`&&(_?.(),_=null)};let v=null,y=null,b=new AbortController;function x(){if(v)return v;let e=new AudioWorkletNode(f,`playback-processor`,{processorOptions:{sampleRate:d}});return e.connect(f.destination),e.port.onmessage=e=>{if(e.data.event===`stop`){if(e.data.reason===`interrupt`)return;y?.(),y=null}},e.onprocessorerror=()=>{let e=Error(`Audio playback worklet crashed`);console.error(`[aai-ui]`,e.message),y?.(),y=null,u?.(e)},v=e,e}let S={enqueue(e){b.signal.aborted||e.byteLength!==0&&x().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){return!v||f.state!==`running`?Promise.resolve():new Promise(e=>{y?.();let r=()=>{clearInterval(i),clearTimeout(a),y===r&&(y=null),e()},i=setInterval(()=>{f.state!==`running`&&r()},t),a=setTimeout(r,n);y=r,v?.port.postMessage({event:`done`})})},flush(){v&&(y?.(),y=null,v.port.postMessage({event:`interrupt`}))},async close(){if(!b.signal.aborted){b.abort(),await new Promise(e=>{let t=setTimeout(e,r);_=()=>{clearTimeout(t),e()},g.port.postMessage({event:`stop`})}),h.disconnect(),g.disconnect(),v&&v.disconnect();for(let e of m.getTracks())e.stop();await f.close().catch(()=>{})}},async[Symbol.asyncDispose](){await S.close()}};return S}export{a as createVoiceIO,i as decodeAudioToPcm16};
1
+ import{t as e}from"./index-I5mZ1vB1.js";var t=1e3,n=65e3,r=250;async function i(e,t){let n=await new OfflineAudioContext(1,1,t).decodeAudioData(e),r=Math.ceil(n.duration*t),i=new OfflineAudioContext(1,r,t),a=i.createBufferSource();a.buffer=n,a.connect(i.destination),a.start();let o=(await i.startRendering()).getChannelData(0),s=new Int16Array(o.length),c=0;for(let e of o){let t=Math.max(-1,Math.min(1,e));s[c++]=t<0?t*32768:t*32767}return s}async function a(i){let{sttSampleRate:a,ttsSampleRate:o,captureWorkletSrc:s,playbackWorkletSrc:c,onMicData:l,onError:u}=i,d=o,f=new AudioContext({sampleRate:d,latencyHint:`playback`}),p=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,voiceIsolation:!0}}),m;try{[m]=await Promise.all([p,f.resume(),f.audioWorklet.addModule(s),f.audioWorklet.addModule(c)])}catch(e){throw p.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{}),await f.close().catch(e=>{console.warn(`AudioContext close failed:`,e)}),e}let h=f.createMediaStreamSource(m),g=new AudioWorkletNode(f,`capture-processor`,{channelCount:1,channelCountMode:`explicit`,processorOptions:{contextRate:d,sttSampleRate:a,bufferSeconds:e}});h.connect(g),g.onprocessorerror=()=>{let e=Error(`Audio capture worklet crashed`);console.error(`[aai-ui]`,e.message),u?.(e)},g.port.postMessage({event:`start`});let _=null;g.port.onmessage=e=>{e.data.event===`chunk`?l(e.data.buffer):e.data.event===`stopped`&&(_?.(),_=null)};let v=null,y=null,b=new AbortController;function x(){if(v)return v;let e=new AudioWorkletNode(f,`playback-processor`,{processorOptions:{sampleRate:d}});return e.connect(f.destination),e.port.onmessage=e=>{if(e.data.event===`stop`){if(e.data.reason===`interrupt`)return;y?.(),y=null}},e.onprocessorerror=()=>{let e=Error(`Audio playback worklet crashed`);console.error(`[aai-ui]`,e.message),y?.(),y=null,u?.(e)},v=e,e}let S={enqueue(e){b.signal.aborted||e.byteLength!==0&&x().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){return!v||f.state!==`running`?Promise.resolve():new Promise(e=>{y?.();let r=()=>{clearInterval(i),clearTimeout(a),y===r&&(y=null),e()},i=setInterval(()=>{f.state!==`running`&&r()},t),a=setTimeout(r,n);y=r,v?.port.postMessage({event:`done`})})},flush(){v&&(y?.(),y=null,v.port.postMessage({event:`interrupt`}))},async close(){if(!b.signal.aborted){b.abort(),await new Promise(e=>{let t=setTimeout(e,r);_=()=>{clearTimeout(t),e()},g.port.postMessage({event:`stop`})}),h.disconnect(),g.disconnect(),v&&v.disconnect();for(let e of m.getTracks())e.stop();await f.close().catch(()=>{})}},async[Symbol.asyncDispose](){await S.close()}};return S}export{a as createVoiceIO,i as decodeAudioToPcm16};
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.3 | 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-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--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-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, 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;--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;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--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);--font-aai:"Monument Grotesk", "ABC Monument Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;--font-aai-serif:"Source Serif 4", "Source Serif Pro", Charter, "Iowan Old Style", Georgia, serif;--font-aai-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--radius-aai:4px}}@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,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, 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:where(:not(iframe)){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{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.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:0}.mx-auto{margin-inline:auto}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-2{margin-right:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.block{display:block}.flex{display:flex}.hidden{display:none}.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-9{height:calc(var(--spacing) * 9)}.h-11{height:calc(var(--spacing) * 11)}.h-\[7px\]{height:7px}.h-screen{height:100vh}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-0{min-height:0}.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-\[7px\]{width:7px}.w-fit{width:fit-content}.w-full{width:100%}.max-w-75{max-width:calc(var(--spacing) * 75)}.max-w-105{max-width:calc(var(--spacing) * 105)}.max-w-190{max-width:calc(var(--spacing) * 190)}.max-w-\[55\%\]{max-width:55%}.max-w-\[60\%\]{max-width:60%}.max-w-\[82\%\]{max-width:82%}.max-w-\[min\(78\%\,64ch\)\]{max-width:min(78%,64ch)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.rotate-90{rotate:90deg}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.\[scrollbar-width\:none\]{scrollbar-width:none}.appearance-none{appearance:none}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-aai{border-radius:var(--radius-aai)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-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-none{--tw-border-style:none;border-style:none}.bg-transparent{background-color:#0000}.p-7{padding:calc(var(--spacing) * 7)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-7{padding-inline:calc(var(--spacing) * 7)}.px-10{padding-inline:calc(var(--spacing) * 10)}.py-1{padding-block:var(--spacing)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.text-center{text-align:center}.text-left{text-align:left}.font-aai{font-family:var(--font-aai)}.font-aai-mono{font-family:var(--font-aai-mono)}.font-aai-serif{font-family:var(--font-aai-serif)}.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-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.text-\[22px\]{font-size:22px}.text-\[32px\]{font-size:32px}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-\[1\.2\]{--tw-leading:1.2;line-height:1.2}.leading-\[1\.15\]{--tw-leading:1.15;line-height:1.15}.leading-\[22px\]{--tw-leading:22px;line-height:22px}.leading-\[23px\]{--tw-leading:23px;line-height:23px}.leading-\[130\%\]{--tw-leading:130%;line-height:130%}.leading-none{--tw-leading:1;line-height:1}.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)}.tracking-\[-0\.2px\]{--tw-tracking:-.2px;letter-spacing:-.2px}.tracking-\[1\.2px\]{--tw-tracking:1.2px;letter-spacing:1.2px}.tracking-\[1\.4px\]{--tw-tracking:1.4px;letter-spacing:1.4px}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-balance{text-wrap:balance}.wrap-break-word{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.uppercase{text-transform:uppercase}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.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))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.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}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:px-16{padding-inline:calc(var(--spacing) * 16)}.sm\:py-14{padding-block:calc(var(--spacing) * 14)}}}@keyframes aai-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.82)}}@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-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@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-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}
@@ -97,7 +97,7 @@ registerProcessor("aai-sync-capture", class extends AudioWorkletProcessor {
97
97
  return true;
98
98
  }
99
99
  });
100
- `,Xs=`data:application/javascript;charset=utf-8,${encodeURIComponent(Ys)}`;function Zs(e){let t=new Int16Array(e.length),n=0;for(let r of e){let e=Math.max(-1,Math.min(1,r));t[n++]=e<0?e*32768:e*32767}return t}async function Qs(e){let t=e.sampleRate??16e3,n=qs({sampleRate:t,...e.vad}),r=t=>{e.onError?.(t instanceof Error?t:Error(Eo(t)))},i=navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),a=new AudioContext({sampleRate:t,latencyHint:`interactive`}),o;try{[o]=await Promise.all([i,a.resume(),a.audioWorklet.addModule(Xs)])}catch(e){throw i.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{}),await a.close().catch(()=>{}),e}let s=a.createMediaStreamSource(o),c=new AudioWorkletNode(a,`aai-sync-capture`,{channelCount:1,channelCountMode:`explicit`,processorOptions:{batchSamples:Js}});s.connect(c);let l=!1,u=!1;function d(n){n&&(e.onSpeechEnd?.(),e.session.sendPcm16(n,t).catch(r))}function f(t){l||(d(n.push(Zs(t))),n.speaking&&!u&&e.onSpeechStart?.(),u=n.speaking)}return c.port.onmessage=e=>{let t=e.data;t.event===`chunk`&&t.samples&&f(t.samples)},c.onprocessorerror=()=>r(Error(`Sync capture worklet crashed`)),{get speaking(){return n.speaking},async stop(){if(!l){l=!0,d(n.flush()),s.disconnect(),c.disconnect();for(let e of o.getTracks())e.stop();await a.close().catch(()=>{})}}}}function $s(e){let t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength),n=``,r=32768;for(let e=0;e<t.length;e+=r)n+=String.fromCharCode(...t.subarray(e,e+r));return btoa(n)}function ec(e){let t=atob(e),n=t.length>>1,r=new Uint8Array(n*2);for(let e=0;e<r.length;e++)r[e]=t.charCodeAt(e);return new Int16Array(r.buffer,0,n)}function tc(e){let t=e.fetch??((e,t)=>globalThis.fetch(e,t)),n=[],r=Promise.resolve();async function i(e){if(!e.ok){let t=Do(await e.text().catch(()=>``))??{};throw Error(`Sync turn failed: HTTP ${e.status}${t.error?` (${t.error})`:``}`)}let t=Io.safeParse(await e.json());if(!t.success)throw Error(`Sync turn failed: malformed server response`);return t.data}async function a(r){try{let a=await i(await t(e.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({...r,history:[...n]})}));n.push({role:`user`,content:a.transcript}),a.reply.length>0&&n.push({role:`assistant`,content:a.reply});let o={...a,pcm:a.audio===void 0?null:ec(a.audio)};return e.onTurn?.(o),o}catch(t){let n=t instanceof Error?t:Error(Eo(t));throw e.onError?.(n),n}}function o(e){let t=r.then(()=>a(e),()=>a(e));return r=t.catch(()=>void 0),t}return{get history(){return n},sendText(e){return o({text:e})},sendPcm16(e,t){return o({audio:$s(e),sampleRate:t})},reset(){n.length=0}}}function nc(e,t){if(!(t.pcm&&t.sampleRate))return;e.current??=new AudioContext;let n=e.current,r=n.createBuffer(1,t.pcm.length,t.sampleRate),i=r.getChannelData(0);t.pcm.forEach((e,t)=>{i[t]=e/32768});let a=n.createBufferSource();a.buffer=r,a.connect(n.destination),a.start()}function rc({syncUrl:e,title:t,greeting:n}){let r=os(),[i,a]=(0,B.useState)([]),[o,s]=(0,B.useState)(``),[c,l]=(0,B.useState)(!1),[u,d]=(0,B.useState)(!1),[f,p]=(0,B.useState)(null),m=(0,B.useRef)(null),h=(0,B.useRef)(null),g=(0,B.useRef)(tc({url:e,onTurn:e=>{a(t=>[...t,{id:t.length,role:`user`,text:e.transcript},{id:t.length+1,role:`assistant`,text:e.reply}]),l(!1),p(e.ttsError?`TTS unavailable: ${e.ttsError}`:null),nc(m,e)},onError:e=>{l(!1),p(e.message)}}));(0,B.useEffect)(()=>()=>{h.current?.stop(),m.current?.close()},[]);async function _(){if(h.current){let e=h.current;h.current=null,d(!1),await e.stop();return}try{h.current=await Qs({session:g.current,onSpeechEnd:()=>l(!0),onError:e=>p(e.message)}),d(!0),p(null)}catch(e){p(e instanceof Error?e.message:String(e))}}function v(){let e=o.trim();!e||c||(s(``),l(!0),g.current.sendText(e).catch(()=>{}))}return(0,W.jsxs)(`div`,{className:`flex flex-col h-screen max-w-2xl mx-auto font-aai`,style:{background:r.bg,color:r.text},children:[(0,W.jsxs)(`header`,{className:`px-4 py-3 border-b flex items-center justify-between shrink-0`,style:{borderColor:r.border},children:[(0,W.jsx)(`h1`,{className:`font-bold`,children:t??`Voice Agent`}),(0,W.jsx)(`span`,{className:`text-xs opacity-60`,children:`HTTP turns — no WebSocket`})]}),(0,W.jsxs)(`main`,{className:`flex-1 overflow-y-auto px-4 py-3 space-y-2`,children:[n!==void 0&&n.length>0&&(0,W.jsx)(`div`,{className:`max-w-[85%] rounded-lg px-3 py-2 text-sm whitespace-pre-wrap`,style:{background:r.surface,color:r.text},children:n}),i.length===0&&(0,W.jsx)(`p`,{className:`text-sm`,style:{color:`#57534B`},children:`Turn the mic on and speak — each utterance becomes one HTTP request — or type below.`}),i.map(e=>(0,W.jsx)(`div`,{className:`max-w-[85%] rounded-lg px-3 py-2 text-sm whitespace-pre-wrap ${e.role===`user`?`ml-auto`:``}`,style:{background:e.role===`user`?r.primary:r.surface,color:e.role===`user`?`#fff`:r.text},children:e.text},e.id)),c&&(0,W.jsx)(`p`,{className:`text-sm opacity-60 animate-pulse`,children:`Thinking…`}),f&&(0,W.jsx)(`p`,{className:`text-sm`,style:{color:`#dc2626`},children:f})]}),(0,W.jsxs)(`footer`,{className:`p-3 border-t flex gap-2 items-center shrink-0`,style:{borderColor:r.border},children:[(0,W.jsx)(`button`,{type:`button`,onClick:()=>void _(),"aria-pressed":u,className:`rounded-full w-11 h-11 shrink-0 text-lg`,style:{background:u?`#dc2626`:r.primary,color:`#fff`},title:u?`Stop listening`:`Start listening`,children:u?`■`:`🎤`}),(0,W.jsx)(`input`,{className:`flex-1 rounded-lg px-3 py-2 text-sm border bg-transparent`,style:{borderColor:r.border,color:r.text},placeholder:`Type a message…`,value:o,onChange:e=>s(e.target.value),onKeyDown:e=>{e.key===`Enter`&&v()}}),(0,W.jsx)(`button`,{type:`button`,onClick:v,disabled:c||o.trim().length===0,className:`rounded-lg px-4 py-2 text-sm font-medium disabled:opacity-50`,style:{background:r.primary,color:`#fff`},children:`Send`})]})]})}var ic=`modulepreload`,ac=function(e,t){return new URL(e,t).href},oc={},sc=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=ac(t,n),t=s(t),t in oc)return;oc[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:ic,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};async function cc(e,t,n,r){if(e.audioSetupInFlight)return;e.audioSetupInFlight=!0;let i=e.generation,a=()=>e.generation!==i||!e.ws||e.ws.readyState!==1,o=e=>{r?n.updateState({state:`error`,error:{code:`audio`,message:e},running:!1,recording:!1}):(n.cleanupAudio(),n.updateState({error:{code:`audio`,message:e},recording:!1}))};try{let[{createVoiceIO:r},s,c]=await Promise.all([sc(()=>import(`./audio-NYmRKbVI.js`),[],import.meta.url),sc(()=>import(`./capture-processor-UlKEKyIW.js`).then(e=>e.default),[],import.meta.url),sc(()=>import(`./playback-processor-C5HVRVbu.js`).then(e=>e.default),[],import.meta.url)]),l=await r({sttSampleRate:t.sampleRate,ttsSampleRate:t.ttsSampleRate,captureWorkletSrc:s,playbackWorkletSrc:c,onMicData:e=>{try{n.sendAudio(new Uint8Array(e))}catch{console.debug(`[aai-ui] sendAudio dropped: connection closed`)}},onError:t=>{e.generation===i&&o(t.message)}});if(a()){l.close().catch(()=>{});return}if(e.voiceIO?.close().catch(()=>{}),e.voiceIO=l,e.preInitAudio.length>0){for(let t of e.preInitAudio)l.enqueue(t.buffer);e.preInitAudio=[]}n.sendJson({type:`audio_ready`}),n.updateState({recording:!0}),e.preInitDone?(e.preInitDone=!1,n.settleWhenAudioDrained(l)):n.updateState({state:`listening`})}catch(e){if(a())return;o(`Microphone access failed: ${Eo(e)}`)}finally{e.generation===i&&(e.audioSetupInFlight=!1)}}var lc=200,uc=200,dc=100,fc={messages:[],toolCalls:[],customEvents:[],userTranscript:null,agentTranscript:null,error:null};function pc(e,t,n){if(e.length<n)return[...e,t];let r=e.slice(e.length-n+1);return r.push(t),r}function mc(e){let{getSnapshot:t,updateState:n,conn:r,discardUpload:i,cleanupAudio:a}=e,o=0,s=0,c=0,l=0;function u(e,r){n({customEvents:pc(t().customEvents,{id:++s,event:e,data:r},lc)})}function d(e){o++,n({userTranscript:null,messages:pc(t().messages,{id:++c,role:`user`,content:e},uc),state:`thinking`})}function f(e){n({agentTranscript:null,messages:pc(t().messages,{id:++c,role:`assistant`,content:e},uc)})}function p(){let e=t();e.state===`error`?n({state:`listening`,error:null}):e.error!==null&&n({error:null})}function m(e){console.error(`Agent error:`,e.message),e.fatal===!1?n({error:{code:e.code,message:e.message}}):(a(),n({state:`error`,error:{code:e.code,message:e.message},running:!1,recording:!1}))}function h(e){switch(e.type!==`error`&&p(),e.type){case`speech_started`:n({userTranscript:``});break;case`speech_stopped`:break;case`user_transcript`:d(e.text);break;case`user_transcript_partial`:n({userTranscript:e.text});break;case`agent_transcript`:f(e.text);break;case`tool_call`:n({toolCalls:pc(t().toolCalls,{callId:e.toolCallId,name:e.toolName,args:e.args??{},status:`pending`,seq:++l,afterMessageId:t().messages.at(-1)?.id??-1},uc)});break;case`tool_call_done`:{let r=t().toolCalls,i=r.findIndex(t=>t.callId===e.toolCallId);if(i!==-1){let t=[...r],a=t[i];a&&(t[i]={...a,status:`done`,result:e.result}),n({toolCalls:t})}break}case`reply_done`:n({state:`listening`});break;case`cancelled`:o++,r.voiceIO?.flush(),n({userTranscript:null,agentTranscript:null,state:`listening`});break;case`reset`:o++,i(),r.voiceIO?.flush(),n({...fc,state:`listening`});break;case`custom_event`:u(e.event,e.data);break;case`error`:m(e);break;case`idle_timeout`:break;default:break}}function g(e){let i=t();i.state===`error`||i.state===`disconnected`&&i.error!==null||(i.state!==`speaking`&&n({state:`speaking`}),r.voiceIO?r.voiceIO.enqueue(e.buffer):r.preInitAudio.length<dc&&r.preInitAudio.push(e))}function _(e){let t=o;e.done().then(()=>{o===t&&n({state:`listening`})}).catch(e=>{console.warn(`Audio playback done failed:`,e)})}function v(){let e=r.voiceIO;e?_(e):(r.preInitDone=!0,n({state:`listening`}))}function y(e){if(e instanceof ArrayBuffer){g(new Uint8Array(e));return}if(typeof e!=`string`){console.warn(`session-core: non-string, non-binary frame received; dropping`);return}let t=Do(e);if(t===void 0){console.warn(`session-core: invalid JSON; dropping`);return}let n=Ro(Uo,t);if(!n.ok){n.malformed&&console.warn(`session-core: malformed server message`,n.error);return}let r=n.data;if(r.type===`config`)return{sampleRate:r.sampleRate,ttsSampleRate:r.ttsSampleRate,audioOut:r.audioOut,sid:r.sessionId};if(r.type===`audio_done`){v();return}h(r)}return{handleMessage:y,settleWhenAudioDrained:_}}(!globalThis.EventTarget||!globalThis.Event)&&console.error(`
100
+ `,Xs=URL.createObjectURL(new Blob([Ys],{type:`application/javascript`}));function Zs(e){let t=new Int16Array(e.length),n=0;for(let r of e){let e=Math.max(-1,Math.min(1,r));t[n++]=e<0?e*32768:e*32767}return t}async function Qs(e){let t=e.sampleRate??16e3,n=qs({sampleRate:t,...e.vad}),r=t=>{e.onError?.(t instanceof Error?t:Error(Eo(t)))},i=navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),a=new AudioContext({sampleRate:t,latencyHint:`interactive`}),o;try{[o]=await Promise.all([i,a.resume(),a.audioWorklet.addModule(Xs)])}catch(e){throw i.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{}),await a.close().catch(()=>{}),e}let s=a.createMediaStreamSource(o),c=new AudioWorkletNode(a,`aai-sync-capture`,{channelCount:1,channelCountMode:`explicit`,processorOptions:{batchSamples:Js}});s.connect(c);let l=!1,u=!1;function d(n){n&&(e.onSpeechEnd?.(),e.session.sendPcm16(n,t).catch(r))}function f(t){l||(d(n.push(Zs(t))),n.speaking&&!u&&e.onSpeechStart?.(),u=n.speaking)}return c.port.onmessage=e=>{let t=e.data;t.event===`chunk`&&t.samples&&f(t.samples)},c.onprocessorerror=()=>r(Error(`Sync capture worklet crashed`)),{get speaking(){return n.speaking},async stop(){if(!l){l=!0,d(n.flush()),s.disconnect(),c.disconnect();for(let e of o.getTracks())e.stop();await a.close().catch(()=>{})}}}}function $s(e){let t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength),n=``,r=32768;for(let e=0;e<t.length;e+=r)n+=String.fromCharCode(...t.subarray(e,e+r));return btoa(n)}function ec(e){let t=atob(e),n=t.length>>1,r=new Uint8Array(n*2);for(let e=0;e<r.length;e++)r[e]=t.charCodeAt(e);return new Int16Array(r.buffer,0,n)}function tc(e){let t=e.fetch??((e,t)=>globalThis.fetch(e,t)),n=[],r=Promise.resolve();async function i(e){if(!e.ok){let t=Do(await e.text().catch(()=>``))??{};throw Error(`Sync turn failed: HTTP ${e.status}${t.error?` (${t.error})`:``}`)}let t=Io.safeParse(await e.json());if(!t.success)throw Error(`Sync turn failed: malformed server response`);return t.data}async function a(r){try{let a=await i(await t(e.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({...r,history:[...n]})}));n.push({role:`user`,content:a.transcript}),a.reply.length>0&&n.push({role:`assistant`,content:a.reply});let o={...a,pcm:a.audio===void 0?null:ec(a.audio)};return e.onTurn?.(o),o}catch(t){let n=t instanceof Error?t:Error(Eo(t));throw e.onError?.(n),n}}function o(e){let t=r.then(()=>a(e),()=>a(e));return r=t.catch(()=>void 0),t}return{get history(){return n},sendText(e){return o({text:e})},sendPcm16(e,t){return o({audio:$s(e),sampleRate:t})},reset(){n.length=0}}}function nc(e,t,n){if(!(t.pcm&&t.sampleRate))return;e.current??=new AudioContext;let r=e.current,i=r.createBuffer(1,t.pcm.length,t.sampleRate),a=i.getChannelData(0);t.pcm.forEach((e,t)=>{a[t]=e/32768});let o=r.createBufferSource();o.buffer=i,o.connect(r.destination),o.onended=()=>n(!1),n(!0),o.start()}function rc(e){return e.pending>0?`thinking`:e.agentSpeaking?`speaking`:e.live?`listening`:e.error?`error`:`ready`}function ic({syncUrl:e,title:t,greeting:n}){let r=os(),[i,a]=(0,B.useState)([]),[o,s]=(0,B.useState)(!1),[c,l]=(0,B.useState)(!1),[u,d]=(0,B.useState)(0),[f,p]=(0,B.useState)(!1),[m,h]=(0,B.useState)(null),g=(0,B.useRef)(null),_=(0,B.useRef)(null),v=(0,B.useRef)(!1),y=(0,B.useRef)(null),b=(0,B.useRef)(tc({url:e,onTurn:e=>{a(t=>[...t,{id:t.length,heard:e.transcript,reply:e.reply}]),d(e=>Math.max(0,e-1)),h(e.ttsError?`TTS unavailable: ${e.ttsError}`:null),nc(g,e,p)},onError:e=>{d(e=>Math.max(0,e-1)),h(e.message)}}));(0,B.useEffect)(()=>()=>{_.current?.stop(),g.current?.close()},[]);let x=i.length+ +(u>0);(0,B.useEffect)(()=>{x!==0&&y.current?.scrollIntoView({behavior:`smooth`,block:`end`})},[x]);async function S(){if(!v.current){v.current=!0;try{if(_.current){let e=_.current;_.current=null,s(!1),l(!1),await e.stop();return}_.current=await Qs({session:b.current,onSpeechStart:()=>l(!0),onSpeechEnd:()=>{l(!1),d(e=>e+1)},onError:e=>h(e.message)}),s(!0),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}finally{v.current=!1}}}let C=rc({error:m,live:o,pending:u,agentSpeaking:f}),w=o?`End conversation`:`Start conversation`,ee=c||f;return(0,W.jsxs)(`div`,{className:`flex flex-col h-screen w-full max-w-190 mx-auto box-border px-6 py-8 gap-5 font-aai text-sm`,style:{background:r.bg,color:r.text},children:[(0,W.jsxs)(`div`,{className:`flex items-center justify-between shrink-0`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-3 min-w-0`,children:[(0,W.jsx)(ps,{size:22}),(0,W.jsx)(`span`,{className:`font-aai-serif text-[22px] leading-[1.2] font-normal truncate`,style:{color:r.text},children:t??`Voice Agent`})]}),(0,W.jsxs)(Ss,{className:`shrink-0`,"data-state":C,children:[(0,W.jsx)(`span`,{className:`w-[7px] h-[7px] rounded-full`,style:{background:zs(C,r.primary),animation:ee?`aai-pulse 1.6s ease-in-out infinite`:`none`}}),C]})]}),m&&(0,W.jsx)(`div`,{className:`px-3.5 py-2.5 rounded-aai border text-[13px] leading-[130%] shrink-0`,style:{borderColor:`rgba(179,38,30,0.35)`,background:`rgba(179,38,30,0.06)`,color:`#B3261E`},children:m}),(0,W.jsx)(`div`,{className:`flex flex-col flex-1 min-h-0 border rounded-lg overflow-hidden`,style:{background:r.surface,borderColor:r.border,boxShadow:`0 1px 3px 0 rgb(20 18 12 / 0.06)`},children:(0,W.jsx)(`div`,{role:`log`,className:`flex-1 overflow-y-auto [scrollbar-width:none]`,style:{background:r.surface},children:(0,W.jsxs)(`div`,{className:`flex flex-col gap-5 p-7`,children:[n!==void 0&&n.length>0&&(0,W.jsx)(`p`,{className:`text-[15px] leading-[23px]`,style:{color:r.text},children:n}),i.length===0&&(0,W.jsx)(`p`,{className:`text-sm`,style:{color:`#57534B`},children:`Start the conversation and just talk — each pause endpoints an utterance, which goes out as one HTTP request to the endpoint below.`}),i.map(e=>(0,W.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,W.jsx)(`span`,{className:`text-[10px] font-medium tracking-[1.2px] uppercase leading-none`,style:{color:cs},children:`Heard`}),(0,W.jsx)(`p`,{className:`text-[15px] leading-[22px]`,style:{color:ss},children:e.heard}),(0,W.jsx)(`span`,{className:`text-[10px] font-medium tracking-[1.2px] uppercase leading-none mt-1.5`,style:{color:cs},children:`Agent`}),(0,W.jsx)(`p`,{className:`whitespace-pre-wrap wrap-break-word text-[15px] font-normal leading-[23px]`,style:{color:r.text},children:e.reply})]},e.id)),u>0&&(0,W.jsx)(`div`,{"data-testid":`thinking`,children:(0,W.jsx)(As,{})}),(0,W.jsx)(`div`,{ref:y})]})})}),(0,W.jsxs)(`div`,{className:`flex items-center gap-2 shrink-0`,children:[(0,W.jsxs)(ms,{size:`lg`,variant:o?`default`:`secondary`,className:`select-none`,style:o?{background:us,borderColor:`transparent`}:void 0,onClick:()=>void S(),"aria-pressed":o,title:`Start or end the conversation`,children:[(0,W.jsx)(`span`,{className:Yo(`w-2 h-2 rounded-full mr-2`,ee&&`animate-pulse`),style:{background:o?`#fff`:us}}),w]}),(0,W.jsx)(gs,{label:`Sync`,url:e,hint:`Each utterance is one POST to this endpoint`,testId:`sync-url-chip`,className:`ml-auto min-w-0 max-w-[55%]`})]})]})}var ac=`modulepreload`,oc=function(e,t){return new URL(e,t).href},sc={},cc=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=oc(t,n),t=s(t),t in sc)return;sc[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:ac,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};async function lc(e,t,n,r){if(e.audioSetupInFlight)return;e.audioSetupInFlight=!0;let i=e.generation,a=()=>e.generation!==i||!e.ws||e.ws.readyState!==1,o=e=>{r?n.updateState({state:`error`,error:{code:`audio`,message:e},running:!1,recording:!1}):(n.cleanupAudio(),n.updateState({error:{code:`audio`,message:e},recording:!1}))};try{let[{createVoiceIO:r},s,c]=await Promise.all([cc(()=>import(`./audio-CTPVKBQ_.js`),[],import.meta.url),cc(()=>import(`./capture-processor-UlKEKyIW.js`).then(e=>e.default),[],import.meta.url),cc(()=>import(`./playback-processor-C5HVRVbu.js`).then(e=>e.default),[],import.meta.url)]),l=await r({sttSampleRate:t.sampleRate,ttsSampleRate:t.ttsSampleRate,captureWorkletSrc:s,playbackWorkletSrc:c,onMicData:e=>{try{n.sendAudio(new Uint8Array(e))}catch{console.debug(`[aai-ui] sendAudio dropped: connection closed`)}},onError:t=>{e.generation===i&&o(t.message)}});if(a()){l.close().catch(()=>{});return}if(e.voiceIO?.close().catch(()=>{}),e.voiceIO=l,e.preInitAudio.length>0){for(let t of e.preInitAudio)l.enqueue(t.buffer);e.preInitAudio=[]}n.sendJson({type:`audio_ready`}),n.updateState({recording:!0}),e.preInitDone?(e.preInitDone=!1,n.settleWhenAudioDrained(l)):n.updateState({state:`listening`})}catch(e){if(a())return;o(`Microphone access failed: ${Eo(e)}`)}finally{e.generation===i&&(e.audioSetupInFlight=!1)}}var uc=200,dc=200,fc=100,pc={messages:[],toolCalls:[],customEvents:[],userTranscript:null,agentTranscript:null,error:null};function mc(e,t,n){if(e.length<n)return[...e,t];let r=e.slice(e.length-n+1);return r.push(t),r}function hc(e){let{getSnapshot:t,updateState:n,conn:r,discardUpload:i,cleanupAudio:a}=e,o=0,s=0,c=0,l=0;function u(e,r){n({customEvents:mc(t().customEvents,{id:++s,event:e,data:r},uc)})}function d(e){o++,n({userTranscript:null,messages:mc(t().messages,{id:++c,role:`user`,content:e},dc),state:`thinking`})}function f(e){n({agentTranscript:null,messages:mc(t().messages,{id:++c,role:`assistant`,content:e},dc)})}function p(){let e=t();e.state===`error`?n({state:`listening`,error:null}):e.error!==null&&n({error:null})}function m(e){console.error(`Agent error:`,e.message),e.fatal===!1?n({error:{code:e.code,message:e.message}}):(a(),n({state:`error`,error:{code:e.code,message:e.message},running:!1,recording:!1}))}function h(e){switch(e.type!==`error`&&p(),e.type){case`speech_started`:n({userTranscript:``});break;case`speech_stopped`:break;case`user_transcript`:d(e.text);break;case`user_transcript_partial`:n({userTranscript:e.text});break;case`agent_transcript`:f(e.text);break;case`tool_call`:n({toolCalls:mc(t().toolCalls,{callId:e.toolCallId,name:e.toolName,args:e.args??{},status:`pending`,seq:++l,afterMessageId:t().messages.at(-1)?.id??-1},dc)});break;case`tool_call_done`:{let r=t().toolCalls,i=r.findIndex(t=>t.callId===e.toolCallId);if(i!==-1){let t=[...r],a=t[i];a&&(t[i]={...a,status:`done`,result:e.result}),n({toolCalls:t})}break}case`reply_done`:n({state:`listening`});break;case`cancelled`:o++,r.voiceIO?.flush(),n({userTranscript:null,agentTranscript:null,state:`listening`});break;case`reset`:o++,i(),r.voiceIO?.flush(),n({...pc,state:`listening`});break;case`custom_event`:u(e.event,e.data);break;case`error`:m(e);break;case`idle_timeout`:break;default:break}}function g(e){let i=t();i.state===`error`||i.state===`disconnected`&&i.error!==null||(i.state!==`speaking`&&n({state:`speaking`}),r.voiceIO?r.voiceIO.enqueue(e.buffer):r.preInitAudio.length<fc&&r.preInitAudio.push(e))}function _(e){let t=o;e.done().then(()=>{o===t&&n({state:`listening`})}).catch(e=>{console.warn(`Audio playback done failed:`,e)})}function v(){let e=r.voiceIO;e?_(e):(r.preInitDone=!0,n({state:`listening`}))}function y(e){if(e instanceof ArrayBuffer){g(new Uint8Array(e));return}if(typeof e!=`string`){console.warn(`session-core: non-string, non-binary frame received; dropping`);return}let t=Do(e);if(t===void 0){console.warn(`session-core: invalid JSON; dropping`);return}let n=Ro(Uo,t);if(!n.ok){n.malformed&&console.warn(`session-core: malformed server message`,n.error);return}let r=n.data;if(r.type===`config`)return{sampleRate:r.sampleRate,ttsSampleRate:r.ttsSampleRate,audioOut:r.audioOut,sid:r.sessionId};if(r.type===`audio_done`){v();return}h(r)}return{handleMessage:y,settleWhenAudioDrained:_}}(!globalThis.EventTarget||!globalThis.Event)&&console.error(`
101
101
  PartySocket requires a global 'EventTarget' class to be available!
102
102
  You can polyfill this global by adding this to your code before any partysocket imports:
103
103
 
@@ -105,7 +105,7 @@ registerProcessor("aai-sync-capture", class extends AudioWorkletProcessor {
105
105
  import 'partysocket/event-target-polyfill';
106
106
  \`\`\`
107
107
  Please file an issue at https://github.com/partykit/partykit if you're still having trouble.
108
- `);var hc=class extends Event{message;error;constructor(e,t){super(`error`,t),this.message=e.message,this.error=e}},gc=class extends Event{code;reason;wasClean=!0;constructor(e=1e3,t=``,n){super(`close`,n),this.code=e,this.reason=t}},_c={Event,ErrorEvent:hc,CloseEvent:gc};function vc(e,t){if(!e)throw Error(t)}function yc(e){return new e.constructor(e.type,e)}function bc(e){return`data`in e?new MessageEvent(e.type,e):`code`in e||`reason`in e?new gc(e.code||1999,e.reason||`unknown reason`,e):`error`in e?new hc(e.error,e):new Event(e.type,e)}var xc=typeof process<`u`&&process.versions?.node!==void 0,Sc=typeof navigator<`u`&&navigator.product===`ReactNative`,Cc=xc||Sc?bc:yc,wc={maxReconnectionDelay:1e4,minReconnectionDelay:3e3,minUptime:5e3,reconnectionDelayGrowFactor:1.3,connectionTimeout:4e3,maxRetries:1/0,maxEnqueuedMessages:1/0,startClosed:!1,debug:!1},Tc=!1;function Ec(){}var Dc=class e extends EventTarget{_ws;_retryCount=-1;_uptimeTimeout;_connectTimeout;_shouldReconnect=!0;_connectLock=!1;_binaryType=`blob`;_closeCalled=!1;_didWarnAboutClosedSend=!1;_messageQueue=[];_debugLogger=console.log.bind(console);_url;_protocols;_options;constructor(e,t,n={}){super(),this._url=e,this._protocols=t,this._options=n,this._options.startClosed&&(this._shouldReconnect=!1),this._options.debugLogger&&(this._debugLogger=this._options.debugLogger),this._connect()}static get CONNECTING(){return 0}static get OPEN(){return 1}static get CLOSING(){return 2}static get CLOSED(){return 3}get CONNECTING(){return e.CONNECTING}get OPEN(){return e.OPEN}get CLOSING(){return e.CLOSING}get CLOSED(){return e.CLOSED}get binaryType(){return this._ws?this._ws.binaryType:this._binaryType}set binaryType(e){this._binaryType=e,this._ws&&(this._ws.binaryType=e)}get retryCount(){return Math.max(this._retryCount,0)}get bufferedAmount(){return this._messageQueue.reduce((e,t)=>(typeof t==`string`?e+=t.length:t instanceof Blob?e+=t.size:e+=t.byteLength,e),0)+(this._ws?this._ws.bufferedAmount:0)}get extensions(){return this._ws?this._ws.extensions:``}get protocol(){return this._ws?this._ws.protocol:``}get readyState(){return this._closeCalled?e.CLOSED:this._ws?this._ws.readyState:this._options.startClosed?e.CLOSED:e.CONNECTING}get url(){return this._ws?this._ws.url:``}get shouldReconnect(){return this._shouldReconnect}onclose=null;onerror=null;onmessage=null;onopen=null;close(e=1e3,t){if(this._closeCalled=!0,this._shouldReconnect=!1,this._clearTimeouts(),!this._ws){this._debug(`close enqueued: no ws instance`);return}if(this._ws.readyState===this.CLOSED||this._ws.readyState===this.CLOSING){this._debug(`close: already closing or closed`);return}this._disconnect(e,t)}reconnect(e,t){this._shouldReconnect=!0,this._closeCalled=!1,this._didWarnAboutClosedSend=!1,this._retryCount=-1,!this._ws||this._ws.readyState===this.CLOSED||this._ws.readyState===this.CLOSING||this._disconnect(e,t),this._connect()}send(e){if(this._ws&&this._ws.readyState===this.OPEN)return this._debug(`send`,e),this._ws.send(e),!0;this._closeCalled&&!this._didWarnAboutClosedSend&&(this._didWarnAboutClosedSend=!0,console.warn(`ReconnectingWebSocket: send() was called after close(). The message has been buffered, but it will only be delivered if reconnect() is called on this socket. If this socket has been discarded, the message is lost — this usually means a stale socket reference is being used.`));let{maxEnqueuedMessages:t=wc.maxEnqueuedMessages}=this._options;return this._messageQueue.length<t&&(this._debug(`enqueue`,e),this._messageQueue.push(e)),!1}drainQueuedMessages(){let e=this._messageQueue;return this._messageQueue=[],e}_debug(...e){this._options.debug&&this._debugLogger(`RWS>`,...e)}_getNextDelay(){let{reconnectionDelayGrowFactor:e=wc.reconnectionDelayGrowFactor,minReconnectionDelay:t=wc.minReconnectionDelay,maxReconnectionDelay:n=wc.maxReconnectionDelay}=this._options,r=0;return this._retryCount>0&&(r=t*e**(this._retryCount-1),r>n&&(r=n)),this._debug(`next delay`,r),r}_wait(){return new Promise(e=>{setTimeout(e,this._getNextDelay())})}_getNextProtocols(e){if(!e)return Promise.resolve(null);if(typeof e==`string`||Array.isArray(e))return Promise.resolve(e);if(typeof e==`function`){let t=e();if(!t)return Promise.resolve(null);if(typeof t==`string`||Array.isArray(t))return Promise.resolve(t);if(t.then)return t}throw Error(`Invalid protocols`)}_getNextUrl(e){if(typeof e==`string`)return Promise.resolve(e);if(typeof e==`function`){let t=e();if(typeof t==`string`)return Promise.resolve(t);if(t.then)return t}throw Error(`Invalid URL`)}_connect(){if(this._connectLock||!this._shouldReconnect)return;this._connectLock=!0;let{maxRetries:e=wc.maxRetries,connectionTimeout:t=wc.connectionTimeout}=this._options;if(this._retryCount>=e){this._debug(`max retries reached`,this._retryCount,`>=`,e),this._connectLock=!1;return}this._retryCount++,this._debug(`connect`,this._retryCount),this._removeListeners(),this._wait().then(()=>Promise.all([this._getNextUrl(this._url),this._getNextProtocols(this._protocols||null)])).then(([e,n])=>{if(this._closeCalled){this._connectLock=!1;return}!this._options.WebSocket&&typeof WebSocket>`u`&&!Tc&&(console.error(`‼️ No WebSocket implementation available. You should define options.WebSocket.
108
+ `);var gc=class extends Event{message;error;constructor(e,t){super(`error`,t),this.message=e.message,this.error=e}},_c=class extends Event{code;reason;wasClean=!0;constructor(e=1e3,t=``,n){super(`close`,n),this.code=e,this.reason=t}},vc={Event,ErrorEvent:gc,CloseEvent:_c};function yc(e,t){if(!e)throw Error(t)}function bc(e){return new e.constructor(e.type,e)}function xc(e){return`data`in e?new MessageEvent(e.type,e):`code`in e||`reason`in e?new _c(e.code||1999,e.reason||`unknown reason`,e):`error`in e?new gc(e.error,e):new Event(e.type,e)}var Sc=typeof process<`u`&&process.versions?.node!==void 0,Cc=typeof navigator<`u`&&navigator.product===`ReactNative`,wc=Sc||Cc?xc:bc,Tc={maxReconnectionDelay:1e4,minReconnectionDelay:3e3,minUptime:5e3,reconnectionDelayGrowFactor:1.3,connectionTimeout:4e3,maxRetries:1/0,maxEnqueuedMessages:1/0,startClosed:!1,debug:!1},Ec=!1;function Dc(){}var Oc=class e extends EventTarget{_ws;_retryCount=-1;_uptimeTimeout;_connectTimeout;_shouldReconnect=!0;_connectLock=!1;_binaryType=`blob`;_closeCalled=!1;_didWarnAboutClosedSend=!1;_messageQueue=[];_debugLogger=console.log.bind(console);_url;_protocols;_options;constructor(e,t,n={}){super(),this._url=e,this._protocols=t,this._options=n,this._options.startClosed&&(this._shouldReconnect=!1),this._options.debugLogger&&(this._debugLogger=this._options.debugLogger),this._connect()}static get CONNECTING(){return 0}static get OPEN(){return 1}static get CLOSING(){return 2}static get CLOSED(){return 3}get CONNECTING(){return e.CONNECTING}get OPEN(){return e.OPEN}get CLOSING(){return e.CLOSING}get CLOSED(){return e.CLOSED}get binaryType(){return this._ws?this._ws.binaryType:this._binaryType}set binaryType(e){this._binaryType=e,this._ws&&(this._ws.binaryType=e)}get retryCount(){return Math.max(this._retryCount,0)}get bufferedAmount(){return this._messageQueue.reduce((e,t)=>(typeof t==`string`?e+=t.length:t instanceof Blob?e+=t.size:e+=t.byteLength,e),0)+(this._ws?this._ws.bufferedAmount:0)}get extensions(){return this._ws?this._ws.extensions:``}get protocol(){return this._ws?this._ws.protocol:``}get readyState(){return this._closeCalled?e.CLOSED:this._ws?this._ws.readyState:this._options.startClosed?e.CLOSED:e.CONNECTING}get url(){return this._ws?this._ws.url:``}get shouldReconnect(){return this._shouldReconnect}onclose=null;onerror=null;onmessage=null;onopen=null;close(e=1e3,t){if(this._closeCalled=!0,this._shouldReconnect=!1,this._clearTimeouts(),!this._ws){this._debug(`close enqueued: no ws instance`);return}if(this._ws.readyState===this.CLOSED||this._ws.readyState===this.CLOSING){this._debug(`close: already closing or closed`);return}this._disconnect(e,t)}reconnect(e,t){this._shouldReconnect=!0,this._closeCalled=!1,this._didWarnAboutClosedSend=!1,this._retryCount=-1,!this._ws||this._ws.readyState===this.CLOSED||this._ws.readyState===this.CLOSING||this._disconnect(e,t),this._connect()}send(e){if(this._ws&&this._ws.readyState===this.OPEN)return this._debug(`send`,e),this._ws.send(e),!0;this._closeCalled&&!this._didWarnAboutClosedSend&&(this._didWarnAboutClosedSend=!0,console.warn(`ReconnectingWebSocket: send() was called after close(). The message has been buffered, but it will only be delivered if reconnect() is called on this socket. If this socket has been discarded, the message is lost — this usually means a stale socket reference is being used.`));let{maxEnqueuedMessages:t=Tc.maxEnqueuedMessages}=this._options;return this._messageQueue.length<t&&(this._debug(`enqueue`,e),this._messageQueue.push(e)),!1}drainQueuedMessages(){let e=this._messageQueue;return this._messageQueue=[],e}_debug(...e){this._options.debug&&this._debugLogger(`RWS>`,...e)}_getNextDelay(){let{reconnectionDelayGrowFactor:e=Tc.reconnectionDelayGrowFactor,minReconnectionDelay:t=Tc.minReconnectionDelay,maxReconnectionDelay:n=Tc.maxReconnectionDelay}=this._options,r=0;return this._retryCount>0&&(r=t*e**(this._retryCount-1),r>n&&(r=n)),this._debug(`next delay`,r),r}_wait(){return new Promise(e=>{setTimeout(e,this._getNextDelay())})}_getNextProtocols(e){if(!e)return Promise.resolve(null);if(typeof e==`string`||Array.isArray(e))return Promise.resolve(e);if(typeof e==`function`){let t=e();if(!t)return Promise.resolve(null);if(typeof t==`string`||Array.isArray(t))return Promise.resolve(t);if(t.then)return t}throw Error(`Invalid protocols`)}_getNextUrl(e){if(typeof e==`string`)return Promise.resolve(e);if(typeof e==`function`){let t=e();if(typeof t==`string`)return Promise.resolve(t);if(t.then)return t}throw Error(`Invalid URL`)}_connect(){if(this._connectLock||!this._shouldReconnect)return;this._connectLock=!0;let{maxRetries:e=Tc.maxRetries,connectionTimeout:t=Tc.connectionTimeout}=this._options;if(this._retryCount>=e){this._debug(`max retries reached`,this._retryCount,`>=`,e),this._connectLock=!1;return}this._retryCount++,this._debug(`connect`,this._retryCount),this._removeListeners(),this._wait().then(()=>Promise.all([this._getNextUrl(this._url),this._getNextProtocols(this._protocols||null)])).then(([e,n])=>{if(this._closeCalled){this._connectLock=!1;return}!this._options.WebSocket&&typeof WebSocket>`u`&&!Ec&&(console.error(`‼️ No WebSocket implementation available. You should define options.WebSocket.
109
109
 
110
110
  For example, if you're using node.js, run \`npm install ws\`, and then in your code:
111
111
 
@@ -118,4 +118,4 @@ const partysocket = new PartySocket({
118
118
  WebSocket: WS
119
119
  });
120
120
 
121
- `),Tc=!0);let r=this._options.WebSocket||WebSocket;this._debug(`connect`,{url:e,protocols:n}),this._ws=n?new r(e,n):new r(e),this._ws.binaryType=this._binaryType,this._connectLock=!1,this._addListeners(),this._connectTimeout=setTimeout(()=>this._handleTimeout(),t)}).catch(e=>{this._connectLock=!1,this._handleError(new _c.ErrorEvent(Error(e.message),this))})}_handleTimeout(){this._debug(`timeout event`),this._handleError(new _c.ErrorEvent(Error(`TIMEOUT`),this))}_disconnect(e=1e3,t){if(this._clearTimeouts(),this._ws){this._removeListeners();try{(this._ws.readyState===this.OPEN||this._ws.readyState===this.CONNECTING)&&this._ws.close(e,t),this._handleClose(new _c.CloseEvent(e,t,this))}catch{}}}_acceptOpen(){this._debug(`accept open`),this._retryCount=0}_handleOpen=e=>{this._debug(`open event`);let{minUptime:t=wc.minUptime}=this._options;clearTimeout(this._connectTimeout),this._uptimeTimeout=setTimeout(()=>this._acceptOpen(),t),vc(this._ws,`WebSocket is not defined`),this._ws.binaryType=this._binaryType,this._messageQueue.forEach(e=>{this._ws?.send(e)}),this._messageQueue=[],this.onopen&&this.onopen(e),this.dispatchEvent(Cc(e))};_handleMessage=e=>{this._debug(`message event`),this.onmessage&&this.onmessage(e),this.dispatchEvent(Cc(e))};_handleError=e=>{this._debug(`error event`,e.message),this._disconnect(void 0,e.message===`TIMEOUT`?`timeout`:void 0),this.onerror&&this.onerror(e),this._debug(`exec error listeners`),this.dispatchEvent(Cc(e)),this._connect()};_handleClose=e=>{this._debug(`close event`),this._clearTimeouts(),this._options.shouldReconnectOnClose&&!this._options.shouldReconnectOnClose(e)&&(this._shouldReconnect=!1),this._shouldReconnect&&this._connect(),this.onclose&&this.onclose(e),this.dispatchEvent(Cc(e))};_removeListeners(){this._ws&&(this._debug(`removeListeners`),this._ws.removeEventListener(`open`,this._handleOpen),this._ws.removeEventListener(`close`,this._handleClose),this._ws.removeEventListener(`message`,this._handleMessage),this._ws.removeEventListener(`error`,this._handleError),this._ws.addEventListener(`error`,Ec))}_addListeners(){this._ws&&(this._debug(`addListeners`),this._ws.addEventListener(`open`,this._handleOpen),this._ws.addEventListener(`close`,this._handleClose),this._ws.addEventListener(`message`,this._handleMessage),this._ws.addEventListener(`error`,this._handleError))}_clearTimeouts(){clearTimeout(this._connectTimeout),clearTimeout(this._uptimeTimeout)}},Oc={minReconnectionDelay:1e3,maxReconnectionDelay:15e3,reconnectionDelayGrowFactor:2,maxRetries:10};function kc(e){return new Dc(e,void 0,Oc)}function Ac(e){return e instanceof Dc&&e.shouldReconnect&&e.retryCount<Oc.maxRetries}function jc(e){let{conn:t,getSnapshot:n,sendJson:r}=e,i=!1,a=0;async function o(e,n){for(let r=0;r<e.byteLength;r+=Co){for(;n===a&&t.ws&&t.ws.readyState===1&&t.ws.bufferedAmount>65536;)await new Promise(e=>setTimeout(e,50));if(n!==a)throw Error(`sendAudioFile: session was reset mid-send`);if(!t.ws||t.ws.readyState!==1)throw Error(`sendAudioFile: connection closed mid-send`);t.ws.send(e.subarray(r,r+Co))}}function s(){let e=t.readyConfig;if(!(e&&t.ws)||t.ws.readyState!==1)throw Error(`sendAudioFile: session is not connected`);let r=n();if(r.audioOut)throw Error(`sendAudioFile is only available in text-only sessions (tts: none()) — voice sessions stream the microphone instead`);if(r.recording)throw Error(`sendAudioFile: stop recording before uploading a file`);if(i)throw Error(`sendAudioFile: another upload is already in progress`);return e}async function c(e){let c=s();i=!0;let l=a;try{let{decodeAudioToPcm16:i}=await sc(async()=>{let{decodeAudioToPcm16:e}=await import(`./audio-NYmRKbVI.js`);return{decodeAudioToPcm16:e}},[],import.meta.url),s=await i(await e.arrayBuffer(),c.sampleRate);if(n().recording||t.audioSetupInFlight)throw Error(`sendAudioFile: stop recording before uploading a file`);if(l!==a)throw Error(`sendAudioFile: session was reset mid-send`);if(s.length/c.sampleRate<=120){let e=new Uint8Array(s.buffer,s.byteOffset,s.byteLength);r({type:`transcribe_file_start`,sampleRate:c.sampleRate,byteLength:e.byteLength}),await o(e,l),r({type:`transcribe_file_end`});return}let u=new Int16Array(s.length+c.sampleRate);u.set(s),await o(new Uint8Array(u.buffer),l)}finally{i=!1}}return{sendAudioFile:c,inFlight:()=>i,discard:()=>{a++}}}function Mc(e,t,n){let r=new URL(`websocket`,e.endsWith(`/`)?e:`${e}/`);return r.protocol=r.protocol===`https:`?`wss:`:`ws:`,n?r.searchParams.set(`sessionId`,n):t&&r.searchParams.set(`resume`,`1`),r}function Nc(e){let t={...fc,state:`disconnected`,contentVersion:0,started:!1,running:!1,audioOut:!0,recording:!1,apiUrl:Mc(e.platformUrl,!1).toString()},n=new Set;function r(){for(let e of n)e()}let i=[`messages`,`toolCalls`,`userTranscript`,`agentTranscript`];function a(e){t=i.some(n=>n in e&&e[n]!==t[n])?{...t,...e,contentVersion:t.contentVersion+1}:{...t,...e},r()}function o(){return t}function s(e){return n.add(e),()=>{n.delete(e)}}let c={ws:null,voiceIO:null,audioSetupInFlight:!1,generation:0,preInitAudio:[],preInitDone:!1,readyConfig:null},l=null,u=!1;function d(){h.discard(),c.audioSetupInFlight=!1,c.voiceIO?.close().catch(()=>{}),c.voiceIO=null,c.preInitAudio=[],c.preInitDone=!1}function f(){a(fc)}function p(e){c.ws&&c.ws.readyState===1&&c.ws.send(JSON.stringify(e))}function m(e){!c.ws||c.ws.readyState!==1||c.ws.bufferedAmount>65536||c.ws.send(e)}let h=jc({conn:c,getSnapshot:o,sendJson:p}),{handleMessage:g,settleWhenAudioDrained:_}=mc({getSnapshot:o,updateState:a,conn:c,discardUpload:h.discard,cleanupAudio:d}),v={sendJson:p,sendAudio:m,updateState:a,settleWhenAudioDrained:_,cleanupAudio:d};function y(){l?.abort(),l=null,d(),c.ws?.close(),c.ws=null}function b(n){n.sid&&e.onSessionId?.(n.sid);let r=u;u=!0,c.readyConfig={sampleRate:n.sampleRate,ttsSampleRate:n.ttsSampleRate};let i=n.audioOut!==!1;a({audioOut:i}),i?cc(c,n,v,!0):(p({type:`audio_ready`}),a({state:`listening`})),r&&t.messages.length>0&&p({type:`history`,messages:t.messages.map(e=>({role:e.role,content:e.content}))})}function x(){let t=u?void 0:e.resumeSessionId;return Mc(e.platformUrl,u,t).toString()}function S(){return e.WebSocket?new e.WebSocket(x()):kc(x)}function C(e){if(e?.signal?.aborted){te();return}a({state:`connecting`,error:null}),y(),c.generation++;let n=new AbortController;l=n;let{signal:r}=n;e?.signal&&e.signal.addEventListener(`abort`,()=>te(),{signal:r});let i=S();i.binaryType=`arraybuffer`,c.ws=i;let o=!1;i.addEventListener(`open`,()=>{a({state:`ready`})},{signal:r}),i.addEventListener(`message`,e=>{let t=g(e.data);t&&b(t)},{signal:r}),i.addEventListener(`error`,()=>{o=!0},{signal:r}),i.addEventListener(`close`,()=>{if(!r.aborted){if(d(),Ac(i)){c.generation++,o=!1,a({state:`connecting`,recording:!1});return}n.abort(),i.close(),c.ws=null,o?a({state:`error`,error:{code:`connection`,message:`WebSocket connection error`},running:!1,recording:!1}):t.state===`error`?a({running:!1,recording:!1}):a({state:`disconnected`,error:null,running:!1,recording:!1})}},{signal:r})}function w(){!c.ws||c.ws.readyState!==1||(c.voiceIO?.flush(),a({state:`listening`}),p({type:`cancel`}))}function ee(){if(h.discard(),c.voiceIO?.flush(),c.ws&&c.ws.readyState===1){p({type:`reset`});return}f(),te(),a({running:!0}),C()}function te(){y(),a({state:`disconnected`,running:!1,recording:!1})}function ne(){if(t.audioOut||t.recording||c.audioSetupInFlight||h.inFlight())return;let e=c.readyConfig;!(e&&c.ws)||c.ws.readyState!==1||cc(c,e,v,!1)}function re(){t.audioOut||!t.recording||(d(),a({recording:!1}))}function ie(){a({started:!0,running:!0}),C()}function ae(){t.running?te():(a({running:!0}),C())}return{getSnapshot:o,subscribe:s,connect:C,cancel:w,resetState:f,reset:ee,disconnect:te,start:ie,toggle:ae,startRecording:ne,stopRecording:re,sendAudioFile:h.sendAudioFile,[Symbol.dispose](){te()}}}function Pc(e=`#app`){if(typeof e!=`string`)return e;let t=document.querySelector(e);if(!t)throw Error(`Element not found: ${e}`);return t}function Fc({name:e,Sidebar:t,sidebarWidth:n}){let r=(0,W.jsx)(Bs,{title:e});return(0,W.jsx)(Hs,{title:e,children:t?(0,W.jsx)(Vs,{sidebar:(0,W.jsx)(t,{}),sidebarWidth:n,children:r}):r})}function Ic({platformUrl:e,transport:t,name:n,Sidebar:r,sidebarWidth:i}){let[a,o]=(0,B.useState)(t===void 0?null:{transport:t});return(0,B.useEffect)(()=>{if(t!==void 0)return;let n=!1;return qo(e).then(e=>{n||o(e)}),()=>{n=!0}},[e,t]),a?.transport===`sync`?(0,W.jsx)(rc,{syncUrl:Go(e,`sync`).href,title:n??a.name,greeting:a.greeting}):(0,W.jsx)(Fc,{name:n,Sidebar:r,sidebarWidth:i})}function G(e){let t=Pc(e.target),n=e.platformUrl??globalThis.location.origin+globalThis.location.pathname,r=Nc({platformUrl:n,onSessionId:e.onSessionId,resumeSessionId:e.resumeSessionId,WebSocket:e.WebSocket}),i=e.component?(0,B.createElement)(e.component):(0,B.createElement)(Ic,{platformUrl:n,transport:e.transport,name:e.name,Sidebar:e.sidebar,sidebarWidth:e.sidebarWidth}),a=e.tools??{},o=(0,fo.createRoot)(t);(0,uo.flushSync)(()=>{o.render((0,B.createElement)(Ts.Provider,{value:a},(0,B.createElement)(is,{value:e.theme},(0,B.createElement)(es,{value:r},i))))});let s={session:r,dispose(){o.unmount(),r[Symbol.dispose]()},[Symbol.dispose](){s.dispose()}};return s}G({});export{wo as t};
121
+ `),Ec=!0);let r=this._options.WebSocket||WebSocket;this._debug(`connect`,{url:e,protocols:n}),this._ws=n?new r(e,n):new r(e),this._ws.binaryType=this._binaryType,this._connectLock=!1,this._addListeners(),this._connectTimeout=setTimeout(()=>this._handleTimeout(),t)}).catch(e=>{this._connectLock=!1,this._handleError(new vc.ErrorEvent(Error(e.message),this))})}_handleTimeout(){this._debug(`timeout event`),this._handleError(new vc.ErrorEvent(Error(`TIMEOUT`),this))}_disconnect(e=1e3,t){if(this._clearTimeouts(),this._ws){this._removeListeners();try{(this._ws.readyState===this.OPEN||this._ws.readyState===this.CONNECTING)&&this._ws.close(e,t),this._handleClose(new vc.CloseEvent(e,t,this))}catch{}}}_acceptOpen(){this._debug(`accept open`),this._retryCount=0}_handleOpen=e=>{this._debug(`open event`);let{minUptime:t=Tc.minUptime}=this._options;clearTimeout(this._connectTimeout),this._uptimeTimeout=setTimeout(()=>this._acceptOpen(),t),yc(this._ws,`WebSocket is not defined`),this._ws.binaryType=this._binaryType,this._messageQueue.forEach(e=>{this._ws?.send(e)}),this._messageQueue=[],this.onopen&&this.onopen(e),this.dispatchEvent(wc(e))};_handleMessage=e=>{this._debug(`message event`),this.onmessage&&this.onmessage(e),this.dispatchEvent(wc(e))};_handleError=e=>{this._debug(`error event`,e.message),this._disconnect(void 0,e.message===`TIMEOUT`?`timeout`:void 0),this.onerror&&this.onerror(e),this._debug(`exec error listeners`),this.dispatchEvent(wc(e)),this._connect()};_handleClose=e=>{this._debug(`close event`),this._clearTimeouts(),this._options.shouldReconnectOnClose&&!this._options.shouldReconnectOnClose(e)&&(this._shouldReconnect=!1),this._shouldReconnect&&this._connect(),this.onclose&&this.onclose(e),this.dispatchEvent(wc(e))};_removeListeners(){this._ws&&(this._debug(`removeListeners`),this._ws.removeEventListener(`open`,this._handleOpen),this._ws.removeEventListener(`close`,this._handleClose),this._ws.removeEventListener(`message`,this._handleMessage),this._ws.removeEventListener(`error`,this._handleError),this._ws.addEventListener(`error`,Dc))}_addListeners(){this._ws&&(this._debug(`addListeners`),this._ws.addEventListener(`open`,this._handleOpen),this._ws.addEventListener(`close`,this._handleClose),this._ws.addEventListener(`message`,this._handleMessage),this._ws.addEventListener(`error`,this._handleError))}_clearTimeouts(){clearTimeout(this._connectTimeout),clearTimeout(this._uptimeTimeout)}},kc={minReconnectionDelay:1e3,maxReconnectionDelay:15e3,reconnectionDelayGrowFactor:2,maxRetries:10};function Ac(e){return new Oc(e,void 0,kc)}function jc(e){return e instanceof Oc&&e.shouldReconnect&&e.retryCount<kc.maxRetries}function Mc(e){let{conn:t,getSnapshot:n,sendJson:r}=e,i=!1,a=0;async function o(e,n){for(let r=0;r<e.byteLength;r+=Co){for(;n===a&&t.ws&&t.ws.readyState===1&&t.ws.bufferedAmount>65536;)await new Promise(e=>setTimeout(e,50));if(n!==a)throw Error(`sendAudioFile: session was reset mid-send`);if(!t.ws||t.ws.readyState!==1)throw Error(`sendAudioFile: connection closed mid-send`);t.ws.send(e.subarray(r,r+Co))}}function s(){let e=t.readyConfig;if(!(e&&t.ws)||t.ws.readyState!==1)throw Error(`sendAudioFile: session is not connected`);let r=n();if(r.audioOut)throw Error(`sendAudioFile is only available in text-only sessions (tts: none()) — voice sessions stream the microphone instead`);if(r.recording)throw Error(`sendAudioFile: stop recording before uploading a file`);if(i)throw Error(`sendAudioFile: another upload is already in progress`);return e}async function c(e){let c=s();i=!0;let l=a;try{let{decodeAudioToPcm16:i}=await cc(async()=>{let{decodeAudioToPcm16:e}=await import(`./audio-CTPVKBQ_.js`);return{decodeAudioToPcm16:e}},[],import.meta.url),s=await i(await e.arrayBuffer(),c.sampleRate);if(n().recording||t.audioSetupInFlight)throw Error(`sendAudioFile: stop recording before uploading a file`);if(l!==a)throw Error(`sendAudioFile: session was reset mid-send`);if(s.length/c.sampleRate<=120){let e=new Uint8Array(s.buffer,s.byteOffset,s.byteLength);r({type:`transcribe_file_start`,sampleRate:c.sampleRate,byteLength:e.byteLength}),await o(e,l),r({type:`transcribe_file_end`});return}let u=new Int16Array(s.length+c.sampleRate);u.set(s),await o(new Uint8Array(u.buffer),l)}finally{i=!1}}return{sendAudioFile:c,inFlight:()=>i,discard:()=>{a++}}}function Nc(e,t,n){let r=new URL(`websocket`,e.endsWith(`/`)?e:`${e}/`);return r.protocol=r.protocol===`https:`?`wss:`:`ws:`,n?r.searchParams.set(`sessionId`,n):t&&r.searchParams.set(`resume`,`1`),r}function Pc(e){let t={...pc,state:`disconnected`,contentVersion:0,started:!1,running:!1,audioOut:!0,recording:!1,apiUrl:Nc(e.platformUrl,!1).toString()},n=new Set;function r(){for(let e of n)e()}let i=[`messages`,`toolCalls`,`userTranscript`,`agentTranscript`];function a(e){t=i.some(n=>n in e&&e[n]!==t[n])?{...t,...e,contentVersion:t.contentVersion+1}:{...t,...e},r()}function o(){return t}function s(e){return n.add(e),()=>{n.delete(e)}}let c={ws:null,voiceIO:null,audioSetupInFlight:!1,generation:0,preInitAudio:[],preInitDone:!1,readyConfig:null},l=null,u=!1;function d(){h.discard(),c.audioSetupInFlight=!1,c.voiceIO?.close().catch(()=>{}),c.voiceIO=null,c.preInitAudio=[],c.preInitDone=!1}function f(){a(pc)}function p(e){c.ws&&c.ws.readyState===1&&c.ws.send(JSON.stringify(e))}function m(e){!c.ws||c.ws.readyState!==1||c.ws.bufferedAmount>65536||c.ws.send(e)}let h=Mc({conn:c,getSnapshot:o,sendJson:p}),{handleMessage:g,settleWhenAudioDrained:_}=hc({getSnapshot:o,updateState:a,conn:c,discardUpload:h.discard,cleanupAudio:d}),v={sendJson:p,sendAudio:m,updateState:a,settleWhenAudioDrained:_,cleanupAudio:d};function y(){l?.abort(),l=null,d(),c.ws?.close(),c.ws=null}function b(n){n.sid&&e.onSessionId?.(n.sid);let r=u;u=!0,c.readyConfig={sampleRate:n.sampleRate,ttsSampleRate:n.ttsSampleRate};let i=n.audioOut!==!1;a({audioOut:i}),i?lc(c,n,v,!0):(p({type:`audio_ready`}),a({state:`listening`})),r&&t.messages.length>0&&p({type:`history`,messages:t.messages.map(e=>({role:e.role,content:e.content}))})}function x(){let t=u?void 0:e.resumeSessionId;return Nc(e.platformUrl,u,t).toString()}function S(){return e.WebSocket?new e.WebSocket(x()):Ac(x)}function C(e){if(e?.signal?.aborted){te();return}a({state:`connecting`,error:null}),y(),c.generation++;let n=new AbortController;l=n;let{signal:r}=n;e?.signal&&e.signal.addEventListener(`abort`,()=>te(),{signal:r});let i=S();i.binaryType=`arraybuffer`,c.ws=i;let o=!1;i.addEventListener(`open`,()=>{a({state:`ready`})},{signal:r}),i.addEventListener(`message`,e=>{let t=g(e.data);t&&b(t)},{signal:r}),i.addEventListener(`error`,()=>{o=!0},{signal:r}),i.addEventListener(`close`,()=>{if(!r.aborted){if(d(),jc(i)){c.generation++,o=!1,a({state:`connecting`,recording:!1});return}n.abort(),i.close(),c.ws=null,o?a({state:`error`,error:{code:`connection`,message:`WebSocket connection error`},running:!1,recording:!1}):t.state===`error`?a({running:!1,recording:!1}):a({state:`disconnected`,error:null,running:!1,recording:!1})}},{signal:r})}function w(){!c.ws||c.ws.readyState!==1||(c.voiceIO?.flush(),a({state:`listening`}),p({type:`cancel`}))}function ee(){if(h.discard(),c.voiceIO?.flush(),c.ws&&c.ws.readyState===1){p({type:`reset`});return}f(),te(),a({running:!0}),C()}function te(){y(),a({state:`disconnected`,running:!1,recording:!1})}function ne(){if(t.audioOut||t.recording||c.audioSetupInFlight||h.inFlight())return;let e=c.readyConfig;!(e&&c.ws)||c.ws.readyState!==1||lc(c,e,v,!1)}function re(){t.audioOut||!t.recording||(d(),a({recording:!1}))}function ie(){a({started:!0,running:!0}),C()}function ae(){t.running?te():(a({running:!0}),C())}return{getSnapshot:o,subscribe:s,connect:C,cancel:w,resetState:f,reset:ee,disconnect:te,start:ie,toggle:ae,startRecording:ne,stopRecording:re,sendAudioFile:h.sendAudioFile,[Symbol.dispose](){te()}}}function Fc(e=`#app`){if(typeof e!=`string`)return e;let t=document.querySelector(e);if(!t)throw Error(`Element not found: ${e}`);return t}function Ic({name:e,Sidebar:t,sidebarWidth:n}){let r=(0,W.jsx)(Bs,{title:e});return(0,W.jsx)(Hs,{title:e,children:t?(0,W.jsx)(Vs,{sidebar:(0,W.jsx)(t,{}),sidebarWidth:n,children:r}):r})}function G({platformUrl:e,transport:t,name:n,Sidebar:r,sidebarWidth:i}){let[a,o]=(0,B.useState)(t===void 0?null:{transport:t});return(0,B.useEffect)(()=>{if(t!==void 0)return;let n=!1;return qo(e).then(e=>{n||o(e)}),()=>{n=!0}},[e,t]),a?.transport===`sync`?(0,W.jsx)(ic,{syncUrl:Go(e,`sync`).href,title:n??a.name,greeting:a.greeting}):(0,W.jsx)(Ic,{name:n,Sidebar:r,sidebarWidth:i})}function Lc(e){let t=Fc(e.target),n=e.platformUrl??globalThis.location.origin+globalThis.location.pathname,r=Pc({platformUrl:n,onSessionId:e.onSessionId,resumeSessionId:e.resumeSessionId,WebSocket:e.WebSocket}),i=e.component?(0,B.createElement)(e.component):(0,B.createElement)(G,{platformUrl:n,transport:e.transport,name:e.name,Sidebar:e.sidebar,sidebarWidth:e.sidebarWidth}),a=e.tools??{},o=(0,fo.createRoot)(t);(0,uo.flushSync)(()=>{o.render((0,B.createElement)(Ts.Provider,{value:a},(0,B.createElement)(is,{value:e.theme},(0,B.createElement)(es,{value:r},i))))});let s={session:r,dispose(){o.unmount(),r[Symbol.dispose]()},[Symbol.dispose](){s.dispose()}};return s}Lc({});export{wo as t};
@@ -6,8 +6,8 @@
6
6
  <title>aai</title>
7
7
  <link rel="icon" href="data:," />
8
8
  <style>html, body { background: #FBF8F2; margin: 0; }</style>
9
- <script type="module" crossorigin src="./assets/index-Dx70XGBr.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-NMhTOGW2.css">
9
+ <script type="module" crossorigin src="./assets/index-I5mZ1vB1.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-CF3RKVUo.css">
11
11
  </head>
12
12
  <body>
13
13
  <main id="app"></main>
@@ -1,11 +1,17 @@
1
1
  import { SessionProvider, ThemeProvider, useTheme } from "./context.js";
2
- import "./_colors-DYX7XRTr.js";
2
+ import { Button } from "./components/button.js";
3
+ import { i as TEXT_MUTED, r as TEXT_FAINT, t as ERROR_COLOR } from "./_colors-DYX7XRTr.js";
4
+ import { t as AaiLogo } from "./aai-logo-B8lDmsut.js";
5
+ import { a as UrlChip } from "./controls-BbZcmnJf.js";
6
+ import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
3
7
  import { n as ToolConfigContext } from "./tool-call-block-DIxpG8GM.js";
4
- import { t as ChatView } from "./chat-view-u3yBAlig.js";
8
+ import { ThinkingDots } from "./components/message-list.js";
9
+ import { n as stateColor, t as ChatView } from "./chat-view-gi6FccZq.js";
5
10
  import { SidebarLayout } from "./components/sidebar-layout.js";
6
11
  import { StartScreen } from "./components/start-screen.js";
7
12
  import { t as createSessionCore } from "./session-core-B64kau_v.js";
8
13
  import { CLIENT_CONFIG_PATH, ClientConfigResponseSchema, SyncTurnResponseSchema } from "@alexkroman1/aai/protocol";
14
+ import clsx from "clsx";
9
15
  import { createElement, useEffect, useRef, useState } from "react";
10
16
  import { jsx, jsxs } from "react/jsx-runtime";
11
17
  import { errorMessage, safeJsonParse } from "@alexkroman1/aai";
@@ -163,15 +169,23 @@ function createUtteranceDetector(opts) {
163
169
  * through the utterance detector, and hands each completed utterance to
164
170
  * the sync session as one HTTP turn. No WebSocket anywhere on the path.
165
171
  *
166
- * The worklet module ships inline as a data URI, so sync mode needs no
167
- * separately-served processor file.
172
+ * The worklet module ships inline as a blob URL (same pattern as the
173
+ * WebSocket path's worklets), so sync mode needs no separately-served
174
+ * processor file. A blob URL rather than a data URI because the agent
175
+ * page's CSP allows `script-src blob:` but not `data:` — a data-URI
176
+ * module fails `addModule` with "Unable to load a worklet's module".
168
177
  */
169
178
  /** Default capture rate — what the STT providers expect. */
170
179
  const DEFAULT_SYNC_MIC_SAMPLE_RATE = 16e3;
171
180
  /** ~128 ms at 16 kHz: few messages per second, fine-enough VAD granularity. */
172
181
  const CAPTURE_BATCH_SAMPLES = 2048;
173
- /** Data URI form of the capture processor (no served asset, no blob URL). */
174
- const CAPTURE_WORKLET_DATA_URI = `data:application/javascript;charset=utf-8,${encodeURIComponent(`
182
+ /**
183
+ * The capture processor: coalesces 128-sample render quanta into
184
+ * {@link CAPTURE_BATCH_SAMPLES} batches and posts them (transferred, so no
185
+ * per-batch copy). Inlined as source because it must be stringified into a
186
+ * blob URL.
187
+ */
188
+ const CAPTURE_PROCESSOR_SRC = `
175
189
  registerProcessor("aai-sync-capture", class extends AudioWorkletProcessor {
176
190
  constructor(options) {
177
191
  super();
@@ -198,7 +212,12 @@ registerProcessor("aai-sync-capture", class extends AudioWorkletProcessor {
198
212
  return true;
199
213
  }
200
214
  });
201
- `)}`;
215
+ `;
216
+ /**
217
+ * Blob-URL module for the capture processor (no served asset). Satisfies the
218
+ * agent page's `script-src blob:` CSP, which rejects data-URI modules.
219
+ */
220
+ const CAPTURE_WORKLET_MODULE_URL = URL.createObjectURL(new Blob([CAPTURE_PROCESSOR_SRC], { type: "application/javascript" }));
202
221
  /** Clamp-and-convert one Float32 capture batch to PCM16. */
203
222
  function floatToPcm16(samples) {
204
223
  const pcm = new Int16Array(samples.length);
@@ -210,6 +229,89 @@ function floatToPcm16(samples) {
210
229
  return pcm;
211
230
  }
212
231
  /**
232
+ * Push-to-talk recorder on the same WebRTC capture pipeline as
233
+ * {@link startSyncMicrophone} — `getUserMedia` voice processing feeding the
234
+ * capture worklet — minus the VAD: the caller's button is the endpointing.
235
+ * Recording runs exactly between `start()` and `stop()`; the mic stays open
236
+ * across presses until `close()`.
237
+ *
238
+ * @public
239
+ */
240
+ function createPttRecorder(sampleRate = DEFAULT_SYNC_MIC_SAMPLE_RATE) {
241
+ let ctx = null;
242
+ let stream = null;
243
+ let node = null;
244
+ let chunks = [];
245
+ let recording = false;
246
+ async function ensureOpen() {
247
+ if (ctx) return;
248
+ const streamPromise = navigator.mediaDevices.getUserMedia({ audio: {
249
+ echoCancellation: true,
250
+ noiseSuppression: true,
251
+ autoGainControl: true
252
+ } });
253
+ const audioCtx = new AudioContext({
254
+ sampleRate,
255
+ latencyHint: "interactive"
256
+ });
257
+ try {
258
+ const [media] = await Promise.all([
259
+ streamPromise,
260
+ audioCtx.resume(),
261
+ audioCtx.audioWorklet.addModule(CAPTURE_WORKLET_MODULE_URL)
262
+ ]);
263
+ stream = media;
264
+ } catch (err) {
265
+ streamPromise.then((s) => {
266
+ for (const t of s.getTracks()) t.stop();
267
+ }).catch(() => {});
268
+ await audioCtx.close().catch(() => {});
269
+ throw err;
270
+ }
271
+ const workletNode = new AudioWorkletNode(audioCtx, "aai-sync-capture", {
272
+ channelCount: 1,
273
+ channelCountMode: "explicit",
274
+ processorOptions: { batchSamples: CAPTURE_BATCH_SAMPLES }
275
+ });
276
+ workletNode.port.onmessage = (e) => {
277
+ const data = e.data;
278
+ if (recording && data.event === "chunk" && data.samples) chunks.push(data.samples);
279
+ };
280
+ audioCtx.createMediaStreamSource(stream).connect(workletNode);
281
+ ctx = audioCtx;
282
+ node = workletNode;
283
+ }
284
+ return {
285
+ async start() {
286
+ await ensureOpen();
287
+ chunks = [];
288
+ recording = true;
289
+ },
290
+ async stop() {
291
+ await new Promise((r) => setTimeout(r, 150));
292
+ recording = false;
293
+ const total = chunks.reduce((n, c) => n + c.length, 0);
294
+ const all = new Float32Array(total);
295
+ let offset = 0;
296
+ for (const c of chunks) {
297
+ all.set(c, offset);
298
+ offset += c.length;
299
+ }
300
+ chunks = [];
301
+ return floatToPcm16(all);
302
+ },
303
+ async close() {
304
+ recording = false;
305
+ node?.disconnect();
306
+ if (stream) for (const t of stream.getTracks()) t.stop();
307
+ await ctx?.close().catch(() => {});
308
+ ctx = null;
309
+ node = null;
310
+ stream = null;
311
+ }
312
+ };
313
+ }
314
+ /**
213
315
  * Open the microphone and stream endpointed utterances into a sync session.
214
316
  *
215
317
  * @throws If microphone access is denied or worklet registration fails.
@@ -237,7 +339,7 @@ async function startSyncMicrophone(opts) {
237
339
  [stream] = await Promise.all([
238
340
  streamPromise,
239
341
  ctx.resume(),
240
- ctx.audioWorklet.addModule(CAPTURE_WORKLET_DATA_URI)
342
+ ctx.audioWorklet.addModule(CAPTURE_WORKLET_MODULE_URL)
241
343
  ]);
242
344
  } catch (err) {
243
345
  streamPromise.then((s) => {
@@ -390,19 +492,33 @@ function createSyncSession(opts) {
390
492
  //#region components/sync-chat-view.tsx
391
493
  /** @jsxImportSource react */
392
494
  /**
393
- * Default chat shell for sync-transport agents (`agent({ transport: "sync" })`).
495
+ * Default shell for sync-transport agents (`agent({ transport: "sync" })`).
394
496
  *
395
- * No WebSocket anywhere: voice turns are endpointed in the browser (WebRTC
396
- * mic + energy VAD via `startSyncMicrophone`) and each utterance — or each
397
- * typed message is one `POST /sync` request through `createSyncSession`.
398
- * The conversation history lives client-side and replays with every turn.
497
+ * A hands-free voice agent: one toggle starts the conversation, and from
498
+ * then on the mic stays open `startSyncMicrophone` runs the WebRTC
499
+ * voice-processing capture through the energy VAD (`sync-vad.ts`), which
500
+ * endpoints each utterance automatically and sends it as one `POST /sync`
501
+ * request through `createSyncSession`. No button per turn: speak, pause,
502
+ * and the reply comes back and plays. The view shows what was heard, the
503
+ * agent's reply, and — via the endpoint chip next to the toggle — exactly
504
+ * where each utterance is being sent.
505
+ *
506
+ * Visually it is the same "voice agent console" as the WebSocket
507
+ * {@link ChatView}: header with logo + live-status eyebrow, the output on a
508
+ * raised card, controls beneath — built from the same shared pieces
509
+ * ({@link ThinkingDots}, {@link Eyebrow}, {@link Button}, {@link UrlChip})
510
+ * so the two transports are indistinguishable at a glance.
399
511
  *
400
512
  * Rendered by `client()` when the agent's `GET /client-config` declares
401
513
  * `transport: "sync"`; also exported for custom clients that want the stock
402
514
  * sync UI with their own chrome around it.
403
515
  */
404
- /** Play one reply's PCM16 through a shared AudioContext. */
405
- function playReply(ctxRef, turn) {
516
+ /**
517
+ * Play one reply's PCM16 through a shared AudioContext. `onPlaying`
518
+ * tracks playback so the eyebrow can show "speaking" while the reply is
519
+ * audible.
520
+ */
521
+ function playReply(ctxRef, turn, onPlaying) {
406
522
  if (!(turn.pcm && turn.sampleRate)) return;
407
523
  ctxRef.current ??= new AudioContext();
408
524
  const ctx = ctxRef.current;
@@ -414,173 +530,227 @@ function playReply(ctxRef, turn) {
414
530
  const source = ctx.createBufferSource();
415
531
  source.buffer = buffer;
416
532
  source.connect(ctx.destination);
533
+ source.onended = () => onPlaying(false);
534
+ onPlaying(true);
417
535
  source.start();
418
536
  }
419
537
  /**
420
- * Sync-transport chat view: mic toggle (client-side VAD), text composer,
421
- * message list, and spoken-reply playback one HTTP request per turn.
538
+ * Map the hands-free conversation lifecycle onto the same states the
539
+ * WebSocket eyebrow shows, so the status chip reads identically across
540
+ * transports: an endpointed utterance in flight is "thinking", an audible
541
+ * reply is "speaking", a live mic is "listening".
542
+ */
543
+ function syncState(opts) {
544
+ if (opts.pending > 0) return "thinking";
545
+ if (opts.agentSpeaking) return "speaking";
546
+ if (opts.live) return "listening";
547
+ if (opts.error) return "error";
548
+ return "ready";
549
+ }
550
+ /**
551
+ * Sync-transport view: a hands-free VAD-endpointed conversation, transcript
552
+ * + reply output, and the endpoint each utterance is POSTed to — one HTTP
553
+ * request per turn.
422
554
  *
423
555
  * @public
424
556
  */
425
557
  function SyncChatView({ syncUrl, title, greeting }) {
426
558
  const theme = useTheme();
427
- const [lines, setLines] = useState([]);
428
- const [draft, setDraft] = useState("");
429
- const [busy, setBusy] = useState(false);
430
- const [micOn, setMicOn] = useState(false);
559
+ const [exchanges, setExchanges] = useState([]);
560
+ const [live, setLive] = useState(false);
561
+ const [userSpeaking, setUserSpeaking] = useState(false);
562
+ const [pending, setPending] = useState(0);
563
+ const [agentSpeaking, setAgentSpeaking] = useState(false);
431
564
  const [error, setError] = useState(null);
432
565
  const playbackCtx = useRef(null);
433
- const micRef = useRef(null);
566
+ const mic = useRef(null);
567
+ const toggling = useRef(false);
568
+ const anchorRef = useRef(null);
434
569
  const sessionRef = useRef(createSyncSession({
435
570
  url: syncUrl,
436
571
  onTurn: (turn) => {
437
- setLines((prev) => [
438
- ...prev,
439
- {
440
- id: prev.length,
441
- role: "user",
442
- text: turn.transcript
443
- },
444
- {
445
- id: prev.length + 1,
446
- role: "assistant",
447
- text: turn.reply
448
- }
449
- ]);
450
- setBusy(false);
572
+ setExchanges((prev) => [...prev, {
573
+ id: prev.length,
574
+ heard: turn.transcript,
575
+ reply: turn.reply
576
+ }]);
577
+ setPending((n) => Math.max(0, n - 1));
451
578
  setError(turn.ttsError ? `TTS unavailable: ${turn.ttsError}` : null);
452
- playReply(playbackCtx, turn);
579
+ playReply(playbackCtx, turn, setAgentSpeaking);
453
580
  },
454
581
  onError: (err) => {
455
- setBusy(false);
582
+ setPending((n) => Math.max(0, n - 1));
456
583
  setError(err.message);
457
584
  }
458
585
  }));
459
586
  useEffect(() => () => {
460
- micRef.current?.stop();
587
+ mic.current?.stop();
461
588
  playbackCtx.current?.close();
462
589
  }, []);
463
- async function toggleMic() {
464
- if (micRef.current) {
465
- const mic = micRef.current;
466
- micRef.current = null;
467
- setMicOn(false);
468
- await mic.stop();
469
- return;
470
- }
590
+ const contentCount = exchanges.length + (pending > 0 ? 1 : 0);
591
+ useEffect(() => {
592
+ if (contentCount === 0) return;
593
+ anchorRef.current?.scrollIntoView({
594
+ behavior: "smooth",
595
+ block: "end"
596
+ });
597
+ }, [contentCount]);
598
+ async function toggleConversation() {
599
+ if (toggling.current) return;
600
+ toggling.current = true;
471
601
  try {
472
- micRef.current = await startSyncMicrophone({
602
+ if (mic.current) {
603
+ const handle = mic.current;
604
+ mic.current = null;
605
+ setLive(false);
606
+ setUserSpeaking(false);
607
+ await handle.stop();
608
+ return;
609
+ }
610
+ mic.current = await startSyncMicrophone({
473
611
  session: sessionRef.current,
474
- onSpeechEnd: () => setBusy(true),
612
+ onSpeechStart: () => setUserSpeaking(true),
613
+ onSpeechEnd: () => {
614
+ setUserSpeaking(false);
615
+ setPending((n) => n + 1);
616
+ },
475
617
  onError: (err) => setError(err.message)
476
618
  });
477
- setMicOn(true);
619
+ setLive(true);
478
620
  setError(null);
479
621
  } catch (err) {
480
622
  setError(err instanceof Error ? err.message : String(err));
623
+ } finally {
624
+ toggling.current = false;
481
625
  }
482
626
  }
483
- function sendDraft() {
484
- const text = draft.trim();
485
- if (!text || busy) return;
486
- setDraft("");
487
- setBusy(true);
488
- sessionRef.current.sendText(text).catch(() => {});
489
- }
627
+ const state = syncState({
628
+ error,
629
+ live,
630
+ pending,
631
+ agentSpeaking
632
+ });
633
+ const buttonLabel = live ? "End conversation" : "Start conversation";
634
+ const pulsing = userSpeaking || agentSpeaking;
490
635
  return /* @__PURE__ */ jsxs("div", {
491
- className: "flex flex-col h-screen max-w-2xl mx-auto font-aai",
636
+ className: "flex flex-col h-screen w-full max-w-190 mx-auto box-border px-6 py-8 gap-5 font-aai text-sm",
492
637
  style: {
493
638
  background: theme.bg,
494
639
  color: theme.text
495
640
  },
496
641
  children: [
497
- /* @__PURE__ */ jsxs("header", {
498
- className: "px-4 py-3 border-b flex items-center justify-between shrink-0",
499
- style: { borderColor: theme.border },
500
- children: [/* @__PURE__ */ jsx("h1", {
501
- className: "font-bold",
502
- children: title ?? "Voice Agent"
503
- }), /* @__PURE__ */ jsx("span", {
504
- className: "text-xs opacity-60",
505
- children: "HTTP turns — no WebSocket"
642
+ /* @__PURE__ */ jsxs("div", {
643
+ className: "flex items-center justify-between shrink-0",
644
+ children: [/* @__PURE__ */ jsxs("div", {
645
+ className: "flex items-center gap-3 min-w-0",
646
+ children: [/* @__PURE__ */ jsx(AaiLogo, { size: 22 }), /* @__PURE__ */ jsx("span", {
647
+ className: "font-aai-serif text-[22px] leading-[1.2] font-normal truncate",
648
+ style: { color: theme.text },
649
+ children: title ?? "Voice Agent"
650
+ })]
651
+ }), /* @__PURE__ */ jsxs(Eyebrow, {
652
+ className: "shrink-0",
653
+ "data-state": state,
654
+ children: [/* @__PURE__ */ jsx("span", {
655
+ className: "w-[7px] h-[7px] rounded-full",
656
+ style: {
657
+ background: stateColor(state, theme.primary),
658
+ animation: pulsing ? "aai-pulse 1.6s ease-in-out infinite" : "none"
659
+ }
660
+ }), state]
506
661
  })]
507
662
  }),
508
- /* @__PURE__ */ jsxs("main", {
509
- className: "flex-1 overflow-y-auto px-4 py-3 space-y-2",
510
- children: [
511
- greeting !== void 0 && greeting.length > 0 && /* @__PURE__ */ jsx("div", {
512
- className: "max-w-[85%] rounded-lg px-3 py-2 text-sm whitespace-pre-wrap",
513
- style: {
514
- background: theme.surface,
515
- color: theme.text
516
- },
517
- children: greeting
518
- }),
519
- lines.length === 0 && /* @__PURE__ */ jsx("p", {
520
- className: "text-sm",
521
- style: { color: "#57534B" },
522
- children: "Turn the mic on and speak — each utterance becomes one HTTP request — or type below."
523
- }),
524
- lines.map((line) => /* @__PURE__ */ jsx("div", {
525
- className: `max-w-[85%] rounded-lg px-3 py-2 text-sm whitespace-pre-wrap ${line.role === "user" ? "ml-auto" : ""}`,
526
- style: {
527
- background: line.role === "user" ? theme.primary : theme.surface,
528
- color: line.role === "user" ? "#fff" : theme.text
529
- },
530
- children: line.text
531
- }, line.id)),
532
- busy && /* @__PURE__ */ jsx("p", {
533
- className: "text-sm opacity-60 animate-pulse",
534
- children: "Thinking…"
535
- }),
536
- error && /* @__PURE__ */ jsx("p", {
537
- className: "text-sm",
538
- style: { color: "#dc2626" },
539
- children: error
540
- })
541
- ]
663
+ error && /* @__PURE__ */ jsx("div", {
664
+ className: "px-3.5 py-2.5 rounded-aai border text-[13px] leading-[130%] shrink-0",
665
+ style: {
666
+ borderColor: "rgba(179,38,30,0.35)",
667
+ background: "rgba(179,38,30,0.06)",
668
+ color: "#B3261E"
669
+ },
670
+ children: error
542
671
  }),
543
- /* @__PURE__ */ jsxs("footer", {
544
- className: "p-3 border-t flex gap-2 items-center shrink-0",
545
- style: { borderColor: theme.border },
546
- children: [
547
- /* @__PURE__ */ jsx("button", {
548
- type: "button",
549
- onClick: () => void toggleMic(),
550
- "aria-pressed": micOn,
551
- className: "rounded-full w-11 h-11 shrink-0 text-lg",
552
- style: {
553
- background: micOn ? "#dc2626" : theme.primary,
554
- color: "#fff"
555
- },
556
- title: micOn ? "Stop listening" : "Start listening",
557
- children: micOn ? "■" : "🎤"
558
- }),
559
- /* @__PURE__ */ jsx("input", {
560
- className: "flex-1 rounded-lg px-3 py-2 text-sm border bg-transparent",
561
- style: {
562
- borderColor: theme.border,
563
- color: theme.text
564
- },
565
- placeholder: "Type a message…",
566
- value: draft,
567
- onChange: (e) => setDraft(e.target.value),
568
- onKeyDown: (e) => {
569
- if (e.key === "Enter") sendDraft();
570
- }
571
- }),
572
- /* @__PURE__ */ jsx("button", {
573
- type: "button",
574
- onClick: sendDraft,
575
- disabled: busy || draft.trim().length === 0,
576
- className: "rounded-lg px-4 py-2 text-sm font-medium disabled:opacity-50",
577
- style: {
578
- background: theme.primary,
579
- color: "#fff"
580
- },
581
- children: "Send"
672
+ /* @__PURE__ */ jsx("div", {
673
+ className: "flex flex-col flex-1 min-h-0 border rounded-lg overflow-hidden",
674
+ style: {
675
+ background: theme.surface,
676
+ borderColor: theme.border,
677
+ boxShadow: "0 1px 3px 0 rgb(20 18 12 / 0.06)"
678
+ },
679
+ children: /* @__PURE__ */ jsx("div", {
680
+ role: "log",
681
+ className: "flex-1 overflow-y-auto [scrollbar-width:none]",
682
+ style: { background: theme.surface },
683
+ children: /* @__PURE__ */ jsxs("div", {
684
+ className: "flex flex-col gap-5 p-7",
685
+ children: [
686
+ greeting !== void 0 && greeting.length > 0 && /* @__PURE__ */ jsx("p", {
687
+ className: "text-[15px] leading-[23px]",
688
+ style: { color: theme.text },
689
+ children: greeting
690
+ }),
691
+ exchanges.length === 0 && /* @__PURE__ */ jsx("p", {
692
+ className: "text-sm",
693
+ style: { color: "#57534B" },
694
+ children: "Start the conversation and just talk — each pause endpoints an utterance, which goes out as one HTTP request to the endpoint below."
695
+ }),
696
+ exchanges.map((ex) => /* @__PURE__ */ jsxs("div", {
697
+ className: "flex flex-col gap-1.5",
698
+ children: [
699
+ /* @__PURE__ */ jsx("span", {
700
+ className: "text-[10px] font-medium tracking-[1.2px] uppercase leading-none",
701
+ style: { color: TEXT_FAINT },
702
+ children: "Heard"
703
+ }),
704
+ /* @__PURE__ */ jsx("p", {
705
+ className: "text-[15px] leading-[22px]",
706
+ style: { color: TEXT_MUTED },
707
+ children: ex.heard
708
+ }),
709
+ /* @__PURE__ */ jsx("span", {
710
+ className: "text-[10px] font-medium tracking-[1.2px] uppercase leading-none mt-1.5",
711
+ style: { color: TEXT_FAINT },
712
+ children: "Agent"
713
+ }),
714
+ /* @__PURE__ */ jsx("p", {
715
+ className: "whitespace-pre-wrap wrap-break-word text-[15px] font-normal leading-[23px]",
716
+ style: { color: theme.text },
717
+ children: ex.reply
718
+ })
719
+ ]
720
+ }, ex.id)),
721
+ pending > 0 && /* @__PURE__ */ jsx("div", {
722
+ "data-testid": "thinking",
723
+ children: /* @__PURE__ */ jsx(ThinkingDots, {})
724
+ }),
725
+ /* @__PURE__ */ jsx("div", { ref: anchorRef })
726
+ ]
582
727
  })
583
- ]
728
+ })
729
+ }),
730
+ /* @__PURE__ */ jsxs("div", {
731
+ className: "flex items-center gap-2 shrink-0",
732
+ children: [/* @__PURE__ */ jsxs(Button, {
733
+ size: "lg",
734
+ variant: live ? "default" : "secondary",
735
+ className: "select-none",
736
+ style: live ? {
737
+ background: ERROR_COLOR,
738
+ borderColor: "transparent"
739
+ } : void 0,
740
+ onClick: () => void toggleConversation(),
741
+ "aria-pressed": live,
742
+ title: "Start or end the conversation",
743
+ children: [/* @__PURE__ */ jsx("span", {
744
+ className: clsx("w-2 h-2 rounded-full mr-2", pulsing && "animate-pulse"),
745
+ style: { background: live ? "#fff" : ERROR_COLOR }
746
+ }), buttonLabel]
747
+ }), /* @__PURE__ */ jsx(UrlChip, {
748
+ label: "Sync",
749
+ url: syncUrl,
750
+ hint: "Each utterance is one POST to this endpoint",
751
+ testId: "sync-url-chip",
752
+ className: "ml-auto min-w-0 max-w-[55%]"
753
+ })]
584
754
  })
585
755
  ]
586
756
  });
@@ -708,4 +878,4 @@ function client(config) {
708
878
  return handle;
709
879
  }
710
880
  //#endregion
711
- export { pcm16ToBase64 as a, floatToPcm16 as c, buildAgentUrl as d, fetchClientConfig as f, createSyncSession as i, startSyncMicrophone as l, SyncChatView as n, CAPTURE_WORKLET_DATA_URI as o, base64ToPcm16 as r, DEFAULT_SYNC_MIC_SAMPLE_RATE as s, client as t, createUtteranceDetector as u };
881
+ export { pcm16ToBase64 as a, createPttRecorder as c, createUtteranceDetector as d, buildAgentUrl as f, createSyncSession as i, floatToPcm16 as l, SyncChatView as n, CAPTURE_WORKLET_MODULE_URL as o, fetchClientConfig as p, base64ToPcm16 as r, DEFAULT_SYNC_MIC_SAMPLE_RATE as s, client as t, startSyncMicrophone as u };
@@ -1,4 +1,4 @@
1
- import { t as client } from "./define-client-Cl1bl24M.js";
1
+ import { t as client } from "./define-client-Dx4JaWzL.js";
2
2
  import "./context.js";
3
3
  import "./components/sidebar-layout.js";
4
4
  import "./components/start-screen.js";
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export { client } from "./define-client.tsx";
17
17
  export { useEvent, useToolCallStart, useToolResult } from "./hooks.ts";
18
18
  export type { CustomEvent, SessionCore, SessionCoreOptions, SessionSnapshot, } from "./session-core.ts";
19
19
  export { createSessionCore } from "./session-core.ts";
20
- export { CAPTURE_WORKLET_DATA_URI, DEFAULT_SYNC_MIC_SAMPLE_RATE, floatToPcm16, type SyncMicrophone, type SyncMicrophoneOptions, startSyncMicrophone, } from "./sync-mic.ts";
20
+ export { CAPTURE_WORKLET_MODULE_URL, createPttRecorder, DEFAULT_SYNC_MIC_SAMPLE_RATE, floatToPcm16, type PttRecorder, type SyncMicrophone, type SyncMicrophoneOptions, startSyncMicrophone, } from "./sync-mic.ts";
21
21
  export { base64ToPcm16, createSyncSession, pcm16ToBase64, type SyncSession, type SyncSessionOptions, type SyncTurnResult, } from "./sync-session.ts";
22
22
  export { createUtteranceDetector, type UtteranceDetector, type UtteranceDetectorOptions, } from "./sync-vad.ts";
23
23
  export type { AgentState, ChatMessage, ClientTheme, SessionError, SessionErrorCode, ToolCallInfo, VoiceSessionOptions, WebSocketConstructor, } from "./types.ts";
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
- import { a as pcm16ToBase64, c as floatToPcm16, d as buildAgentUrl, f as fetchClientConfig, i as createSyncSession, l as startSyncMicrophone, n as SyncChatView, o as CAPTURE_WORKLET_DATA_URI, r as base64ToPcm16, s as DEFAULT_SYNC_MIC_SAMPLE_RATE, t as client, u as createUtteranceDetector } from "./define-client-Cl1bl24M.js";
1
+ import { a as pcm16ToBase64, c as createPttRecorder, d as createUtteranceDetector, f as buildAgentUrl, i as createSyncSession, l as floatToPcm16, n as SyncChatView, o as CAPTURE_WORKLET_MODULE_URL, p as fetchClientConfig, r as base64ToPcm16, s as DEFAULT_SYNC_MIC_SAMPLE_RATE, t as client, u as startSyncMicrophone } from "./define-client-Dx4JaWzL.js";
2
2
  import { SessionProvider, ThemeProvider, useSession, useSessionSelector, useTheme } from "./context.js";
3
3
  import { Button } from "./components/button.js";
4
- import { i as UiUrlChip, n as ApiUrlChip, r as SessionUrlChips, t as Controls } from "./controls-B2EPUJDU.js";
4
+ import { i as UiUrlChip, n as ApiUrlChip, r as SessionUrlChips, t as Controls } from "./controls-BbZcmnJf.js";
5
5
  import { n as ToolConfigContext } from "./tool-call-block-DIxpG8GM.js";
6
6
  import { MessageList } from "./components/message-list.js";
7
- import { n as TextControls, t as ChatView } from "./chat-view-u3yBAlig.js";
7
+ import { r as TextControls, t as ChatView } from "./chat-view-gi6FccZq.js";
8
8
  import { SidebarLayout } from "./components/sidebar-layout.js";
9
9
  import { StartScreen } from "./components/start-screen.js";
10
10
  import { t as createSessionCore } from "./session-core-B64kau_v.js";
11
11
  import { useEvent, useToolCallStart, useToolResult } from "./hooks.js";
12
- export { ApiUrlChip, Button, CAPTURE_WORKLET_DATA_URI, ChatView, Controls, DEFAULT_SYNC_MIC_SAMPLE_RATE, MessageList, SessionProvider, SessionUrlChips, SidebarLayout, StartScreen, SyncChatView, TextControls, ThemeProvider, ToolConfigContext, UiUrlChip, base64ToPcm16, buildAgentUrl, client, createSessionCore, createSyncSession, createUtteranceDetector, fetchClientConfig, floatToPcm16, pcm16ToBase64, startSyncMicrophone, useEvent, useSession, useSessionSelector, useTheme, useToolCallStart, useToolResult };
12
+ export { ApiUrlChip, Button, CAPTURE_WORKLET_MODULE_URL, ChatView, Controls, DEFAULT_SYNC_MIC_SAMPLE_RATE, MessageList, SessionProvider, SessionUrlChips, SidebarLayout, StartScreen, SyncChatView, TextControls, ThemeProvider, ToolConfigContext, UiUrlChip, base64ToPcm16, buildAgentUrl, client, createPttRecorder, createSessionCore, createSyncSession, createUtteranceDetector, fetchClientConfig, floatToPcm16, pcm16ToBase64, startSyncMicrophone, useEvent, useSession, useSessionSelector, useTheme, useToolCallStart, useToolResult };
@@ -8,15 +8,21 @@
8
8
  * through the utterance detector, and hands each completed utterance to
9
9
  * the sync session as one HTTP turn. No WebSocket anywhere on the path.
10
10
  *
11
- * The worklet module ships inline as a data URI, so sync mode needs no
12
- * separately-served processor file.
11
+ * The worklet module ships inline as a blob URL (same pattern as the
12
+ * WebSocket path's worklets), so sync mode needs no separately-served
13
+ * processor file. A blob URL rather than a data URI because the agent
14
+ * page's CSP allows `script-src blob:` but not `data:` — a data-URI
15
+ * module fails `addModule` with "Unable to load a worklet's module".
13
16
  */
14
17
  import type { SyncSession } from "./sync-session.ts";
15
18
  import { type UtteranceDetectorOptions } from "./sync-vad.ts";
16
19
  /** Default capture rate — what the STT providers expect. */
17
20
  export declare const DEFAULT_SYNC_MIC_SAMPLE_RATE = 16000;
18
- /** Data URI form of the capture processor (no served asset, no blob URL). */
19
- export declare const CAPTURE_WORKLET_DATA_URI: string;
21
+ /**
22
+ * Blob-URL module for the capture processor (no served asset). Satisfies the
23
+ * agent page's `script-src blob:` CSP, which rejects data-URI modules.
24
+ */
25
+ export declare const CAPTURE_WORKLET_MODULE_URL: string;
20
26
  /** Clamp-and-convert one Float32 capture batch to PCM16. */
21
27
  export declare function floatToPcm16(samples: Float32Array): Int16Array;
22
28
  /** Configuration for {@link startSyncMicrophone}. */
@@ -41,6 +47,25 @@ export type SyncMicrophone = {
41
47
  /** Release the mic, the AudioContext, and flush a trailing utterance. */
42
48
  stop(): Promise<void>;
43
49
  };
50
+ /** Hold-to-record handle returned by {@link createPttRecorder}. */
51
+ export type PttRecorder = {
52
+ /** Open the mic (first call) and start collecting frames. */
53
+ start(): Promise<void>;
54
+ /** Stop collecting and return everything recorded since `start()` as PCM16. */
55
+ stop(): Promise<Int16Array>;
56
+ /** Release the mic and the AudioContext. */
57
+ close(): Promise<void>;
58
+ };
59
+ /**
60
+ * Push-to-talk recorder on the same WebRTC capture pipeline as
61
+ * {@link startSyncMicrophone} — `getUserMedia` voice processing feeding the
62
+ * capture worklet — minus the VAD: the caller's button is the endpointing.
63
+ * Recording runs exactly between `start()` and `stop()`; the mic stays open
64
+ * across presses until `close()`.
65
+ *
66
+ * @public
67
+ */
68
+ export declare function createPttRecorder(sampleRate?: number): PttRecorder;
44
69
  /**
45
70
  * Open the microphone and stream endpointed utterances into a sync session.
46
71
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-ui",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -20,7 +20,7 @@
20
20
  "clsx": "^2.1.1",
21
21
  "partysocket": "^1.3.0",
22
22
  "use-sync-external-store": "^1.6.0",
23
- "@alexkroman1/aai": "1.12.0"
23
+ "@alexkroman1/aai": "1.14.0"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "react": "^19.0.0",
@@ -1,2 +0,0 @@
1
- /*! tailwindcss v4.3.3 | 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-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--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-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, 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-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-normal:400;--font-weight-medium:500;--font-weight-bold:700;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--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);--font-aai:"Monument Grotesk", "ABC Monument Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;--font-aai-serif:"Source Serif 4", "Source Serif Pro", Charter, "Iowan Old Style", Georgia, serif;--font-aai-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--radius-aai:4px}}@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,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, 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:where(:not(iframe)){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{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.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:0}.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-2{margin-right:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.block{display:block}.flex{display:flex}.hidden{display:none}.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-9{height:calc(var(--spacing) * 9)}.h-11{height:calc(var(--spacing) * 11)}.h-\[7px\]{height:7px}.h-screen{height:100vh}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-0{min-height:0}.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-11{width:calc(var(--spacing) * 11)}.w-\[7px\]{width:7px}.w-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-75{max-width:calc(var(--spacing) * 75)}.max-w-105{max-width:calc(var(--spacing) * 105)}.max-w-190{max-width:calc(var(--spacing) * 190)}.max-w-\[55\%\]{max-width:55%}.max-w-\[60\%\]{max-width:60%}.max-w-\[82\%\]{max-width:82%}.max-w-\[85\%\]{max-width:85%}.max-w-\[min\(78\%\,64ch\)\]{max-width:min(78%,64ch)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.rotate-90{rotate:90deg}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.\[scrollbar-width\:none\]{scrollbar-width:none}.appearance-none{appearance:none}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-aai{border-radius:var(--radius-aai)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-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-none{--tw-border-style:none;border-style:none}.bg-transparent{background-color:#0000}.p-3{padding:calc(var(--spacing) * 3)}.p-7{padding:calc(var(--spacing) * 7)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-7{padding-inline:calc(var(--spacing) * 7)}.px-10{padding-inline:calc(var(--spacing) * 10)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.text-center{text-align:center}.text-left{text-align:left}.font-aai{font-family:var(--font-aai)}.font-aai-mono{font-family:var(--font-aai-mono)}.font-aai-serif{font-family:var(--font-aai-serif)}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.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-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.text-\[22px\]{font-size:22px}.text-\[32px\]{font-size:32px}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-\[1\.2\]{--tw-leading:1.2;line-height:1.2}.leading-\[1\.15\]{--tw-leading:1.15;line-height:1.15}.leading-\[22px\]{--tw-leading:22px;line-height:22px}.leading-\[23px\]{--tw-leading:23px;line-height:23px}.leading-\[130\%\]{--tw-leading:130%;line-height:130%}.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)}.tracking-\[-0\.2px\]{--tw-tracking:-.2px;letter-spacing:-.2px}.tracking-\[1\.2px\]{--tw-tracking:1.2px;letter-spacing:1.2px}.tracking-\[1\.4px\]{--tw-tracking:1.4px;letter-spacing:1.4px}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-balance{text-wrap:balance}.wrap-break-word{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.uppercase{text-transform:uppercase}.opacity-60{opacity:.6}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.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))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.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}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:px-16{padding-inline:calc(var(--spacing) * 16)}.sm\:py-14{padding-block:calc(var(--spacing) * 14)}}}@keyframes aai-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.82)}}@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-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@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-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@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-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}