@alexkroman1/aai-ui 6.4.0 → 6.5.1

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.
@@ -18,5 +18,17 @@ function pageBaseUrl() {
18
18
  if (typeof location === "undefined") return "";
19
19
  return location.origin + location.pathname;
20
20
  }
21
+ /**
22
+ * Set the document title, when there is one to set and a document to set it on.
23
+ *
24
+ * Both mounts do this and neither may clobber a title the page's own HTML
25
+ * declared — `client()`'s custom-component tier because there is no shell header
26
+ * to show the name in, `page()` because there is no shell at all. One copy, for
27
+ * the reason `resolveContainer` and `mountRoot` are shared: the rule is the
28
+ * same, so the two mounts must not be able to disagree about it.
29
+ */
30
+ function setPageTitle(name) {
31
+ if (name && typeof document !== "undefined") document.title = name;
32
+ }
21
33
  //#endregion
22
- export { truncate as n, tryParseJSON as r, pageBaseUrl as t };
34
+ export { tryParseJSON as i, setPageTitle as n, truncate as r, pageBaseUrl as t };
package/dist/_utils.d.ts CHANGED
@@ -8,3 +8,13 @@ export declare function truncate(s: string, max?: number): string;
8
8
  * shareable-URL chip can never disagree about which agent the page is.
9
9
  */
10
10
  export declare function pageBaseUrl(): string;
11
+ /**
12
+ * Set the document title, when there is one to set and a document to set it on.
13
+ *
14
+ * Both mounts do this and neither may clobber a title the page's own HTML
15
+ * declared — `client()`'s custom-component tier because there is no shell header
16
+ * to show the name in, `page()` because there is no shell at all. One copy, for
17
+ * the reason `resolveContainer` and `mountRoot` are shared: the rule is the
18
+ * same, so the two mounts must not be able to disagree about it.
19
+ */
20
+ export declare function setPageTitle(name: string | undefined): void;
@@ -16,3 +16,19 @@
16
16
  * into ids would corrupt it silently.
17
17
  */
18
18
  export declare function filesOf(value: unknown): File[];
19
+ /**
20
+ * The input properties still carrying a `File` — i.e. the ones that CANNOT survive
21
+ * being sent.
22
+ *
23
+ * A run input is JSON, and `JSON.stringify(new File(…))` is `{}` — no `toJSON`, no
24
+ * own enumerable properties. So a File left in a payload does not fail to send: it
25
+ * arrives as an empty object, and the workflow rejects it against whatever its own
26
+ * schema says the property should be. Measured in production as
27
+ * `Invalid input for workflow "transcribe": recording: Invalid input` — a message
28
+ * about a type, on a form where the user had picked a perfectly good file.
29
+ *
30
+ * Exported beside {@link filesOf} because it is the same question asked at the
31
+ * other end: that one decides which fields to UPLOAD, this one checks that none
32
+ * were missed. Both hooks are the callers.
33
+ */
34
+ export declare function fileFields(input: unknown): string[];
package/dist/audio.js CHANGED
@@ -12,6 +12,19 @@ function assertGranted(granted, requested, side) {
12
12
  throw new Error(`Browser refused the ${side} sample rate: asked for ${requested} Hz, got ${granted} Hz`);
13
13
  }
14
14
  /**
15
+ * The error a dead worklet processor reports.
16
+ *
17
+ * A processor exception permanently kills the node — no further messages or
18
+ * audio will ever arrive — so both sides log it and hand it on. One spelling of
19
+ * that, because the pair differed only in the word "capture"/"playback" and in
20
+ * what the playback side has to settle afterwards.
21
+ */
22
+ function workletCrash(side) {
23
+ const err = /* @__PURE__ */ new Error(`Audio ${side} worklet crashed`);
24
+ console.error("[aai-ui]", err.message);
25
+ return err;
26
+ }
27
+ /**
15
28
  * Release a microphone that was (or later gets) granted while another init
16
29
  * step failed; if `getUserMedia` itself rejected, this is a no-op. Without
17
30
  * it, a mic granted after a failed init keeps the browser's recording
@@ -116,11 +129,7 @@ async function createVoiceIO(opts) {
116
129
  const mic = capCtx.createMediaStreamSource(stream);
117
130
  const capture = createCaptureNode(capCtx, onMicData, onMicSilent);
118
131
  mic.connect(capture.node);
119
- capture.node.onprocessorerror = () => {
120
- const err = /* @__PURE__ */ new Error("Audio capture worklet crashed");
121
- console.error("[aai-ui]", err.message);
122
- onError?.(err);
123
- };
132
+ capture.node.onprocessorerror = () => onError?.(workletCrash("capture"));
124
133
  capture.start();
