@alexkroman1/aai-ui 0.12.3 → 1.0.2

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/_react-test-utils.d.ts +86 -0
  2. package/dist/audio.js +4 -5
  3. package/dist/build-default-client.d.ts +1 -0
  4. package/dist/components/button.d.ts +4 -4
  5. package/dist/components/button.js +27 -4
  6. package/dist/components/chat-view.d.ts +14 -5
  7. package/dist/components/chat-view.js +64 -23
  8. package/dist/components/controls.d.ts +1 -1
  9. package/dist/components/controls.js +7 -4
  10. package/dist/components/message-list.d.ts +2 -2
  11. package/dist/components/message-list.js +100 -27
  12. package/dist/components/sidebar-layout.d.ts +7 -7
  13. package/dist/components/sidebar-layout.js +15 -8
  14. package/dist/components/start-screen.d.ts +4 -4
  15. package/dist/components/start-screen.js +16 -7
  16. package/dist/components/tool-call-block.d.ts +5 -6
  17. package/dist/components/tool-call-block.js +2 -117
  18. package/dist/components/tool-config-context.d.ts +22 -0
  19. package/dist/context.d.ts +23 -0
  20. package/dist/context.js +39 -0
  21. package/dist/default-client/assets/audio-DH9LpexG.js +1 -0
  22. package/dist/default-client/assets/capture-processor-C26lSiVr.js +53 -0
  23. package/dist/default-client/assets/default-client-AeJhjHfj.js +49 -0
  24. package/dist/default-client/assets/default-client-BK1ItCvw.css +2 -0
  25. package/dist/default-client/assets/playback-processor-dSA8Im99.js +101 -0
  26. package/dist/default-client/default-client.html +15 -0
  27. package/dist/default-client.d.ts +2 -0
  28. package/dist/define-client.d.ts +72 -24
  29. package/dist/define-client.js +71 -45
  30. package/dist/hooks.d.ts +5 -0
  31. package/dist/hooks.js +55 -0
  32. package/dist/index.d.ts +10 -32
  33. package/dist/index.js +7 -14
  34. package/dist/session-core.d.ts +70 -0
  35. package/dist/session-core.js +437 -0
  36. package/dist/tool-call-block-CCuChsFm.js +131 -0
  37. package/dist/types.d.ts +19 -22
  38. package/package.json +21 -17
  39. package/styles.css +3 -33
  40. package/dist/client-context.d.ts +0 -33
  41. package/dist/client-context.js +0 -15
  42. package/dist/client-handler.d.ts +0 -43
  43. package/dist/components/app.d.ts +0 -23
  44. package/dist/components/app.js +0 -41
  45. package/dist/components/error-banner.d.ts +0 -21
  46. package/dist/components/error-banner.js +0 -27
  47. package/dist/components/message-bubble.d.ts +0 -23
  48. package/dist/components/message-bubble.js +0 -35
  49. package/dist/components/state-indicator.d.ts +0 -29
  50. package/dist/components/state-indicator.js +0 -37
  51. package/dist/components/thinking-indicator.d.ts +0 -16
  52. package/dist/components/thinking-indicator.js +0 -34
  53. package/dist/components/tool-icons.d.ts +0 -17
  54. package/dist/components/tool-icons.js +0 -128
  55. package/dist/components/transcript.d.ts +0 -25
  56. package/dist/components/transcript.js +0 -35
  57. package/dist/session-CWB7vmqz.js +0 -429
  58. package/dist/session.d.ts +0 -69
  59. package/dist/session.js +0 -2
  60. package/dist/signals.d.ts +0 -120
  61. package/dist/signals.js +0 -161
  62. package/dist/types.test-d.d.ts +0 -7
@@ -1,8 +1,9 @@
1
- import { useSession } from "../signals.js";
1
+ import { useSession, useTheme } from "../context.js";
2
2
  import { Button } from "./button.js";
3
3
  import clsx from "clsx";
