@alexkroman1/aai-ui 1.8.3 → 1.9.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 (62) hide show
  1. package/dist/_colors-DJUordGv.js +19 -0
  2. package/dist/_utils-5cs73OrA.js +16 -0
  3. package/dist/_utils.d.ts +4 -0
  4. package/dist/aai-logo-B8lDmsut.js +84 -0
  5. package/dist/audio.d.ts +9 -2
  6. package/dist/audio.js +41 -24
  7. package/dist/chat-view-C1XbqDk0.js +186 -0
  8. package/dist/components/_colors.d.ts +26 -0
  9. package/dist/components/aai-logo.d.ts +7 -6
  10. package/dist/components/button.d.ts +11 -13
  11. package/dist/components/button.js +7 -16
  12. package/dist/components/chat-view.d.ts +8 -19
  13. package/dist/components/chat-view.js +2 -98
  14. package/dist/components/controls.d.ts +1 -1
  15. package/dist/components/controls.js +2 -39
  16. package/dist/components/eyebrow.d.ts +12 -0
  17. package/dist/components/message-list.d.ts +1 -1
  18. package/dist/components/message-list.js +126 -56
  19. package/dist/components/sidebar-layout.d.ts +1 -1
  20. package/dist/components/start-screen.d.ts +4 -3
  21. package/dist/components/start-screen.js +20 -14
  22. package/dist/components/text-controls.d.ts +14 -0
  23. package/dist/components/tool-call-block.d.ts +5 -3
  24. package/dist/components/tool-call-block.js +1 -1
  25. package/dist/components/url-chips.d.ts +28 -0
  26. package/dist/context.d.ts +30 -4
  27. package/dist/context.js +86 -10
  28. package/dist/controls-BngrPbOC.js +135 -0
  29. package/dist/default-client/assets/audio-Cs-6t_Wd.js +1 -0
  30. package/dist/default-client/assets/capture-processor-C19oBn4L.js +105 -0
  31. package/dist/default-client/assets/index-Bf4ZTNcx.js +73 -0
  32. package/dist/default-client/assets/index-Bzlh9i7w.css +2 -0
  33. package/dist/default-client/assets/playback-processor-BtlzAH78.js +149 -0
  34. package/dist/default-client/index.html +3 -3
  35. package/dist/define-client.d.ts +2 -2
  36. package/dist/define-client.js +18 -24
  37. package/dist/eyebrow-C6ZFuiz6.js +27 -0
  38. package/dist/hooks.js +71 -42
  39. package/dist/index.d.ts +3 -1
  40. package/dist/index.js +6 -5
  41. package/dist/session-core-D3NDaySY.js +652 -0
  42. package/dist/session-core-messages.d.ts +50 -0
  43. package/dist/session-core-types.d.ts +147 -0
  44. package/dist/session-core-upload.d.ts +16 -0
  45. package/dist/session-core.d.ts +2 -68
  46. package/dist/session-core.js +1 -466
  47. package/dist/{tool-call-block-wby_jyoY.js → tool-call-block-7f1GTG-P.js} +33 -31
  48. package/dist/types.d.ts +29 -4
  49. package/dist/types.js +10 -1
  50. package/dist/worklets/capture-processor.d.ts +2 -0
  51. package/dist/worklets/capture-processor.js +80 -25
  52. package/dist/worklets/playback-processor.d.ts +2 -0
  53. package/dist/worklets/playback-processor.js +80 -29
  54. package/package.json +14 -14
  55. package/styles.css +30 -0
  56. package/dist/_react-test-utils.d.ts +0 -86
  57. package/dist/aai-logo-BqFv6JU6.js +0 -21
  58. package/dist/default-client/assets/audio-UowwhmYo.js +0 -1
  59. package/dist/default-client/assets/capture-processor-C26lSiVr.js +0 -53
  60. package/dist/default-client/assets/index-CSyVJsy5.css +0 -2
  61. package/dist/default-client/assets/index-D1MWTIM_.js +0 -74
  62. package/dist/default-client/assets/playback-processor-dSA8Im99.js +0 -101
