@alexkroman1/aai-ui 1.16.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/audio.d.ts +43 -5
  2. package/dist/audio.js +39 -20
  3. package/dist/chat-view-DKFhxMAT.js +129 -0
  4. package/dist/client-config.d.ts +7 -6
  5. package/dist/components/chat-view.d.ts +1 -8
  6. package/dist/components/chat-view.js +2 -2
  7. package/dist/components/console-shell.d.ts +37 -0
  8. package/dist/components/controls.js +1 -1
  9. package/dist/components/url-chips.d.ts +1 -1
  10. package/dist/components/workflow-view.d.ts +13 -0
  11. package/dist/{controls-BbZcmnJf.js → controls-4OJoekj6.js} +1 -1
  12. package/dist/default-client/assets/audio-Cgviqo9t.js +1 -0
  13. package/dist/default-client/assets/capture-processor-DLHxAIfT.js +90 -0
  14. package/dist/default-client/assets/index-BgbIWnfG.css +2 -0
  15. package/dist/default-client/assets/index-DbKKR3UE.js +62 -0
  16. package/dist/default-client/assets/playback-processor-bMTdFp-8.js +271 -0
  17. package/dist/default-client/assets/rolldown-runtime-BpQH8Ho1.js +1 -0
  18. package/dist/default-client/assets/types-Bpg3ZIZK.js +64 -0
  19. package/dist/default-client/index.html +5 -2
  20. package/dist/define-client-yoGybEYR.js +710 -0
  21. package/dist/define-client.d.ts +0 -9
  22. package/dist/define-client.js +1 -1
  23. package/dist/index.d.ts +3 -4
  24. package/dist/index.js +6 -5
  25. package/dist/{session-core-B64kau_v.js → session-core-BLhiQ18c.js} +6 -0
  26. package/dist/session-core.js +1 -1
  27. package/dist/sync-mic.d.ts +11 -43
  28. package/dist/types.d.ts +24 -1
  29. package/dist/types.js +32 -2
  30. package/dist/worklets/capture-processor.d.ts +1 -1
  31. package/dist/worklets/capture-processor.js +32 -49
  32. package/dist/worklets/playback-processor.d.ts +1 -1
  33. package/dist/worklets/playback-processor.js +125 -9
  34. package/package.json +2 -2
  35. package/dist/chat-view-gi6FccZq.js +0 -193
  36. package/dist/components/sync-chat-view.d.ts +0 -18
  37. package/dist/components/text-controls.d.ts +0 -14
  38. package/dist/default-client/assets/audio-DNDZgEZp.js +0 -1
  39. package/dist/default-client/assets/capture-processor-UlKEKyIW.js +0 -108
  40. package/dist/default-client/assets/index-BogmeUln.css +0 -2
  41. package/dist/default-client/assets/index-BunwIXSP.js +0 -124
  42. package/dist/default-client/assets/playback-processor-C5HVRVbu.js +0 -156
  43. package/dist/define-client-DdpijqAu.js +0 -906
  44. package/dist/sync-vad.d.ts +0 -54