125
134
  let playNode = null;
126
135
  let onPlaybackStop = null;
@@ -157,8 +166,7 @@ async function createVoiceIO(opts) {
157
166
  else if (e.data.event === "progress") onPlaybackProgress?.(e.data.bufferedMs);
158
167
  };
159
168
  node.onprocessorerror = () => {
160
- const err = /* @__PURE__ */ new Error("Audio playback worklet crashed");
161
- console.error("[aai-ui]", err.message);
169
+ const err = workletCrash("playback");
162
170
  settlePendingStop();
163
171
  onError?.(err);
164
172
  };
@@ -1,9 +1,9 @@
1
- import { t as MessageList } from "./message-list-BwA3rdPi.js";
1
+ import { t as MessageList } from "./message-list-CpPV7dGx.js";
2
2
  import { useSessionSelector, useTheme } from "./context.js";
3
3
  import { n as THINKING_COLOR, r as inkTint, t as ERROR_COLOR } from "./_colors-CcAi2FOU.js";
4
4
  import { t as AaiLogo } from "./aai-logo-9xRBGVFl.js";
5
5
  import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
6
- import { t as Controls } from "./controls-Cy_YVfsa.js";
6
+ import { t as Controls } from "./controls-CjG91QJ4.js";
7
7
  import clsx from "clsx";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region components/console-shell.tsx
@@ -4,6 +4,35 @@ import clsx from "clsx";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  //#region components/button.tsx
6
6
  /** @jsxImportSource react */
7
+ /** One variant's rest and hover colors, derived from the theme. */
8
+ function variantColors(variant, theme) {
9
+ switch (variant) {
10
+ case "default": return {
11
+ bg: theme.primary,
12
+ fg: theme.surface,
13
+ border: "transparent",
14
+ hoverBg: inkTint(theme.text, theme.primary, 14),
15
+ hoverFg: theme.surface,
16
+ hoverBorder: "transparent"
17
+ };
18
+ case "secondary": return {
19
+ bg: "transparent",
20
+ fg: theme.primary,
21
+ border: theme.primary,
22
+ hoverBg: primaryTint(theme.primary, theme.surface, 8),
23
+ hoverFg: theme.primary,
24
+ hoverBorder: theme.primary
25
+ };
26
+ default: return {
27
+ bg: theme.surface,
28
+ fg: theme.text,
29
+ border: theme.border,
30
+ hoverBg: inkTint(theme.text, theme.surface, 5),
31
+ hoverFg: theme.text,
32
+ hoverBorder: theme.border
33
+ };
34
+ }
35
+ }
7
36
  /**
8
37
  * A styled button with variant and size presets.
9
38
  *
@@ -33,31 +62,7 @@ import { jsx } from "react/jsx-runtime";
33
62
  */