@@ -0,0 +1,19 @@
1
+ //#region components/_colors.ts
2
+ /** Muted text — subtitles, secondary labels, thinking dots (fg-muted). */
3
+ const TEXT_MUTED = "#57534B";
4
+ /** Faint text — live transcripts, state indicator, start-screen subtitle (warm-500). */
5
+ const TEXT_FAINT = "#6F6A60";
6
+ /** Subtle surface tint — message bubbles, tool-call blocks. */
7
+ const SURFACE_TINT = "rgba(20,18,12,0.03)";
8
+ /** Error red tuned for warm light surfaces. */
9
+ const ERROR_COLOR = "#B3261E";
10
+ /**
11
+ * Derive the user-bubble fill/edge from the theme primary (the mock's
12
+ * indigo-50 background with an indigo-100 border), so custom themes keep a
13
+ * coherent tinted bubble instead of a hardcoded indigo.
14
+ */
15
+ function primaryTint(primary, surface, pct) {
16
+ return `color-mix(in srgb, ${primary} ${pct}%, ${surface})`;
17
+ }
18
+ //#endregion
19
+ export { primaryTint as a, TEXT_MUTED as i, SURFACE_TINT as n, TEXT_FAINT as r, ERROR_COLOR as t };
@@ -0,0 +1,16 @@
1
+ //#region _utils.ts
2
+ /** Parse a JSON string, returning the input unchanged when it isn't valid JSON. */
3
+ function tryParseJSON(str) {
4
+ if (!str) return str;
5
+ try {
6
+ return JSON.parse(str);
7
+ } catch {
8
+ return str;
9
+ }
10
+ }
11
+ /** Truncate a string to `max` characters, appending an ellipsis when cut. */
12
+ function truncate(s, max = 80) {
13
+ return s.length > max ? `${s.slice(0, max)}...` : s;
14
+ }
15
+ //#endregion
16
+ export { tryParseJSON as n, truncate as t };
@@ -0,0 +1,4 @@
1
+ /** Parse a JSON string, returning the input unchanged when it isn't valid JSON. */
2
+ export declare function tryParseJSON(str: string | undefined): unknown;
3
+ /** Truncate a string to `max` characters, appending an ellipsis when cut. */
4
+ export declare function truncate(s: string, max?: number): string;
@@ -0,0 +1,84 @@
1
+ import { useTheme } from "./context.js";
2
+ import { memo } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region components/aai-logo.tsx
5
+ /** @jsxImportSource react */
6
+ /**
7
+ * The AssemblyAI wordmark (design-system "website refresh" asset).
8
+ *
9
+ * The mark keeps its brand blues; the letterforms inherit the theme's text
10
+ * color so the logo works on the cream default and custom themes alike.
11
+ *
12
+ * @param size - Rendered height in px. Defaults to 20.
13
+ * @internal
14
+ */
15
+ const AaiLogo = memo(function AaiLogo({ size = 20 }) {
16
+ return /* @__PURE__ */ jsxs("svg", {
17
+ role: "img",
18
+ "aria-label": "AssemblyAI",
19
+ height: size,
20
+ viewBox: "0 0 141 24",
21
+ fill: "none",
22
+ xmlns: "http://www.w3.org/2000/svg",
23
+ style: {
24
+ display: "block",
25
+ color: useTheme().text
26
+ },
27
+ children: [
28
+ /* @__PURE__ */ jsx("path", {
29
+ d: "M37.698 16.7132L38.9234 13.4518H44.334L45.5782 16.7132H47.897L42.9766 3.51665H40.3939L35.4546 16.7132H37.698ZM41.6193 5.85433L43.7307 11.6042H39.5267L41.6193 5.85433Z",
30
+ fill: "currentColor"
31
+ }),
32
+ /* @__PURE__ */ jsx("path", {
33
+ d: "M48.9176 13.5272C48.9741 15.6198 50.7462 16.8263 53.329 16.8263C55.6667 16.8263 57.4199 15.6575 57.4199 13.81C57.4199 11.6608 55.6289 11.2083 53.4232 10.9444C52.0282 10.7936 51.1044 10.6805 51.1044 9.77558C51.1044 9.00264 51.8962 8.51249 53.1028 8.51249C54.3093 8.51249 55.1388 9.13461 55.2519 10.0207H57.2502C57.1183 8.04118 55.4404 6.94775 53.065 6.94775C50.7462 6.9289 49.1061 8.11659 49.1061 9.94525C49.1061 11.9436 50.8405 12.4149 53.0462 12.6788C54.5355 12.8673 55.4216 12.9428 55.4216 13.9796C55.4216 14.7526 54.5921 15.2427 53.329 15.2427C51.8585 15.2427 50.9913 14.5263 50.9159 13.5272H48.9176Z",
34
+ fill: "currentColor"
35
+ }),
36
+ /* @__PURE__ */ jsx("path", {
37
+ d: "M58.9645 13.5272C59.021 15.6198 60.7931 16.8263 63.3759 16.8263C65.7135 16.8263 67.4668 15.6575 67.4668 13.81C67.4668 11.6608 65.6758 11.2083 63.4701 10.9444C62.0751 10.7936 61.1513 10.6805 61.1513 9.77558C61.1513 9.00264 61.9431 8.51249 63.1497 8.51249C64.3562 8.51249 65.1857 9.13461 65.2988 10.0207H67.2971C67.1652 8.04118 65.4873 6.94775 63.1119 6.94775C60.7931 6.9289 59.153 8.11659 59.153 9.94525C59.153 11.9436 60.8874 12.4149 63.0931 12.6788C64.5824 12.8673 65.4685 12.9428 65.4685 13.9796C65.4685 14.7526 64.639 15.2427 63.3759 15.2427C61.9054 15.2427 61.0382 14.5263 60.9628 13.5272H58.9645Z",
38
+ fill: "currentColor"
39
+ }),
40
+ /* @__PURE__ */ jsx("path", {
41
+ d: "M74.1913 16.8263C76.5855 16.8263 78.2068 15.3747 78.5462 13.4706H76.5101C76.2273 14.5452 75.3224 15.1673 74.0782 15.1673C72.4757 15.1673 71.4389 14.0927 71.3823 12.4526V12.3206H78.6593C78.697 12.0567 78.7158 11.7928 78.7158 11.5477C78.6593 8.71986 76.7929 6.94775 74.0028 6.94775C71.1561 6.94775 69.252 8.92723 69.252 11.9059C69.252 14.8657 71.1561 16.8263 74.1913 16.8263ZM71.4577 10.8313C71.6085 9.4551 72.7208 8.5879 74.0216 8.5879C75.4355 8.5879 76.4347 9.39854 76.6044 10.8313H71.4577Z",
42
+ fill: "currentColor"
43
+ }),
44
+ /* @__PURE__ */ jsx("path", {
45
+ d: "M92.0314 6.94775C90.4478 6.94775 89.3544 7.64529 88.7322 8.70101C88.1478 7.45676 87.0167 6.94775 85.7536 6.94775C84.302 6.96661 83.4725 7.60758 82.9258 8.32396L82.6995 7.06087H80.9274V16.7132H82.9635V11.5477C82.9635 9.83214 83.8307 8.70101 85.2257 8.70101C86.5642 8.70101 87.2618 9.58706 87.2618 11.2838V16.7132H89.3167V11.4911C89.3167 9.75673 90.2027 8.70101 91.5978 8.70101C92.9174 8.70101 93.615 9.58706 93.615 11.2838V16.7132H95.651V11.1518C95.651 8.09774 94.0297 6.94775 92.0314 6.94775Z",
46
+ fill: "currentColor"
47
+ }),
48
+ /* @__PURE__ */ jsx("path", {
49
+ d: "M103.228 6.94775C101.738 6.94775 100.739 7.62643 100.098 8.49363V3.51665H98.0623V16.7132H99.8344L100.079 15.3181C100.683 16.1853 101.72 16.8263 103.228 16.8263C105.867 16.8263 107.79 14.8468 107.79 11.887C107.79 8.77642 105.867 6.94775 103.228 6.94775ZM102.888 15.1108C101.192 15.1108 100.079 13.7722 100.079 11.8682C100.079 10.0018 101.192 8.68216 102.888 8.68216C104.585 8.68216 105.716 10.0018 105.716 11.887C105.716 13.7911 104.585 15.1108 102.888 15.1108Z",
50
+ fill: "currentColor"
51
+ }),
52
+ /* @__PURE__ */ jsx("path", {
53
+ d: "M109.987 16.7132H112.023V3.51665H109.987V16.7132Z",
54
+ fill: "currentColor"
55
+ }),
56
+ /* @__PURE__ */ jsx("path", {
57
+ d: "M117.375 16.7697L116.998 17.7124C116.696 18.5041 116.507 18.6927 115.716 18.6927H114.283V20.4836H116.47C117.884 20.4836 118.298 19.7861 118.902 18.2214L123.275 7.06087H121.107L118.487 14.3944L115.791 7.06087H113.623L117.375 16.7697Z",
58
+ fill: "currentColor"
59
+ }),
60
+ /* @__PURE__ */ jsx("path", {
61
+ d: "M125.769 16.7132L126.994 13.4518H132.405L133.649 16.7132H135.968L131.047 3.51665H128.465L123.525 16.7132H125.769ZM129.69 5.85433L131.801 11.6042H127.597L129.69 5.85433Z",
62
+ fill: "currentColor"
63
+ }),
64
+ /* @__PURE__ */ jsx("path", {
65
+ d: "M137.887 16.7132H140.055V3.51665H137.887V16.7132Z",
66
+ fill: "currentColor"
67
+ }),
68
+ /* @__PURE__ */ jsx("path", {
69
+ fillRule: "evenodd",
70
+ clipRule: "evenodd",
71
+ d: "M12.2496 0C10.4867 0 8.90488 1.08157 8.26713 2.72302L0 24H6.28017L12.7874 7.25245H12.7902C12.9534 6.84425 13.353 6.55584 13.8199 6.55584C14.2869 6.55584 14.6864 6.84425 14.8497 7.25245H15.7167V3.85532H14.1073L15.6053 0H12.2496Z",
72
+ fill: "#2545D3"
73
+ }),
74
+ /* @__PURE__ */ jsx("path", {
75
+ fillRule: "evenodd",
76
+ clipRule: "evenodd",
77
+ d: "M8.2677 2.72302C8.87959 1.1483 10.3603 0.0888597 12.0361 0.00532694L12.034 0H12.2501H14.6661H15.4972C17.2601 0 18.8419 1.08157 19.4797 2.72302L27.7468 24H21.3592L13.3424 3.36747C12.8835 2.35631 11.864 1.65296 10.6801 1.65296C9.49331 1.65296 8.47176 2.35976 8.0144 3.37493L8.2677 2.72302Z",
78
+ fill: "#566DE8"
79
+ })
80
+ ]
81
+ });
82
+ });
83
+ //#endregion
84
+ export { AaiLogo as t };
package/dist/audio.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Decode an audio file (any container/codec the browser can decode) and
3
+ * resample it to mono PCM16 at `targetRate` — the format the server's STT
4
+ * side expects on the wire. Returns the raw clip; any endpointing padding
5
+ * is the caller's concern (the one-shot upload path needs none).
6
+ *
7
+ * @throws If the browser cannot decode the payload.
8
+ */
9
+ export declare function decodeAudioToPcm16(data: ArrayBuffer, targetRate: number): Promise<Int16Array>;
1
10
  /** Configuration for creating a {@link VoiceIO} instance. */