@@ -1,193 +0,0 @@
1
- import { useSessionCore, useSessionSelector, useTheme } from "./context.js";
2
- import { Button } from "./components/button.js";
3
- import { r as TEXT_FAINT, t as ERROR_COLOR } from "./_colors-DYX7XRTr.js";
4
- import { t as AaiLogo } from "./aai-logo-B8lDmsut.js";
5
- import { r as SessionUrlChips, t as Controls } from "./controls-BbZcmnJf.js";
6
- import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
7
- import { MessageList } from "./components/message-list.js";
8
- import clsx from "clsx";
9
- import { memo, useRef, useState } from "react";
10
- import { jsx, jsxs } from "react/jsx-runtime";
11
- import { errorMessage } from "@alexkroman1/aai";
12
- //#region components/text-controls.tsx
13
- /** @jsxImportSource react */
14
- /**
15
- * Controls for a text-only session (`tts: none()`): a **record** toggle that
16
- * streams the microphone while active, an **upload** button that transcribes
17
- * an audio file, and **New Conversation**. Replies arrive as text in the
18
- * {@link MessageList} — there is no playback side.
19
- *
20
- * Rendered automatically by {@link ChatView} when the server declares the
21
- * session text-only; custom clients can compose it directly.
22
- *
23
- * @public
24
- */
25
- const TextControls = memo(function TextControls({ className }) {
26
- const recording = useSessionSelector((s) => s.recording);
27
- const state = useSessionSelector((s) => s.state);
28
- const core = useSessionCore();
29
- const fileInput = useRef(null);
30
- const [uploading, setUploading] = useState(false);
31
- const [uploadError, setUploadError] = useState(null);
32
- const connected = state !== "disconnected" && state !== "connecting" && state !== "error";
33
- function toggleRecording() {
34
- if (recording) core.stopRecording();
35
- else core.startRecording();
36
- }
37
- function onFileChosen(file) {
38
- if (!file || uploading) return;
39
- setUploading(true);
40
- setUploadError(null);
41
- core.sendAudioFile(file).catch((err) => {
42
- setUploadError(errorMessage(err));
43
- }).finally(() => {
44
- setUploading(false);
45
- if (fileInput.current) fileInput.current.value = "";
46
- });
47
- }
48
- return /* @__PURE__ */ jsxs("div", {
49
- className: clsx("flex flex-col gap-1.5 shrink-0", className),
50
- children: [/* @__PURE__ */ jsxs("div", {
51
- className: "flex items-center gap-2",
52
- children: [
53
- /* @__PURE__ */ jsxs(Button, {
54
- variant: recording ? "default" : "secondary",
55
- onClick: toggleRecording,
56
- disabled: !connected || uploading,
57
- "aria-pressed": recording,
58
- style: recording ? { background: ERROR_COLOR } : void 0,
59
- "data-testid": "record-button",
60
- children: [/* @__PURE__ */ jsx("span", {
61
- className: clsx("w-2 h-2 rounded-full mr-2", recording && "animate-pulse"),
62
- style: { background: recording ? "#fff" : ERROR_COLOR }
63
- }), recording ? "Stop recording" : "Record"]
64
- }),
65
- /* @__PURE__ */ jsx(Button, {
66
- variant: "ghost",
67
- onClick: () => fileInput.current?.click(),
68
- disabled: !connected || uploading,
69
- "data-testid": "upload-button",
70
- children: uploading ? "Transcribing…" : "Upload audio"
71
- }),
72
- /* @__PURE__ */ jsx("input", {
73
- ref: fileInput,
74
- type: "file",
75
- accept: "audio/*",
76
- className: "hidden",
77
- onChange: (e) => onFileChosen(e.target.files?.[0]),
78
- "data-testid": "upload-input"
79
- }),
80
- /* @__PURE__ */ jsx(Button, {
81
- variant: "ghost",
82
- onClick: core.reset,
83
- children: "New Conversation"
84
- }),
85
- /* @__PURE__ */ jsx(SessionUrlChips, { className: "ml-auto max-w-[55%]" })
86
- ]
87
- }), uploadError && /* @__PURE__ */ jsx("div", {
88
- className: "text-[12px]",
89
- style: { color: "#B3261E" },
90
- "data-testid": "upload-error",
91
- children: uploadError
92
- })]
93
- });
94
- });
95
- //#endregion
96
- //#region components/chat-view.tsx
97
- /** @jsxImportSource react */
98
- const PULSING_STATES = /* @__PURE__ */ new Set(["listening", "speaking"]);
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
- */
105
- function stateColor(state, primary) {
106
- switch (state) {
107
- case "listening":
108
- case "speaking":
109
- case "ready": return primary;
110
- case "thinking": return "#B98900";
111
- case "error": return ERROR_COLOR;
112
- default: return TEXT_FAINT;
113
- }
114
- }
115
- /**
116
- * The main chat interface for a voice agent session — the design-system
117
- * "voice agent console": a 760px column on the cream page with a header
118
- * (logo + live-status eyebrow), the conversation on a raised white card,
119
- * and the session controls beneath it.
120
- *
121
- * Must be rendered inside a {@link SessionProvider}.
122
- *
123
- * @example
124
- * ```tsx
125
- * <StartScreen icon="🍕" title="Pizza Palace">
126
- * <ChatView />
127
- * </StartScreen>
128
- * ```
129
- *
130
- * @param icon - Optional element rendered in place of the logo in the header.
131
- * @param title - Optional title string for the header.
132
- * @param className - Additional CSS class names applied to the root element.
133
- *
134
- * @public
135
- */
136
- function ChatView({ icon, title, className }) {
137
- const state = useSessionSelector((s) => s.state);
138
- const error = useSessionSelector((s) => s.error);
139
- const audioOut = useSessionSelector((s) => s.audioOut);
140
- const theme = useTheme();
141
- const pulsing = PULSING_STATES.has(state);
142
- return /* @__PURE__ */ jsxs("div", {
143
- className: clsx("flex flex-col h-screen w-full max-w-190 mx-auto box-border px-6 py-8 gap-5 font-aai text-sm", className),
144
- style: {
145
- background: theme.bg,
146
- color: theme.text
147
- },
148
- children: [
149
- /* @__PURE__ */ jsxs("div", {
150
- className: "flex items-center justify-between shrink-0",
151
- children: [/* @__PURE__ */ jsxs("div", {
152
- className: "flex items-center gap-3 min-w-0",
153
- children: [icon ?? /* @__PURE__ */ jsx(AaiLogo, { size: 22 }), title && /* @__PURE__ */ jsx("span", {
154
- className: "font-aai-serif text-[22px] leading-[1.2] font-normal truncate",
155
- style: { color: theme.text },
156
- children: title
157
- })]
158
- }), /* @__PURE__ */ jsxs(Eyebrow, {
159
- className: "shrink-0",
160
- "data-state": state,
161
- children: [/* @__PURE__ */ jsx("span", {
162
- className: "w-[7px] h-[7px] rounded-full",
163
- style: {
164
- background: stateColor(state, theme.primary),
165
- animation: pulsing ? "aai-pulse 1.6s ease-in-out infinite" : "none"
166
- }
167
- }), state]
168
- })]
169
- }),
170
- error && /* @__PURE__ */ jsx("div", {
171
- className: "px-3.5 py-2.5 rounded-aai border text-[13px] leading-[130%] shrink-0",
172
- style: {
173
- borderColor: "rgba(179,38,30,0.35)",
174
- background: "rgba(179,38,30,0.06)",
175
- color: "#B3261E"
176
- },
177
- children: error.message
178
- }),
179
- /* @__PURE__ */ jsx("div", {
180
- className: "flex flex-col flex-1 min-h-0 border rounded-lg overflow-hidden",
181
- style: {
182
- background: theme.surface,
183
- borderColor: theme.border,
184
- boxShadow: "0 1px 3px 0 rgb(20 18 12 / 0.06)"
185
- },
186
- children: /* @__PURE__ */ jsx(MessageList, {})
187
- }),
188
- audioOut ? /* @__PURE__ */ jsx(Controls, {}) : /* @__PURE__ */ jsx(TextControls, {})
189
- ]
190
- });
191
- }
192
- //#endregion
193
- export { stateColor as n, TextControls as r, ChatView as t };
@@ -1,18 +0,0 @@
1
- /**
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.
5
- *
6
- * @public
7
- */
8
- export declare function SyncChatView({ syncUrl, title, greeting, }: {
9
- /** The agent server's sync endpoint, e.g. `https://host/slug/sync`. */
10
- syncUrl: string;
11
- /** Agent name shown in the header. */
12
- title?: string | undefined;
13
- /**
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.
16
- */
17
- greeting?: string | undefined;
18
- }): import("react").JSX.Element;
@@ -1,14 +0,0 @@
1
- /**
2
- * Controls for a text-only session (`tts: none()`): a **record** toggle that
3
- * streams the microphone while active, an **upload** button that transcribes
4
- * an audio file, and **New Conversation**. Replies arrive as text in the
5
- * {@link MessageList} — there is no playback side.
6
- *
7
- * Rendered automatically by {@link ChatView} when the server declares the
8
- * session text-only; custom clients can compose it directly.
9
- *
10
- * @public
11
- */
12
- export declare const TextControls: import("react").MemoExoticComponent<({ className, }: {
13
- className?: string | undefined;
14
- }) => import("react").JSX.Element>;
@@ -1 +0,0 @@
1
- import{t as e}from"./index-BunwIXSP.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,108 +0,0 @@
1
- var e=new Blob([`
2
- class CaptureProcessor extends AudioWorkletProcessor {
3
- constructor(options) {
4
- super();
5
- this.recording = false;
6
- const opts = options.processorOptions || {};
7
- this.fromRate = opts.contextRate || sampleRate;
8
- this.toRate = opts.sttSampleRate || sampleRate;
9
- this.ratio = this.fromRate / this.toRate;
10
- this.needsResample = this.fromRate !== this.toRate;
11
- // Streaming-resampler state carried across process() blocks so the output
12
- // clock doesn't drift and there's no discontinuity at 128-sample block
13
- // boundaries. \`prev\` is the previous block's last input sample (used to
14
- // interpolate across the boundary); \`pos\` is the fractional read position,
15
- // in input samples, relative to an extended [prev, ...input] frame. Start
16
- // at 1 so the first output is input[0] (not the bogus initial prev).
17
- this.prev = 0;
18
- this.pos = 1;
19
- // Output buffer reused across process() calls -- the render quantum is a
20
- // fixed size, so allocating per call would churn the realtime audio thread.
21
- this.resampleBuf = null;
22
- // Int16 accumulation buffer: flushed to the main thread as one transferred
23
- // ArrayBuffer once ~bufferSeconds of output samples are batched. Sized 2x
24
- // the flush target so a whole render quantum always fits before flushing.
25
- this.targetSamples = Math.max(1, Math.round(this.toRate * (opts.bufferSeconds || 0.1)));
26
- this.pending = new Int16Array(this.targetSamples * 2);
27
- this.pendingLen = 0;
28
- this.port.onmessage = (e) => {
29
- if (e.data.event === 'start') this.recording = true;
30
- else if (e.data.event === 'stop') {
31
- // Final flush so the tail of speech isn't dropped on close, then ack
32
- // so the host knows the tail chunk (if any) has been posted and it is
33
- // safe to tear the context down.
34
- this.flush();
35
- this.recording = false;
36
- this.port.postMessage({ event: 'stopped' });
37
- }
38
- };
39
- }
40
-
41
- resample(input) {
42
- const ratio = this.ratio;
43
- const n = input.length;
44
- if (n === 0) return new Float32Array(0);
45
- // At most ceil(n / ratio) + 1 outputs per block (pos starts in [0, ratio)).
46
- const maxLen = Math.ceil(n / ratio) + 1;
47
- if (this.resampleBuf === null || this.resampleBuf.length < maxLen) {
48
- this.resampleBuf = new Float32Array(maxLen);
49
- }
50
- const out = this.resampleBuf;
51
- // Extended frame: index 0 = prev (previous block's last sample),
52
- // index k>=1 = input[k-1]. Interpolate at fractional positions stepping by
53
- // ratio, carrying \`pos\` across calls to preserve the sample clock.
54
- let count = 0;
55
- let pos = this.pos;
56
- while (pos < n) {
57
- const idx = pos | 0;
58
- const frac = pos - idx;
59
- const a = idx === 0 ? this.prev : input[idx - 1];
60
- const b = input[idx];
61
- out[count++] = a + frac * (b - a);
62
- pos += ratio;
63
- }
64
- // Shift the origin to this block's last sample for the next call.
65
- this.prev = input[n - 1];
66
- this.pos = pos - n;
67
- return out.subarray(0, count);
68
- }
69
-
70
- // Convert Float32 -> Int16 and append to the pending batch. Writes through
71
- // an Int16Array directly (assignment truncates like DataView.setInt16).
72
- accumulate(samples) {
73
- let buf = this.pending;
74
- if (this.pendingLen + samples.length > buf.length) {
75
- // Defensive: only reachable if a render quantum outproduces the 1x
76
- // headroom above the flush target (never with 128-sample quanta).
77
- const grown = new Int16Array((this.pendingLen + samples.length) * 2);
78
- grown.set(buf.subarray(0, this.pendingLen));
79
- this.pending = grown;
80
- buf = grown;
81
- }
82
- for (let i = 0; i < samples.length; i++) {
83
- const s = Math.max(-1, Math.min(1, samples[i]));
84
- buf[this.pendingLen++] = s < 0 ? s * 0x8000 : s * 0x7fff;
85
- }
86
- }
87
-
88
- // Post the batched samples as one transferred ArrayBuffer and reset.
89
- flush() {
90
- if (this.pendingLen === 0) return;
91
- const buffer = this.pending.buffer.slice(0, this.pendingLen * 2);
92
- this.pendingLen = 0;
93
- this.port.postMessage({ event: 'chunk', buffer }, [buffer]);
94
- }
95
-
96
- process(inputs) {
97
- const input = inputs[0];
98
- if (!input || !input[0] || !this.recording) return true;
99
-
100
- const samples = this.needsResample ? this.resample(input[0]) : input[0];
101
- this.accumulate(samples);
102
- if (this.pendingLen >= this.targetSamples) this.flush();
103
- return true;
104
- }
105
- }
106
-
107
- registerProcessor('capture-processor', CaptureProcessor);
108
- `],{type:`application/javascript`}),t=URL.createObjectURL(e);export{t as default};
@@ -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-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{.invisible{visibility:hidden}.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}.grow{flex-grow:1}.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}}