@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.
- package/dist/_react-test-utils.d.ts +86 -0
- package/dist/audio.js +4 -5
- package/dist/build-default-client.d.ts +1 -0
- package/dist/components/button.d.ts +4 -4
- package/dist/components/button.js +27 -4
- package/dist/components/chat-view.d.ts +14 -5
- package/dist/components/chat-view.js +64 -23
- package/dist/components/controls.d.ts +1 -1
- package/dist/components/controls.js +7 -4
- package/dist/components/message-list.d.ts +2 -2
- package/dist/components/message-list.js +100 -27
- package/dist/components/sidebar-layout.d.ts +7 -7
- package/dist/components/sidebar-layout.js +15 -8
- package/dist/components/start-screen.d.ts +4 -4
- package/dist/components/start-screen.js +16 -7
- package/dist/components/tool-call-block.d.ts +5 -6
- package/dist/components/tool-call-block.js +2 -117
- package/dist/components/tool-config-context.d.ts +22 -0
- package/dist/context.d.ts +23 -0
- package/dist/context.js +39 -0
- package/dist/default-client/assets/audio-DH9LpexG.js +1 -0
- package/dist/default-client/assets/capture-processor-C26lSiVr.js +53 -0
- package/dist/default-client/assets/default-client-AeJhjHfj.js +49 -0
- package/dist/default-client/assets/default-client-BK1ItCvw.css +2 -0
- package/dist/default-client/assets/playback-processor-dSA8Im99.js +101 -0
- package/dist/default-client/default-client.html +15 -0
- package/dist/default-client.d.ts +2 -0
- package/dist/define-client.d.ts +72 -24
- package/dist/define-client.js +71 -45
- package/dist/hooks.d.ts +5 -0
- package/dist/hooks.js +55 -0
- package/dist/index.d.ts +10 -32
- package/dist/index.js +7 -14
- package/dist/session-core.d.ts +70 -0
- package/dist/session-core.js +437 -0
- package/dist/tool-call-block-CCuChsFm.js +131 -0
- package/dist/types.d.ts +19 -22
- package/package.json +21 -17
- package/styles.css +3 -33
- package/dist/client-context.d.ts +0 -33
- package/dist/client-context.js +0 -15
- package/dist/client-handler.d.ts +0 -43
- package/dist/components/app.d.ts +0 -23
- package/dist/components/app.js +0 -41
- package/dist/components/error-banner.d.ts +0 -21
- package/dist/components/error-banner.js +0 -27
- package/dist/components/message-bubble.d.ts +0 -23
- package/dist/components/message-bubble.js +0 -35
- package/dist/components/state-indicator.d.ts +0 -29
- package/dist/components/state-indicator.js +0 -37
- package/dist/components/thinking-indicator.d.ts +0 -16
- package/dist/components/thinking-indicator.js +0 -34
- package/dist/components/tool-icons.d.ts +0 -17
- package/dist/components/tool-icons.js +0 -128
- package/dist/components/transcript.d.ts +0 -25
- package/dist/components/transcript.js +0 -35
- package/dist/session-CWB7vmqz.js +0 -429
- package/dist/session.d.ts +0 -69
- package/dist/session.js +0 -2
- package/dist/signals.d.ts +0 -120
- package/dist/signals.js +0 -161
- package/dist/types.test-d.d.ts +0 -7
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { useSession } from "../
|
|
1
|
+
import { useSession, useTheme } from "../context.js";
|
|
2
2
|
import { Button } from "./button.js";
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
-
import {
|
|
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
|
-
|
|
26
|
+
const theme = useTheme();
|
|
27
|
+
if (started) return children;
|
|
26
28
|
return /* @__PURE__ */ jsx("div", {
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
+
className: "font-semibold m-0",
|
|
41
|
+
style: { color: theme.primary },
|
|
34
42
|
children: title
|
|
35
43
|
}),
|
|
36
44
|
subtitle && /* @__PURE__ */ jsx("p", {
|
|
37
|
-
|
|
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
|
|
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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
}):
|
|
26
|
+
}): ReactNode;
|
|
@@ -1,118 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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 };
|
package/dist/context.js
ADDED
|
@@ -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};
|