2
11
  export type VoiceIOOptions = {
3
12
  /** Sample rate in Hz expected by the STT engine (e.g. 16000). */
@@ -10,8 +19,6 @@ export type VoiceIOOptions = {
10
19
  playbackWorkletSrc: string;
11
20
  /** Callback invoked with buffered PCM16 microphone data to send to the server. */
12
21
  onMicData: (pcm16: ArrayBuffer) => void;
13
- /** Callback invoked with the current playback position in samples. */
14
- onPlaybackProgress?: (samplesPlayed: number) => void;
15
22
  };
16
23
  /**
17
24
  * Audio I/O interface for voice capture and playback.
package/dist/audio.js CHANGED
@@ -1,6 +1,30 @@
1
1
  import { MIC_BUFFER_SECONDS } from "./types.js";
2
2
  //#region audio.ts
3
3
  /**
4
+ * Decode an audio file (any container/codec the browser can decode) and
5
+ * resample it to mono PCM16 at `targetRate` — the format the server's STT
6
+ * side expects on the wire. Returns the raw clip; any endpointing padding
7
+ * is the caller's concern (the one-shot upload path needs none).
8
+ *
9
+ * @throws If the browser cannot decode the payload.
10
+ */
11
+ async function decodeAudioToPcm16(data, targetRate) {
12
+ const decoded = await new OfflineAudioContext(1, 1, targetRate).decodeAudioData(data);
13
+ const frames = Math.ceil(decoded.duration * targetRate);
14
+ const offline = new OfflineAudioContext(1, frames, targetRate);
15
+ const source = offline.createBufferSource();
16
+ source.buffer = decoded;
17
+ source.connect(offline.destination);
18
+ source.start();
19
+ const f32 = (await offline.startRendering()).getChannelData(0);
20
+ const pcm = new Int16Array(f32.length);
21
+ for (let i = 0; i < f32.length; i++) {
22
+ const s = Math.max(-1, Math.min(1, f32[i] ?? 0));
23
+ pcm[i] = s < 0 ? s * 32768 : s * 32767;
24
+ }
25
+ return pcm;
26
+ }
27
+ /**
4
28
  * Create a {@link VoiceIO} instance that captures microphone audio and
5
29
  * plays back TTS audio using the Web Audio API.
6
30
  *
@@ -18,18 +42,25 @@ async function createVoiceIO(opts) {
18
42
  sampleRate: contextRate,
19
43
  latencyHint: "playback"
20
44
  });
21
- await ctx.resume();
22
- const stream = await navigator.mediaDevices.getUserMedia({ audio: {
45
+ const streamPromise = navigator.mediaDevices.getUserMedia({ audio: {
23
46
  deviceId: { ideal: "default" },
24
47
  echoCancellation: true,
25
48
  noiseSuppression: true,
26
49
  autoGainControl: true,
27
50
  voiceIsolation: true
28
51
  } });
52
+ let stream;
29
53
  try {
30
- await Promise.all([ctx.audioWorklet.addModule(captureWorkletSrc), ctx.audioWorklet.addModule(playbackWorkletSrc)]);
54
+ [stream] = await Promise.all([
55
+ streamPromise,
56
+ ctx.resume(),
57
+ ctx.audioWorklet.addModule(captureWorkletSrc),
58
+ ctx.audioWorklet.addModule(playbackWorkletSrc)
59
+ ]);
31
60
  } catch (err) {
32
- for (const t of stream.getTracks()) t.stop();
61
+ streamPromise.then((s) => {
62
+ for (const t of s.getTracks()) t.stop();
63
+ }).catch(() => {});
33
64
  await ctx.close().catch((err) => {
34
65
  console.warn("AudioContext close failed:", err);
35
66
  });
@@ -41,43 +72,28 @@ async function createVoiceIO(opts) {
41
72
  channelCountMode: "explicit",
42
73
  processorOptions: {
43
74
  contextRate,
44
- sttSampleRate
75
+ sttSampleRate,
76
+ bufferSeconds: MIC_BUFFER_SECONDS
45
77
  }
46
78
  });
47
79
  mic.connect(capNode);
48
- const chunkSizeBytes = Math.floor(sttSampleRate * MIC_BUFFER_SECONDS) * 2;
49
- const bufSize = chunkSizeBytes * 2;
50
- const capBufA = new Uint8Array(bufSize);
51
- const capBufB = new Uint8Array(bufSize);
52
- let capBuf = capBufA;
53
- let capOffset = 0;
54
80
  capNode.port.postMessage({ event: "start" });
55
81
  capNode.port.onmessage = (e) => {
56
82
  if (e.data.event !== "chunk") return;
57
- const chunk = new Uint8Array(e.data.buffer);
58
- capBuf.set(chunk, capOffset);
59
- capOffset += chunk.byteLength;
60
- if (capOffset >= chunkSizeBytes) {
61
- onMicData(capBuf.buffer.slice(0, capOffset));
62
- capBuf = capBuf === capBufA ? capBufB : capBufA;
63
- capOffset = 0;
64
- }
83
+ onMicData(e.data.buffer);
65
84
  };
66
85
  let playNode = null;
67
86
  let onPlaybackStop = null;
68
87
  const lifecycle = new AbortController();
69
- const { onPlaybackProgress } = opts;
70
88
  function ensurePlayNode() {
71
89
  if (playNode) return playNode;
72
90
  const node = new AudioWorkletNode(ctx, "playback-processor", { processorOptions: { sampleRate: contextRate } });
73
91
  node.connect(ctx.destination);
74
92
  node.port.onmessage = (e) => {
75
93
  if (e.data.event === "stop") {
76
- node.disconnect();
77
- if (playNode === node) playNode = null;
78
94
  onPlaybackStop?.();
79
95
  onPlaybackStop = null;
80
- } else if (e.data.event === "progress") onPlaybackProgress?.(e.data.readPos);
96
+ }
81
97
  };
82
98
  playNode = node;
83
99
  return node;
@@ -93,6 +109,7 @@ async function createVoiceIO(opts) {
93
109
  },
94
110
  done() {
95
111
  if (!playNode) return Promise.resolve();
112
+ if (ctx.state !== "running") return Promise.resolve();
96
113
  return new Promise((resolve) => {
97
114
  onPlaybackStop = resolve;
98
115
  playNode?.port.postMessage({ event: "done" });
@@ -118,4 +135,4 @@ async function createVoiceIO(opts) {
118
135
  return io;
119
136
  }
120
137
  //#endregion
121
- export { createVoiceIO };
138
+ export { createVoiceIO, decodeAudioToPcm16 };
@@ -0,0 +1,186 @@
1
+ import { useSession, 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-DJUordGv.js";
4
+ import { t as AaiLogo } from "./aai-logo-B8lDmsut.js";
5
+ import { r as SessionUrlChips, t as Controls } from "./controls-BngrPbOC.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 { 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
+ 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
+ /** Indicator dot color per state, on the light refresh palette. */
100
+ function stateColor(state, primary) {
101
+ switch (state) {
102
+ case "listening":
103
+ case "speaking":
104
+ case "ready": return primary;
105
+ case "thinking": return "#B98900";
106
+ case "error": return ERROR_COLOR;
107
+ default: return TEXT_FAINT;
108
+ }
109
+ }
110
+ /**
111
+ * The main chat interface for a voice agent session — the design-system
112
+ * "voice agent console": a 760px column on the cream page with a header
113
+ * (logo + live-status eyebrow), the conversation on a raised white card,
114
+ * and the session controls beneath it.
115
+ *
116
+ * Must be rendered inside a {@link SessionProvider}.
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * <StartScreen icon="🍕" title="Pizza Palace">
121
+ * <ChatView />
122
+ * </StartScreen>
123
+ * ```
124
+ *
125
+ * @param icon - Optional element rendered in place of the logo in the header.
126
+ * @param title - Optional title string for the header.
127
+ * @param className - Additional CSS class names applied to the root element.
128
+ *
129
+ * @public
130
+ */
131
+ function ChatView({ icon, title, className }) {
132
+ const session = useSession();
133
+ const theme = useTheme();
134
+ const pulsing = PULSING_STATES.has(session.state);
135
+ return /* @__PURE__ */ jsxs("div", {
136
+ 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),
137
+ style: {
138
+ background: theme.bg,
139
+ color: theme.text
140
+ },
141
+ children: [
142
+ /* @__PURE__ */ jsxs("div", {
143
+ className: "flex items-center justify-between shrink-0",
144
+ children: [/* @__PURE__ */ jsxs("div", {
145
+ className: "flex items-center gap-3 min-w-0",
146
+ children: [icon ?? /* @__PURE__ */ jsx(AaiLogo, { size: 22 }), title && /* @__PURE__ */ jsx("span", {
147
+ className: "font-aai-serif text-[22px] leading-[1.2] font-normal truncate",
148
+ style: { color: theme.text },
149
+ children: title
150
+ })]
151
+ }), /* @__PURE__ */ jsxs(Eyebrow, {
152
+ className: "shrink-0",
153
+ "data-state": session.state,
154
+ children: [/* @__PURE__ */ jsx("span", {
155
+ className: "w-[7px] h-[7px] rounded-full",
156
+ style: {
157
+ background: stateColor(session.state, theme.primary),
158
+ animation: pulsing ? "aai-pulse 1.6s ease-in-out infinite" : "none"
159
+ }
160
+ }), session.state]
161
+ })]
162
+ }),
163
+ session.error && /* @__PURE__ */ jsx("div", {
164
+ className: "px-3.5 py-2.5 rounded-aai border text-[13px] leading-[130%] shrink-0",
165
+ style: {
166
+ borderColor: "rgba(179,38,30,0.35)",
167
+ background: "rgba(179,38,30,0.06)",
168
+ color: "#B3261E"
169
+ },
170
+ children: session.error.message
171
+ }),
172
+ /* @__PURE__ */ jsx("div", {
173
+ className: "flex flex-col flex-1 min-h-0 border rounded-lg overflow-hidden",
174
+ style: {
175
+ background: theme.surface,
176
+ borderColor: theme.border,
177
+ boxShadow: "0 1px 3px 0 rgb(20 18 12 / 0.06)"
178
+ },
179
+ children: /* @__PURE__ */ jsx(MessageList, {})
180
+ }),
181
+ session.audioOut ? /* @__PURE__ */ jsx(Controls, {}) : /* @__PURE__ */ jsx(TextControls, {})
182
+ ]
183
+ });
184
+ }
185
+ //#endregion
186
+ export { TextControls as n, ChatView as t };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared tints used by the default components (AssemblyAI design system,
3
+ * "website refresh": warm neutrals over the cream/white surfaces).
4
+ *
5
+ * These sit on top of the {@link ClientTheme} colors (which own the opaque
6
+ * palette) and are intentionally not themeable: they are the warm-gray text
7
+ * steps and ink alpha layers the refresh uses over any light surface.
8
+ */
9
+ /** Soft text — button labels on muted surfaces (warm-700). */
10
+ export declare const TEXT_SOFT = "#3D3A35";
11
+ /** Muted text — subtitles, secondary labels, thinking dots (fg-muted). */
12
+ export declare const TEXT_MUTED = "#57534B";
13
+ /** Faint text — live transcripts, state indicator, start-screen subtitle (warm-500). */
14
+ export declare const TEXT_FAINT = "#6F6A60";
15
+ /** Raised surface tint — secondary button background. */
16
+ export declare const SURFACE_RAISED = "rgba(20,18,12,0.05)";
17
+ /** Subtle surface tint — message bubbles, tool-call blocks. */
18
+ export declare const SURFACE_TINT = "rgba(20,18,12,0.03)";
19
+ /** Error red tuned for warm light surfaces. */
20
+ export declare const ERROR_COLOR = "#B3261E";
21
+ /**
22
+ * Derive the user-bubble fill/edge from the theme primary (the mock's
23
+ * indigo-50 background with an indigo-100 border), so custom themes keep a
24
+ * coherent tinted bubble instead of a hardcoded indigo.
25
+ */
26
+ export declare function primaryTint(primary: string, surface: string, pct: number): string;
@@ -1,11 +1,12 @@
1
- /** @jsxImportSource react */
2
- import type { ReactNode } from "react";
3
1
  /**
4
- * The AAI ANSI art logo, rendered as a styled `<pre>` block.
2
+ * The AssemblyAI wordmark (design-system "website refresh" asset).
5
3
  *
6
- * @param size - Font size in px. Defaults to 10.
4
+ * The mark keeps its brand blues; the letterforms inherit the theme's text
5
+ * color so the logo works on the cream default and custom themes alike.
6
+ *
7
+ * @param size - Rendered height in px. Defaults to 20.
7
8
  * @internal
8
9
  */