4
- import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region components/start-screen.tsx
6
+ /** @jsxImportSource react */
6
7
  /**
7
8
  * A centered start screen with icon, title, subtitle, and a start button.
8
9
  * Renders `children` (the main app) once the session has started.
@@ -22,19 +23,27 @@ import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
22
23
  */
23
24
  function StartScreen({ children, icon, title, subtitle, buttonText = "Start", className }) {
24
25
  const { started, start } = useSession();
25
- if (started.value) return /* @__PURE__ */ jsx(Fragment, { children });
26
+ const theme = useTheme();
27
+ if (started) return children;
26
28
  return /* @__PURE__ */ jsx("div", {
27
- class: clsx("flex items-center justify-center h-screen bg-aai-bg font-aai", className),
29
+ className: clsx("flex items-center justify-center h-screen font-aai", className),
30
+ style: { background: theme.bg },
28
31
  children: /* @__PURE__ */ jsxs("div", {
29
- class: "flex flex-col items-center gap-4 bg-aai-surface border border-aai-border rounded-lg px-12 py-10 max-w-sm text-center",
32
+ className: "flex flex-col items-center gap-4 border rounded-lg px-12 py-10 max-w-sm text-center",
33
+ style: {
34
+ background: theme.surface,
35
+ borderColor: theme.border
36
+ },
30
37
  children: [
31
38
  icon,
32
39
  title && /* @__PURE__ */ jsx("h1", {
33
- class: "font-semibold text-aai-primary m-0",
40
+ className: "font-semibold m-0",
41
+ style: { color: theme.primary },
34
42
  children: title
35
43
  }),
36
44
  subtitle && /* @__PURE__ */ jsx("p", {
37
- class: "text-sm text-aai-text-muted m-0",
45
+ className: "text-sm m-0",
46
+ style: { color: "rgba(255,255,255,0.284)" },
38
47
  children: subtitle
39
48
  }),
40
49
  /* @__PURE__ */ jsx(Button, {
@@ -1,12 +1,11 @@
1
- import type * as preact from "preact";
1
+ import { type ReactNode } from "react";
2
2
  import type { ToolCallInfo } from "../types.ts";
3
3
  /**
4
- * Renders a tool invocation with an icon, title, subtitle, and a
4
+ * Renders a tool invocation with an optional icon/emoji, title, subtitle, and a
5
5
  * collapsible result viewer.
6
6
  *
7
- * Built-in tool types (`web_search`, `visit_webpage`, `run_code`,
8
- * `fetch_json`, `user_input`) get custom icons and labels. Unknown tools
9
- * fall back to a generic bolt icon.
7
+ * Tool display is configured via `ToolConfigContext`. If no config is found
8
+ * for a tool name, the raw tool name is shown as the title.
10
9
  *
11
10
  * While the tool call is pending a shimmer animation is shown. Once
12
11
  * complete, clicking the block expands the formatted JSON result.
@@ -24,4 +23,4 @@ import type { ToolCallInfo } from "../types.ts";
24
23
  export declare function ToolCallBlock({ toolCall, className, }: {
25
24
  toolCall: ToolCallInfo;
26
25
  className?: string;
27
- }): preact.JSX.Element;
26
+ }): ReactNode;
@@ -1,118 +1,3 @@
1
- import { BoltIcon, ChatBubbleIcon, DownloadIcon, ExternalLinkIcon, SearchIcon, TerminalIcon } from "./tool-icons.js";
2
- import clsx from "clsx";
3
- import { useComputed, useSignal } from "@preact/signals";
4
- import { jsx, jsxs } from "preact/jsx-runtime";
5
- //#region components/tool-call-block.tsx
6
- const argField = (key) => (args) => String(args[key] ?? "");
7
- const TOOL_CONFIG = {
8
- web_search: {
9
- Icon: SearchIcon,
10
- title: "Web Search",
11
- subtitle: argField("query")
12
- },
13
- visit_webpage: {
14
- Icon: ExternalLinkIcon,
15
- title: "Visit Page",
16
- subtitle: argField("url")
17
- },
18
- run_code: {
19
- Icon: TerminalIcon,
20
- title: "Run Code",
21
- subtitle: (args) => {
22
- const firstLine = String(args.code ?? "").split("\n")[0] ?? "";
23
- return firstLine.length > 80 ? `${firstLine.slice(0, 80)}...` : firstLine;
24
- }
25
- },
26
- fetch_json: {
27
- Icon: DownloadIcon,
28
- title: "Fetch JSON",
29
- subtitle: argField("url")
30
- },
31
- user_input: {
32
- Icon: ChatBubbleIcon,
33
- title: "Asking User",
34
- subtitle: argField("question")
35
- }
36
- };
37
- const DEFAULT_CONFIG = {
38
- Icon: BoltIcon,
39
- title: "",
40
- subtitle: (args) => {
41
- const summary = JSON.stringify(args);
42
- return summary.length > 80 ? `${summary.slice(0, 80)}...` : summary;
43
- }
44
- };
45
- function formatResult(result) {
46
- try {
47
- return JSON.stringify(JSON.parse(result), null, 2);
48
- } catch {
49
- return result;
50
- }
51
- }
52
- /**
53
- * Renders a tool invocation with an icon, title, subtitle, and a
54
- * collapsible result viewer.
55
- *
56
- * Built-in tool types (`web_search`, `visit_webpage`, `run_code`,
57
- * `fetch_json`, `user_input`) get custom icons and labels. Unknown tools
58
- * fall back to a generic bolt icon.
59
- *
60
- * While the tool call is pending a shimmer animation is shown. Once
61
- * complete, clicking the block expands the formatted JSON result.
62
- *
63
- * @example
64
- * ```tsx
65
- * <ToolCallBlock toolCall={toolCall} />
66
- * ```
67
- *
68
- * @param toolCall - The tool call to render (see {@link ToolCallInfo}).
69
- * @param className - Additional CSS class names.
70
- *
71
- * @public
72
- */
73
- function ToolCallBlock({ toolCall, className }) {
74
- const isOpen = useSignal(false);
75
- const config = TOOL_CONFIG[toolCall.toolName] ?? DEFAULT_CONFIG;
76
- const isPending = toolCall.status === "pending";
77
- const title = config.title || toolCall.toolName;
78
- const canExpand = !isPending && Boolean(toolCall.result);
79
- const formatted = useComputed(() => toolCall.result ? formatResult(toolCall.result) : "");
80
- return /* @__PURE__ */ jsxs("div", {
81
- class: clsx("flex flex-col", className),
82
- children: [/* @__PURE__ */ jsxs("button", {
83
- type: "button",
84
- "aria-expanded": canExpand ? isOpen.value : void 0,
85
- disabled: isPending,
86
- class: clsx("flex items-center gap-2 px-3 py-2 rounded-aai border border-aai-border bg-aai-surface-faint select-none text-left w-full", canExpand && "cursor-pointer"),
87
- onClick: () => {
88
- if (canExpand) isOpen.value = !isOpen.value;
89
- },
90
- children: [
91
- /* @__PURE__ */ jsx(config.Icon, { class: "w-4 h-4 text-aai-text-dim shrink-0" }),
92
- /* @__PURE__ */ jsx("span", {
93
- class: clsx("text-sm font-medium text-aai-text", isPending && "tool-shimmer"),
94
- children: title
95
- }),
96
- /* @__PURE__ */ jsx("span", {
97
- class: "text-sm text-aai-text-dim truncate flex-1 min-w-0",
98
- children: config.subtitle(toolCall.args)
99
- }),
100
- canExpand && /* @__PURE__ */ jsx("span", {
101
- class: "text-xs text-aai-text-dim shrink-0",
102
- children: isOpen.value ? "▾" : "▸"
103
- })
104
- ]
105
- }), isOpen.value && /* @__PURE__ */ jsxs("div", {
106
- class: "border-x border-b border-aai-border rounded-b-aai bg-aai-surface max-h-64 overflow-auto",
107
- children: [toolCall.toolName === "run_code" && toolCall.args.code && /* @__PURE__ */ jsx("pre", {
108
- class: "text-xs text-aai-text p-2 whitespace-pre-wrap border-b border-aai-border font-mono",
109
- children: String(toolCall.args.code)
110
- }), formatted.value && /* @__PURE__ */ jsx("pre", {
111
- class: "text-xs text-aai-text-dim p-2 whitespace-pre-wrap",
112
- children: formatted.value
113
- })]
114
- })]
115
- });
116
- }
117
- //#endregion
1
+ import "../context.js";
2
+ import { t as ToolCallBlock } from "../tool-call-block-CCuChsFm.js";
118
3
  export { ToolCallBlock };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Display configuration for a tool call in the UI.
3
+ *
4
+ * @public
5
+ */
6
+ export type ToolDisplayConfig = Record<string, {
7
+ icon?: string;
8
+ label?: string;
9
+ }>;
10
+ /**
11
+ * Context for tool display configuration.
12
+ * Provided by `client` or manually via `ToolConfigContext.Provider`.
13
+ *
14
+ * @public
15
+ */
16
+ export declare const ToolConfigContext: import("react").Context<ToolDisplayConfig>;
17
+ /**
18
+ * Read tool display configuration from the nearest `ToolConfigContext.Provider`.
19
+ *
20
+ * @internal
21
+ */
22
+ export declare function useToolConfig(): ToolDisplayConfig;
@@ -0,0 +1,23 @@
1
+ import { type ReactNode } from "react";
2
+ import type { SessionCore, SessionSnapshot } from "./session-core.ts";
3
+ import type { ClientTheme } from "./types.ts";
4
+ declare const DEFAULT_THEME: Required<ClientTheme>;
5
+ export declare function SessionProvider({ value, children }: {
6
+ value: SessionCore;
7
+ children?: ReactNode;
8
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<SessionCore | null>>;
9
+ export type Session = SessionSnapshot & {
10
+ start(): void;
11
+ cancel(): void;
12
+ resetState(): void;
13
+ reset(): void;
14
+ disconnect(): void;
15
+ toggle(): void;
16
+ };
17
+ export declare function useSession(): Session;
18
+ export declare function ThemeProvider({ value, children }: {
19
+ value?: ClientTheme;
20
+ children?: ReactNode;
21
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<Required<ClientTheme>>>;
22
+ export declare function useTheme(): Required<ClientTheme>;
23
+ export { DEFAULT_THEME };
@@ -0,0 +1,39 @@
1
+ import { createContext, createElement, useContext, useSyncExternalStore } from "react";
2
+ //#region context.ts
3
+ const DEFAULT_THEME = {
4
+ bg: "#101010",
5
+ primary: "#fab283",
6
+ text: "rgba(255, 255, 255, 0.94)",
7
+ surface: "#151515",
8
+ border: "#282828"
9
+ };
10
+ const SessionCtx = createContext(null);
11
+ function SessionProvider({ value, children }) {
12
+ return createElement(SessionCtx.Provider, { value }, children);
13
+ }
14
+ function useSession() {
15
+ const core = useContext(SessionCtx);
16
+ if (!core) throw new Error("useSession must be used within <SessionProvider>");
17
+ return {
18
+ ...useSyncExternalStore(core.subscribe, core.getSnapshot),
19
+ start: core.start,
20
+ cancel: core.cancel,
21
+ resetState: core.resetState,
22
+ reset: core.reset,
23
+ disconnect: core.disconnect,
24
+ toggle: core.toggle
25
+ };
26
+ }
27
+ const ThemeCtx = createContext(DEFAULT_THEME);
28
+ function ThemeProvider({ value, children }) {
29
+ const merged = value ? {
30
+ ...DEFAULT_THEME,
31
+ ...value
32
+ } : DEFAULT_THEME;
33
+ return createElement(ThemeCtx.Provider, { value: merged }, children);
34
+ }
35
+ function useTheme() {
36
+ return useContext(ThemeCtx);
37
+ }
38
+ //#endregion
39
+ export { DEFAULT_THEME, SessionProvider, ThemeProvider, useSession, useTheme };
@@ -0,0 +1 @@
1
+ var e=.1;async function t(t){let{sttSampleRate:n,ttsSampleRate:r,captureWorkletSrc:i,playbackWorkletSrc:a,onMicData:o}=t,s=r,c=new AudioContext({sampleRate:s,latencyHint:`playback`});await c.resume();let l=await navigator.mediaDevices.getUserMedia({audio:{sampleRate:s,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}});try{await Promise.all([c.audioWorklet.addModule(i),c.audioWorklet.addModule(a)])}catch(e){for(let e of l.getTracks())e.stop();throw await c.close().catch(e=>{console.warn(`AudioContext close failed:`,e)}),e}let u=c.createMediaStreamSource(l),d=new AudioWorkletNode(c,`capture-processor`,{channelCount:1,channelCountMode:`explicit`,processorOptions:{contextRate:s,sttSampleRate:n}});u.connect(d);let f=Math.floor(n*e)*2,p=f*2,m=new Uint8Array(p),h=new Uint8Array(p),g=m,_=0;d.port.postMessage({event:`start`}),d.port.onmessage=e=>{if(e.data.event!==`chunk`)return;let t=new Uint8Array(e.data.buffer);g.set(t,_),_+=t.byteLength,_>=f&&(o(g.buffer.slice(0,_)),g=g===m?h:m,_=0)};let v=null,y=null,b=new AbortController,{onPlaybackProgress:x}=t;function S(){if(v)return v;let e=new AudioWorkletNode(c,`playback-processor`,{processorOptions:{sampleRate:s}});return e.connect(c.destination),e.port.onmessage=t=>{t.data.event===`stop`?(e.disconnect(),v===e&&(v=null),y?.(),y=null):t.data.event===`progress`&&x?.(t.data.readPos)},v=e,e}let C={enqueue(e){b.signal.aborted||e.byteLength!==0&&S().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){return v?new Promise(e=>{y=e,v?.port.postMessage({event:`done`})}):Promise.resolve()},flush(){v&&v.port.postMessage({event:`interrupt`})},async close(){if(!b.signal.aborted){b.abort(),d.port.postMessage({event:`stop`}),u.disconnect(),d.disconnect(),v&&v.disconnect();for(let e of l.getTracks())e.stop();await c.close().catch(()=>{})}},async[Symbol.asyncDispose](){await C.close()}};return C}export{t as createVoiceIO};
@@ -0,0 +1,53 @@
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
+ this.port.onmessage = (e) => {
12
+ if (e.data.event === 'start') this.recording = true;
13
+ else if (e.data.event === 'stop') this.recording = false;
14
+ };
15
+ }
16
+
17
+ resample(input) {
18
+ const ratio = this.ratio;
19
+ const outLen = Math.ceil(input.length / ratio);
20
+ const out = new Float32Array(outLen);
21
+ for (let i = 0; i < outLen; i++) {
22
+ const srcIdx = i * ratio;
23
+ const idx = srcIdx | 0;
24
+ const frac = srcIdx - idx;
25
+ const a = input[idx];
26
+ const b = idx + 1 < input.length ? input[idx + 1] : a;
27
+ out[i] = a + frac * (b - a);
28
+ }
29
+ return out;
30
+ }
31
+
32
+ process(inputs) {
33
+ const input = inputs[0];
34
+ if (!input || !input[0] || !this.recording) return true;
35
+
36
+ const raw = input[0];
37
+ const samples = this.needsResample ? this.resample(raw) : raw;
38
+
39
+ // Convert Float32 -> Int16
40
+ const buffer = new ArrayBuffer(samples.length * 2);
41
+ const view = new DataView(buffer);
42
+ for (let i = 0; i < samples.length; i++) {
43
+ const s = Math.max(-1, Math.min(1, samples[i]));
44
+ view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);
45
+ }
46
+
47
+ this.port.postMessage({ event: 'chunk', buffer }, [buffer]);
48
+ return true;
49
+ }
50
+ }
51
+
52
+ registerProcessor('capture-processor', CaptureProcessor);
53
+ `],{type:`application/javascript`}),t=URL.createObjectURL(e);export{t as default};