@alexkroman1/aai-ui 5.8.1 → 5.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/audio.d.ts CHANGED
@@ -44,6 +44,16 @@ export type VoiceIOOptions = {
44
44
  * called for a clean turn, so it can be wired straight to a warning.
45
45
  */
46
46
  onPlaybackStats?: ((stats: PlaybackStats) => void) | undefined;
47
+ /**
48
+ * Called every {@link PLAYBACK_PROGRESS_INTERVAL_MS} while the playback
49
+ * buffer holds unplayed agent audio, with its depth in ms. Wire it to the
50
+ * session's `playback_progress` frame: it is the host's only closed-loop
51
+ * view of playback, and without it the host assumes every chunk it forwards
52
+ * starts playing on arrival at exactly 1.0x — so a client whose buffer runs
53
+ * ahead of the wall clock is told the line is silent while the caller is
54
+ * still listening. Unwired, the host degrades to that estimate.
55
+ */
56
+ onPlaybackProgress?: ((bufferedMs: number) => void) | undefined;
47
57
  /**
48
58
  * Called once if the microphone delivers nothing but digital silence for
49
59
  * the first {@link MIC_SILENCE_PROBE_MS} of capture — a muted or wrong input
package/dist/audio.js CHANGED
@@ -77,7 +77,7 @@ function createCaptureNode(ctx, onChunk, onSilent) {
77
77
  * @throws If microphone access is denied or AudioWorklet registration fails.
78
78
  */
79
79
  async function createVoiceIO(opts) {
80
- const { sttSampleRate, ttsSampleRate, captureWorkletSrc, playbackWorkletSrc, onMicData, onError, onPlaybackStats, onMicSilent } = opts;
80
+ const { sttSampleRate, ttsSampleRate, captureWorkletSrc, playbackWorkletSrc, onMicData, onError, onPlaybackStats, onPlaybackProgress, onMicSilent } = opts;
81
81
  const ctx = new AudioContext({
82
82
  sampleRate: ttsSampleRate,
83
83
  latencyHint: "playback"
@@ -124,25 +124,42 @@ async function createVoiceIO(opts) {
124
124
  capture.start();
125
125
  let playNode = null;
126
126
  let onPlaybackStop = null;
127
+ /**
128
+ * Turn ids for the drain handshake. Every `done()` posts a fresh id and the
129
+ * worklet echoes it on the matching 'stop', so a stop the worklet posted for
130
+ * an EARLIER turn — already in flight when a barge-in flushed that turn —
131
+ * cannot settle the current turn's wait. Dropping only `reason: 'interrupt'`
132
+ * stops was not enough: the stale one is a legitimate drain stop, just for a
133
+ * turn the host has already moved past, and settling on it reports the live
134
+ * reply finished while it is still speaking.
135
+ */
136
+ let turnSeq = 0;
137
+ let pendingStopTurn = null;
127
138
  const lifecycle = new AbortController();
139
+ /** Settle whatever drain wait is pending and forget the turn it belonged to. */
140
+ function settlePendingStop() {
141
+ onPlaybackStop?.();
142
+ onPlaybackStop = null;
143
+ pendingStopTurn = null;
144
+ }
145
+ function onWorkletStop(msg) {
146
+ if (msg.stats && msg.stats.concealedSamples > 0) onPlaybackStats?.(msg.stats);
147
+ if (msg.reason === "interrupt") return;
148
+ if (pendingStopTurn !== null && msg.turn !== pendingStopTurn) return;
149
+ settlePendingStop();
150
+ }
128
151
  function ensurePlayNode() {
129
152
  if (playNode) return playNode;
130
153
  const node = new AudioWorkletNode(ctx, "playback-processor");
131
154
  node.connect(ctx.destination);
132
155
  node.port.onmessage = (e) => {
133
- if (e.data.event === "stop") {
134
- const stats = e.data.stats;
135
- if (stats && stats.concealedSamples > 0) onPlaybackStats?.(stats);
136
- if (e.data.reason === "interrupt") return;
137
- onPlaybackStop?.();
138
- onPlaybackStop = null;
139
- }
156
+ if (e.data.event === "stop") onWorkletStop(e.data);
157
+ else if (e.data.event === "progress") onPlaybackProgress?.(e.data.bufferedMs);
140
158
  };
141
159
  node.onprocessorerror = () => {
142
160
  const err = /* @__PURE__ */ new Error("Audio playback worklet crashed");
143
161
  console.error("[aai-ui]", err.message);
144
- onPlaybackStop?.();
145
- onPlaybackStop = null;
162
+ settlePendingStop();
146
163
  onError?.(err);
147
164
  };
148
165
  playNode = node;
@@ -159,14 +176,24 @@ async function createVoiceIO(opts) {
159
176
  },
160
177
  done() {
161
178
  if (!playNode) return Promise.resolve();
162
- playNode.port.postMessage({ event: "done" });
163
- if (ctx.state !== "running") return Promise.resolve();
179
+ const turn = ++turnSeq;
180
+ playNode.port.postMessage({
181
+ event: "done",
182
+ turn
183
+ });
184
+ if (ctx.state !== "running") {
185
+ pendingStopTurn = null;
186
+ return Promise.resolve();
187
+ }
164
188
  return new Promise((resolve) => {
165
189
  onPlaybackStop?.();
166
190
  const settle = () => {
167
191
  clearInterval(poll);
168
192
  clearTimeout(cap);
169
- if (onPlaybackStop === settle) onPlaybackStop = null;
193
+ if (onPlaybackStop === settle) {
194
+ onPlaybackStop = null;
195
+ pendingStopTurn = null;
196
+ }
170
197
  resolve();
171
198
  };
172
199
  const poll = setInterval(() => {
@@ -174,12 +201,12 @@ async function createVoiceIO(opts) {
174
201
  }, PLAYBACK_DONE_POLL_MS);
175
202
  const cap = setTimeout(settle, PLAYBACK_DONE_MAX_WAIT_MS);
176
203
  onPlaybackStop = settle;
204
+ pendingStopTurn = turn;
177
205
  });
178
206
  },
179
207
  flush() {
180
208
  if (!playNode) return;
181
- onPlaybackStop?.();
182
- onPlaybackStop = null;
209
+ settlePendingStop();
183
210
  playNode.port.postMessage({ event: "interrupt" });
184
211
  },
185
212
  async close() {
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Filesystem location of the prebuilt default client — **Node only**.
3
+ *
4
+ * Its own subpath rather than the root barrel because it imports `node:module`
5
+ * and `node:path`: the root export is browser code, and pulling Node builtins
6
+ * into it would break every bundler that consumes this package.
7
+ *
8
+ * This exists because locating the directory is otherwise three lines of module
9
+ * archaeology plus knowledge of an internal `dist/` layout, and everybody
10
+ * serving the default UI has to write them — `aai-cli`'s dev server had its own
11
+ * copy, as did every self-hosted example. Three places that would all silently
12
+ * serve nothing if the build output moved.
13
+ */
14
+ /**
15
+ * Absolute path to the prebuilt browser client's static files — pass it to
16
+ * `createServer`/`createAgentServer` as `clientDir`.
17
+ *
18
+ * A function, not a constant: resolution touches the module graph and throws
19
+ * when the package is missing, and a module-level constant would move that
20
+ * failure to import time — where it fires for callers that never wanted the
21
+ * client, and before any of their own error handling is in place.
22
+ *
23
+ * Resolved through this package's own `package.json` rather than relative to
24
+ * this module, so it lands in the same place whether the caller resolved the
25
+ * `@dev/source` TypeScript entry or the compiled one under `dist/`.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { agent } from "@alexkroman1/aai";
30
+ * import { createAgentServer } from "@alexkroman1/aai/runtime";
31
+ * import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
32
+ *
33
+ * const server = createAgentServer({
34
+ * agent: agent({ name: "Support" }),
35
+ * env: {},
36
+ * clientDir: defaultClientDir(),
37
+ * });
38
+ * ```
39
+ *
40
+ * @public
41
+ */
42
+ export declare function defaultClientDir(): string;
@@ -0,0 +1,56 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ //#region client-dir.ts
4
+ /**
5
+ * Filesystem location of the prebuilt default client — **Node only**.
6
+ *
7
+ * Its own subpath rather than the root barrel because it imports `node:module`
8
+ * and `node:path`: the root export is browser code, and pulling Node builtins
9
+ * into it would break every bundler that consumes this package.
10
+ *
11
+ * This exists because locating the directory is otherwise three lines of module
12
+ * archaeology plus knowledge of an internal `dist/` layout, and everybody
13
+ * serving the default UI has to write them — `aai-cli`'s dev server had its own
14
+ * copy, as did every self-hosted example. Three places that would all silently
15
+ * serve nothing if the build output moved.
16
+ */
17
+ const require = createRequire(import.meta.url);
18
+ /**
19
+ * Absolute path to the prebuilt browser client's static files — pass it to
20
+ * `createServer`/`createAgentServer` as `clientDir`.
21
+ *
22
+ * A function, not a constant: resolution touches the module graph and throws
23
+ * when the package is missing, and a module-level constant would move that
24
+ * failure to import time — where it fires for callers that never wanted the
25
+ * client, and before any of their own error handling is in place.
26
+ *
27
+ * Resolved through this package's own `package.json` rather than relative to
28
+ * this module, so it lands in the same place whether the caller resolved the
29
+ * `@dev/source` TypeScript entry or the compiled one under `dist/`.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { agent } from "@alexkroman1/aai";
34
+ * import { createAgentServer } from "@alexkroman1/aai/runtime";
35
+ * import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
36
+ *
37
+ * const server = createAgentServer({
38
+ * agent: agent({ name: "Support" }),
39
+ * env: {},
40
+ * clientDir: defaultClientDir(),
41
+ * });
42
+ * ```
43
+ *
44
+ * @public
45
+ */
46
+ function defaultClientDir() {
47
+ let pkgPath;
48
+ try {
49
+ pkgPath = require.resolve("@alexkroman1/aai-ui/package.json");
50
+ } catch (err) {
51
+ throw new Error("Could not locate the default client UI — is @alexkroman1/aai-ui installed? Try reinstalling dependencies (pnpm install).", { cause: err });
52
+ }
53
+ return path.join(path.dirname(pkgPath), "dist", "default-client");
54
+ }
55
+ //#endregion
56
+ export { defaultClientDir };
@@ -0,0 +1 @@
1
+ import{a as e,o as t,t as n}from"./index-D_RNiTIh.js";function r(e,t,n){if(e!==t)throw Error(`Browser refused the ${n} sample rate: asked for ${t} Hz, got ${e} Hz`)}function i(e){e.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{})}function a(e,t,n){let r=new AudioWorkletNode(e,`capture-processor`,{channelCount:1,channelCountMode:`explicit`}),i=null;return r.port.onmessage=e=>{let r=e.data;r.event===`chunk`&&r.buffer?t(r.buffer):r.event===`silent`?n?.():r.event===`stopped`&&(i?.(),i=null)},{node:r,start(){r.port.postMessage({event:`start`})},stop(){return new Promise(e=>{let t=setTimeout(e,250);i=()=>{clearTimeout(t),e()},r.port.postMessage({event:`stop`})})}}}async function o(o){let{sttSampleRate:s,ttsSampleRate:c,captureWorkletSrc:l,playbackWorkletSrc:u,onMicData:d,onError:f,onPlaybackStats:p,onPlaybackProgress:m,onMicSilent:h}=o,g=new AudioContext({sampleRate:c,latencyHint:`playback`}),_=s===c,v=_?g:new AudioContext({sampleRate:s,latencyHint:`interactive`});async function y(){let e=_?[g]:[g,v];await Promise.all(e.map(e=>e.close().catch(e=>{console.warn(`AudioContext close failed:`,e)})))}let b=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...n}}),x;try{[x]=await Promise.all([b,g.resume(),v.resume(),v.audioWorklet.addModule(l),g.audioWorklet.addModule(u)]),r(v.sampleRate,s,`capture`),r(g.sampleRate,c,`playback`)}catch(e){throw i(b),await y(),e}let S=v.createMediaStreamSource(x),C=a(v,d,h);S.connect(C.node),C.node.onprocessorerror=()=>{let e=Error(`Audio capture worklet crashed`);console.error(`[aai-ui]`,e.message),f?.(e)},C.start();let w=null,T=null,E=0,D=null,O=new AbortController;function k(){T?.(),T=null,D=null}function A(e){e.stats&&e.stats.concealedSamples>0&&p?.(e.stats),e.reason!==`interrupt`&&(D!==null&&e.turn!==D||k())}function j(){if(w)return w;let e=new AudioWorkletNode(g,`playback-processor`);return e.connect(g.destination),e.port.onmessage=e=>{e.data.event===`stop`?A(e.data):e.data.event===`progress`&&m?.(e.data.bufferedMs)},e.onprocessorerror=()=>{let e=Error(`Audio playback worklet crashed`);console.error(`[aai-ui]`,e.message),k(),f?.(e)},w=e,e}let M={enqueue(e){O.signal.aborted||e.byteLength!==0&&j().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){if(!w)return Promise.resolve();let n=++E;return w.port.postMessage({event:`done`,turn:n}),g.state===`running`?new Promise(r=>{T?.();let i=()=>{clearInterval(a),clearTimeout(o),T===i&&(T=null,D=null),r()},a=setInterval(()=>{g.state!==`running`&&i()},t),o=setTimeout(i,e);T=i,D=n}):(D=null,Promise.resolve())},flush(){w&&(k(),w.port.postMessage({event:`interrupt`}))},async close(){if(!O.signal.aborted){O.abort(),await C.stop(),S.disconnect(),C.node.disconnect(),w&&w.disconnect();for(let e of x.getTracks())e.stop();await y()}},async[Symbol.asyncDispose](){await M.close()}};return M}export{o as createVoiceIO};
@@ -1,4 +1,4 @@
1
- import{n as e,r as t}from"./index-fLjOiK97.js";import{t as n}from"./_module-url-BX0RuRU2.js";var r=n(`
1
+ import{n as e,r as t}from"./index-D_RNiTIh.js";import{t as n}from"./_module-url-BX0RuRU2.js";var r=n(`
2
2
  class CaptureProcessor extends AudioWorkletProcessor {
3
3
  constructor(options) {
4
4
  super();
@@ -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;--font-weight-semibold:600;--tracking-wide:.025em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--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{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.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}.my-0\.5{margin-block:calc(var(--spacing) * .5)}.my-1\.5{margin-block:calc(var(--spacing) * 1.5)}.my-2\.5{margin-block:calc(var(--spacing) * 2.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-1\.5{height:calc(var(--spacing) * 1.5)}.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-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-\[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{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.border-collapse{border-collapse:collapse}.rotate-90{rotate:90deg}.cursor-pointer{cursor:pointer}.resize{resize:both}.\[scrollbar-width\:none\]{scrollbar-width:none}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.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-x-auto{overflow-x:auto}.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-sm{border-radius:var(--radius-sm)}.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-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-none{--tw-border-style:none;border-style:none}.bg-transparent{background-color:#0000}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-7{padding:calc(var(--spacing) * 7)}.px-1{padding-inline:var(--spacing)}.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-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-0\.5{padding-block:calc(var(--spacing) * .5)}.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)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-5{padding-left:calc(var(--spacing) * 5)}.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-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[16px\]{font-size:16px}.text-\[17px\]{font-size:17px}.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)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.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}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.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}.first\:mt-0:first-child{margin-top:0}.last\:mb-0:last-child{margin-bottom:0}.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}