9
- export declare function AaiLogo({ size }: {
10
+ export declare const AaiLogo: import("react").MemoExoticComponent<({ size }: {
10
11
  size?: number;
11
- }): ReactNode;
12
+ }) => import("react").JSX.Element>;
@@ -1,23 +1,20 @@
1
1
  import type { ReactNode } from "react";
2
2
  /**
3
- * Visual style of a {@link Button}.
3
+ * Visual style of a {@link Button} (design-system "website refresh":
4
+ * rectangular, ALL-CAPS, tracked labels).
4
5
  *
5
- * - `"default"` — Primary filled button (accent background).
6
- * - `"secondary"` — Muted surface background with border.
7
- * - `"ghost"` — Transparent background with border.
8
- *
9
- * @public
6
+ * - `"default"` — Primary filled button (indigo background).
7
+ * - `"secondary"` — Outlined primary (transparent background, primary border).
8
+ * - `"ghost"` — Raised neutral (surface background with hairline border).
10
9
  */
11
- export type ButtonVariant = "default" | "secondary" | "ghost";
10
+ type ButtonVariant = "default" | "secondary" | "ghost";
12
11
  /**
13
12
  * Size preset for a {@link Button}.
14
13
  *
15
- * - `"default"` — Compact (height 2rem / 32 px).
16
- * - `"lg"` — Large with generous padding, suitable for primary CTAs.
17
- *
18
- * @public
14
+ * - `"default"` — Compact control (height 36 px).
15
+ * - `"lg"` — Primary CTA (height 44 px, generous padding).
19
16
  */