34
63
  function Button({ variant = "default", size = "default", className, children, style, ...rest }) {
35
64
  const theme = useTheme();
36
- let colors;
37
- if (variant === "default") colors = {
38
- bg: theme.primary,
39
- fg: theme.surface,
40
- border: "transparent",
41
- hoverBg: inkTint(theme.text, theme.primary, 14),
42
- hoverFg: theme.surface,
43
- hoverBorder: "transparent"
44
- };
45
- else if (variant === "secondary") colors = {
46
- bg: "transparent",
47
- fg: theme.primary,
48
- border: theme.primary,
49
- hoverBg: primaryTint(theme.primary, theme.surface, 8),
50
- hoverFg: theme.primary,
51
- hoverBorder: theme.primary
52
- };
53
- else colors = {
54
- bg: theme.surface,
55
- fg: theme.text,
56
- border: theme.border,
57
- hoverBg: inkTint(theme.text, theme.surface, 5),
58
- hoverFg: theme.text,
59
- hoverBorder: theme.border
60
- };
65
+ const colors = variantColors(variant, theme);
61
66
  return /* @__PURE__ */ jsx("button", {
62
67
  type: "button",
63
68
  style: {
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as ChatView } from "../chat-view-CK61bWWx.js";
2
+ import { t as ChatView } from "../chat-view-ByQFf94G.js";
3
3
  export { ChatView };
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as Controls } from "../controls-Cy_YVfsa.js";
2
+ import { t as Controls } from "../controls-CjG91QJ4.js";
3
3
  export { Controls };
@@ -1,3 +1,3 @@
1
- import { t as MessageList } from "../message-list-BwA3rdPi.js";
1
+ import { t as MessageList } from "../message-list-CpPV7dGx.js";
2
2
  import "../context.js";
3
3
  export { MessageList };
@@ -1,3 +1,3 @@
1
1
  import "../context.js";
2
- import { t as ToolCallBlock } from "../tool-call-block-CrLN7xlI.js";
2
+ import { t as ToolCallBlock } from "../tool-call-block-D6pTEPrT.js";
3
3
  export { ToolCallBlock };
@@ -1,7 +1,7 @@
1
1
  import { useSessionCore, useSessionSelector, useTheme } from "./context.js";
2
2
  import { r as inkTint } from "./_colors-CcAi2FOU.js";
3
3
  import { Button } from "./components/button.js";
4
- import { t as pageBaseUrl } from "./_utils-CsqbIVGI.js";
4
+ import { t as pageBaseUrl } from "./_utils-B6498_bm.js";
5
5
  import clsx from "clsx";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { memo, useEffect, useRef, useState } from "react";
@@ -0,0 +1 @@
1
+ import{o as e,s as t}from"./client-audio-constants-Ck0IJO4c.js";var n={echoCancellation:!0,noiseSuppression:!1,autoGainControl:!1,voiceIsolation:!1};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){let t=Error(`Audio ${e} worklet crashed`);return console.error(`[aai-ui]`,t.message),t}function a(e){e.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{})}function o(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(){let{promise:e,resolve:t}=Promise.withResolvers(),n=setTimeout(t,250);return i=()=>{clearTimeout(n),t()},r.port.postMessage({event:`stop`}),e}}}async function s(s){let{sttSampleRate:c,ttsSampleRate:l,captureWorkletSrc:u,playbackWorkletSrc:d,onMicData:f,onError:p,onPlaybackStats:m,onPlaybackProgress:h,onMicSilent:g}=s,_=new AudioContext({sampleRate:l,latencyHint:`playback`}),v=c===l,y=v?_:new AudioContext({sampleRate:c,latencyHint:`interactive`});async function b(){let e=v?[_]:[_,y];await Promise.all(e.map(e=>e.close().catch(e=>{console.warn(`AudioContext close failed:`,e)})))}let x=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...n}}),S;try{[S]=await Promise.all([x,_.resume(),y.resume(),y.audioWorklet.addModule(u),_.audioWorklet.addModule(d)]),r(y.sampleRate,c,`capture`),r(_.sampleRate,l,`playback`)}catch(e){throw a(x),await b(),e}let C=y.createMediaStreamSource(S),w=o(y,f,g);C.connect(w.node),w.node.onprocessorerror=()=>p?.(i(`capture`)),w.start();let T=null,E=null,D=0,O=null,k=new AbortController;function A(){E?.(),E=null,O=null}function j(e){e.stats&&e.stats.concealedSamples>0&&m?.(e.stats),e.reason!==`interrupt`&&(O===null||e.turn===O)&&A()}function M(){if(T)return T;let e=new AudioWorkletNode(_,`playback-processor`);return e.connect(_.destination),e.port.onmessage=e=>{e.data.event===`stop`?j(e.data):e.data.event===`progress`&&h?.(e.data.bufferedMs)},e.onprocessorerror=()=>{let e=i(`playback`);A(),p?.(e)},T=e,e}let N={enqueue(e){k.signal.aborted||e.byteLength!==0&&M().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){if(!T)return Promise.resolve();let n=++D;if(T.port.postMessage({event:`done`,turn:n}),_.state!==`running`)return O=null,Promise.resolve();let{promise:r,resolve:i}=Promise.withResolvers();E?.();let a=()=>{clearInterval(o),clearTimeout(s),E===a&&(E=null,O=null),i()},o=setInterval(()=>{_.state!==`running`&&a()},t),s=setTimeout(a,e);return E=a,O=n,r},flush(){T&&(A(),T.port.postMessage({event:`interrupt`}))},async close(){if(!k.signal.aborted){k.abort(),await w.stop(),C.disconnect(),w.node.disconnect(),T&&T.disconnect();for(let e of S.getTracks())e.stop();await b()}},async[Symbol.asyncDispose](){await N.close()}};return N}export{s as createVoiceIO};