20
- export type ButtonSize = "default" | "lg";
17
+ type ButtonSize = "default" | "lg";
21
18
  /**
22
19
  * A styled button with variant and size presets.
23
20
  *
@@ -47,4 +44,5 @@ export declare function Button({ variant, size, className, children, style, ...r
47
44
  size?: ButtonSize;
48
45
  className?: string;
49
46
  children?: ReactNode;
50
- } & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "className">): import("react/jsx-runtime").JSX.Element;
47
+ } & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "className">): import("react").JSX.Element;
48
+ export {};
@@ -3,14 +3,6 @@ import clsx from "clsx";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  //#region components/button.tsx
5
5
  /** @jsxImportSource react */
6
- const LG_STYLE = {
7
- display: "grid",
8
- placeItems: "center",
9
- appearance: "none",
10
- margin: 0,
11
- padding: "12px 32px",
12
- lineHeight: 1
13
- };
14
6
  /**
15
7
  * A styled button with variant and size presets.
16
8
  *
@@ -40,27 +32,26 @@ function Button({ variant = "default", size = "default", className, children, st
40
32
  let variantStyle;
41
33
  if (variant === "default") variantStyle = {
42
34
  background: theme.primary,
43
- color: "#fff",
35
+ color: theme.surface,
44
36
  borderColor: "transparent"
45
37
  };
46
38
  else if (variant === "secondary") variantStyle = {
47
- background: "rgba(255,255,255,0.059)",
48
- color: "rgba(255,255,255,0.618)",
49
- borderColor: theme.border
39
+ background: "transparent",
40
+ color: theme.primary,
41
+ borderColor: theme.primary
50
42
  };
51
43
  else variantStyle = {
52
- background: "transparent",
53
- color: "rgba(255,255,255,0.618)",
44
+ background: theme.surface,
45
+ color: theme.text,
54
46
  borderColor: theme.border
55
47
  };
56
48
  return /* @__PURE__ */ jsx("button", {
57
49
  type: "button",
58
50
  style: {
59
- ...size === "lg" ? LG_STYLE : void 0,
60
51
  ...variantStyle,
61
52
  ...style
62
53
  },
63
- className: clsx(size !== "lg" && "flex items-center justify-center appearance-none m-0 h-8 px-3 py-1.5 w-fit leading-none", "rounded-aai text-sm font-medium cursor-pointer border outline-none", className),
54
+ className: clsx("inline-flex items-center justify-center appearance-none m-0 w-fit whitespace-nowrap", size === "lg" ? "h-11 px-7 text-xs" : "h-9 px-5 text-[11px]", "rounded-aai font-aai font-medium tracking-[1.4px] uppercase leading-none", "cursor-pointer border outline-none transition-colors duration-150", "disabled:cursor-not-allowed disabled:opacity-50", className),
64
55
  ...rest,
65
56
  children
66
57
  });
@@ -1,8 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
2
  /**
3
- * The main chat interface for a voice agent session.
4
- * Displays a header (with optional icon, title, and state indicator), an
5
- * inline error banner, the {@link MessageList}, and session {@link Controls}.
3
+ * The main chat interface for a voice agent session — the design-system
4
+ * "voice agent console": a 760px column on the cream page with a header
5
+ * (logo + live-status eyebrow), the conversation on a raised white card,
6
+ * and the session controls beneath it.
6
7
  *
7
8
  * Must be rendered inside a {@link SessionProvider}.
8
9
  *
@@ -13,26 +14,14 @@ import type { ReactNode } from "react";
13
14
  * </StartScreen>
14
15
  * ```
15
16
  *
16
- * @example Pair with a sidebar
17
- * ```tsx
18
- * <SidebarLayout sidebar={<RecipeCard />}>
19
- * <ChatView className="border-l" />
20
- * </SidebarLayout>
21
- * ```
22
- *
23
- * @example Custom header icon
24
- * ```tsx
25
- * <ChatView icon={<img src="/logo.svg" />} title="My Agent" />
26
- * ```
27
- *
28
- * @param icon - Optional element rendered before the title in the header.
17
+ * @param icon - Optional element rendered in place of the logo in the header.
29
18
  * @param title - Optional title string for the header.
30
19
  * @param className - Additional CSS class names applied to the root element.
31
20
  *
32
21
  * @public
33
22
  */
34
23
  export declare function ChatView({ icon, title, className, }: {
35
- icon?: ReactNode;
36
- title?: string;
37
- className?: string;
24
+ icon?: ReactNode | undefined;
25
+ title?: string | undefined;
26
+ className?: string | undefined;
38
27
  }): ReactNode;