@alexkroman1/aai-ui 0.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.
- package/LICENSE +21 -0
- package/dist/_components/app.d.ts +5 -0
- package/dist/_components/app.js +23 -0
- package/dist/_components/button.d.ts +11 -0
- package/dist/_components/button.js +22 -0
- package/dist/_components/chat-view.d.ts +5 -0
- package/dist/_components/chat-view.js +37 -0
- package/dist/_components/controls.d.ts +4 -0
- package/dist/_components/controls.js +23 -0
- package/dist/_components/error-banner.d.ts +7 -0
- package/dist/_components/error-banner.js +13 -0
- package/dist/_components/message-bubble.d.ts +7 -0
- package/dist/_components/message-bubble.js +19 -0
- package/dist/_components/message-list.d.ts +4 -0
- package/dist/_components/message-list.js +59 -0
- package/dist/_components/sidebar-layout.d.ts +21 -0
- package/dist/_components/sidebar-layout.js +36 -0
- package/dist/_components/start-screen.d.ts +26 -0
- package/dist/_components/start-screen.js +51 -0
- package/dist/_components/state-indicator.d.ts +7 -0
- package/dist/_components/state-indicator.js +15 -0
- package/dist/_components/thinking-indicator.d.ts +5 -0
- package/dist/_components/thinking-indicator.js +22 -0
- package/dist/_components/tool-call-block.d.ts +7 -0
- package/dist/_components/tool-call-block.js +96 -0
- package/dist/_components/tool-icons.d.ts +17 -0
- package/dist/_components/tool-icons.js +128 -0
- package/dist/_components/transcript.d.ts +7 -0
- package/dist/_components/transcript.js +17 -0
- package/dist/_jsdom-setup.d.ts +0 -0
- package/dist/audio.d.ts +45 -0
- package/dist/audio.js +111 -0
- package/dist/components.d.ts +31 -0
- package/dist/components.js +17 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +19 -0
- package/dist/mount-context.d.ts +33 -0
- package/dist/mount-context.js +15 -0
- package/dist/mount.d.ts +51 -0
- package/dist/mount.js +70 -0
- package/dist/session.d.ts +100 -0
- package/dist/session.js +359 -0
- package/dist/signals.d.ts +89 -0
- package/dist/signals.js +138 -0
- package/dist/tsdown.config.d.ts +2 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +5 -0
- package/dist/worklets/capture-processor.d.ts +2 -0
- package/dist/worklets/capture-processor.js +57 -0
- package/dist/worklets/playback-processor.d.ts +2 -0
- package/dist/worklets/playback-processor.js +105 -0
- package/package.json +56 -0
- package/styles.css +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useMountConfig } from "../mount-context.js";
|
|
2
|
+
import { ChatView } from "./chat-view.js";
|
|
3
|
+
import { StartScreen } from "./start-screen.js";
|
|
4
|
+
import { jsx } from "preact/jsx-runtime";
|
|
5
|
+
//#region _components/app.tsx
|
|
6
|
+
function AnsiLogo() {
|
|
7
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
8
|
+
class: "font-aai-mono text-lg leading-[1.1] font-bold text-aai-primary m-0",
|
|
9
|
+
children: "▄▀█ ▄▀█ █\n█▀█ █▀█ █"
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** @public */
|
|
13
|
+
function App({ className }) {
|
|
14
|
+
const { title } = useMountConfig();
|
|
15
|
+
return /* @__PURE__ */ jsx(StartScreen, {
|
|
16
|
+
icon: title ? void 0 : /* @__PURE__ */ jsx(AnsiLogo, {}),
|
|
17
|
+
title,
|
|
18
|
+
className,
|
|
19
|
+
children: /* @__PURE__ */ jsx(ChatView, {})
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { App };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type * as preact from "preact";
|
|
2
|
+
type ButtonVariant = "default" | "secondary" | "ghost";
|
|
3
|
+
type ButtonSize = "default" | "lg";
|
|
4
|
+
/** @public */
|
|
5
|
+
export declare function Button({ variant, size, className, children, ...rest }: {
|
|
6
|
+
variant?: ButtonVariant;
|
|
7
|
+
size?: ButtonSize;
|
|
8
|
+
className?: string;
|
|
9
|
+
children?: preact.ComponentChildren;
|
|
10
|
+
} & Omit<preact.JSX.HTMLAttributes<HTMLButtonElement>, "className">): preact.JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { jsx } from "preact/jsx-runtime";
|
|
3
|
+
//#region _components/button.tsx
|
|
4
|
+
/** @public */
|
|
5
|
+
function Button({ variant = "default", size = "default", className, children, ...rest }) {
|
|
6
|
+
return /* @__PURE__ */ jsx("button", {
|
|
7
|
+
type: "button",
|
|
8
|
+
style: size === "lg" ? {
|
|
9
|
+
display: "grid",
|
|
10
|
+
placeItems: "center",
|
|
11
|
+
appearance: "none",
|
|
12
|
+
margin: 0,
|
|
13
|
+
padding: "12px 32px",
|
|
14
|
+
lineHeight: 1
|
|
15
|
+
} : void 0,
|
|
16
|
+
class: 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", variant === "secondary" && "bg-aai-surface-hover text-aai-text-secondary border-aai-border", variant === "ghost" && "bg-transparent text-aai-text-secondary border-aai-border", variant === "default" && "bg-aai-primary text-white border-transparent", className),
|
|
17
|
+
...rest,
|
|
18
|
+
children
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { Button };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useMountConfig } from "../mount-context.js";
|
|
2
|
+
import { useSession } from "../signals.js";
|
|
3
|
+
import { Controls } from "./controls.js";
|
|
4
|
+
import { ErrorBanner } from "./error-banner.js";
|
|
5
|
+
import { MessageList } from "./message-list.js";
|
|
6
|
+
import { StateIndicator } from "./state-indicator.js";
|
|
7
|
+
import clsx from "clsx";
|
|
8
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
9
|
+
//#region _components/chat-view.tsx
|
|
10
|
+
/** @public */
|
|
11
|
+
function ChatView({ className }) {
|
|
12
|
+
const { session } = useSession();
|
|
13
|
+
const { title } = useMountConfig();
|
|
14
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
15
|
+
class: clsx("flex flex-col h-screen max-w-130 mx-auto bg-aai-bg text-aai-text font-aai text-sm", className),
|
|
16
|
+
children: [
|
|
17
|
+
/* @__PURE__ */ jsxs("div", {
|
|
18
|
+
class: "flex items-center gap-3 px-4 py-3 border-b border-aai-border shrink-0",
|
|
19
|
+
children: [title ? /* @__PURE__ */ jsx("span", {
|
|
20
|
+
class: "text-sm font-semibold text-aai-primary",
|
|
21
|
+
children: title
|
|
22
|
+
}) : /* @__PURE__ */ jsx("pre", {
|
|
23
|
+
class: "font-aai-mono text-[10px] leading-[1.1] font-bold text-aai-primary m-0",
|
|
24
|
+
children: "▄▀█ ▄▀█ █\n█▀█ █▀█ █"
|
|
25
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
26
|
+
class: "ml-auto",
|
|
27
|
+
children: /* @__PURE__ */ jsx(StateIndicator, { state: session.state })
|
|
28
|
+
})]
|
|
29
|
+
}),
|
|
30
|
+
/* @__PURE__ */ jsx(ErrorBanner, { error: session.error }),
|
|
31
|
+
/* @__PURE__ */ jsx(MessageList, {}),
|
|
32
|
+
/* @__PURE__ */ jsx(Controls, {})
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { ChatView };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useSession } from "../signals.js";
|
|
2
|
+
import { Button } from "./button.js";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
5
|
+
//#region _components/controls.tsx
|
|
6
|
+
/** @public */
|
|
7
|
+
function Controls({ className }) {
|
|
8
|
+
const { running, toggle, reset } = useSession();
|
|
9
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10
|
+
class: clsx("flex gap-2 px-4 py-3 border-t border-aai-border shrink-0", className),
|
|
11
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
12
|
+
variant: "secondary",
|
|
13
|
+
onClick: toggle,
|
|
14
|
+
children: running.value ? "Stop" : "Resume"
|
|
15
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
16
|
+
variant: "ghost",
|
|
17
|
+
onClick: reset,
|
|
18
|
+
children: "New Conversation"
|
|
19
|
+
})]
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { Controls };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type * as preact from "preact";
|
|
2
|
+
import type { Reactive, SessionError } from "../types.ts";
|
|
3
|
+
/** @public */
|
|
4
|
+
export declare function ErrorBanner({ error, className, }: {
|
|
5
|
+
error: Reactive<SessionError | null>;
|
|
6
|
+
className?: string;
|
|
7
|
+
}): preact.JSX.Element | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { jsx } from "preact/jsx-runtime";
|
|
3
|
+
//#region _components/error-banner.tsx
|
|
4
|
+
/** @public */
|
|
5
|
+
function ErrorBanner({ error, className }) {
|
|
6
|
+
if (!error.value) return null;
|
|
7
|
+
return /* @__PURE__ */ jsx("div", {
|
|
8
|
+
class: clsx("mx-4 mt-3 px-3 py-2 rounded-aai border border-aai-error/40 bg-aai-error/8 text-[13px] leading-[130%] text-aai-error", className),
|
|
9
|
+
children: error.value.message
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { ErrorBanner };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { jsx } from "preact/jsx-runtime";
|
|
3
|
+
//#region _components/message-bubble.tsx
|
|
4
|
+
/** @public */
|
|
5
|
+
function MessageBubble({ message, className }) {
|
|
6
|
+
if (message.role === "user") return /* @__PURE__ */ jsx("div", {
|
|
7
|
+
class: clsx("flex flex-col w-full items-end", className),
|
|
8
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
9
|
+
class: "max-w-[min(82%,64ch)] bg-aai-surface-faint border border-aai-border px-3 py-2 rounded-aai whitespace-pre-wrap wrap-break-word text-sm font-normal leading-[150%] text-aai-text",
|
|
10
|
+
children: message.content
|
|
11
|
+
})
|
|
12
|
+
});
|
|
13
|
+
return /* @__PURE__ */ jsx("div", {
|
|
14
|
+
class: clsx("whitespace-pre-wrap wrap-break-word text-sm font-normal leading-[150%] text-aai-text", className),
|
|
15
|
+
children: message.content
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { MessageBubble };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useAutoScroll, useSession } from "../signals.js";
|
|
2
|
+
import { MessageBubble } from "./message-bubble.js";
|
|
3
|
+
import { ThinkingIndicator } from "./thinking-indicator.js";
|
|
4
|
+
import { ToolCallBlock } from "./tool-call-block.js";
|
|
5
|
+
import { Transcript } from "./transcript.js";
|
|
6
|
+
import clsx from "clsx";
|
|
7
|
+
import { computed } from "@preact/signals";
|
|
8
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
9
|
+
//#region _components/message-list.tsx
|
|
10
|
+
/** @public */
|
|
11
|
+
function MessageList({ className }) {
|
|
12
|
+
const { session } = useSession();
|
|
13
|
+
const scrollRef = useAutoScroll();
|
|
14
|
+
const showThinking = computed(() => {
|
|
15
|
+
if (session.state.value !== "thinking") return false;
|
|
16
|
+
const last = session.toolCalls.value.at(-1);
|
|
17
|
+
if (last?.status === "pending") return false;
|
|
18
|
+
const lastMsg = session.messages.value.at(-1);
|
|
19
|
+
return !lastMsg || lastMsg.role === "user" || Boolean(last);
|
|
20
|
+
});
|
|
21
|
+
const messages = session.messages.value;
|
|
22
|
+
const toolCalls = session.toolCalls.value;
|
|
23
|
+
const items = [];
|
|
24
|
+
let tci = 0;
|
|
25
|
+
for (const [i, msg] of messages.entries()) {
|
|
26
|
+
items.push(/* @__PURE__ */ jsx(MessageBubble, { message: msg }, `msg-${i}`));
|
|
27
|
+
let tc = toolCalls[tci];
|
|
28
|
+
while (tc && tc.afterMessageIndex <= i) {
|
|
29
|
+
items.push(/* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.toolCallId));
|
|
30
|
+
tci++;
|
|
31
|
+
tc = toolCalls[tci];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let tc = toolCalls[tci];
|
|
35
|
+
while (tc) {
|
|
36
|
+
items.push(/* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.toolCallId));
|
|
37
|
+
tci++;
|
|
38
|
+
tc = toolCalls[tci];
|
|
39
|
+
}
|
|
40
|
+
return /* @__PURE__ */ jsx("div", {
|
|
41
|
+
role: "log",
|
|
42
|
+
class: clsx("flex-1 overflow-y-auto [scrollbar-width:none] bg-aai-surface", className),
|
|
43
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
44
|
+
class: "flex flex-col gap-4.5 p-4",
|
|
45
|
+
children: [
|
|
46
|
+
items,
|
|
47
|
+
session.agentUtterance.value && /* @__PURE__ */ jsx(MessageBubble, { message: {
|
|
48
|
+
role: "assistant",
|
|
49
|
+
content: session.agentUtterance.value
|
|
50
|
+
} }),
|
|
51
|
+
/* @__PURE__ */ jsx(Transcript, { userUtterance: session.userUtterance }),
|
|
52
|
+
showThinking.value && /* @__PURE__ */ jsx(ThinkingIndicator, {}),
|
|
53
|
+
/* @__PURE__ */ jsx("div", { ref: scrollRef })
|
|
54
|
+
]
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { MessageList };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentChildren } from "preact";
|
|
2
|
+
/**
|
|
3
|
+
* A two-column layout with a fixed-width sidebar and a flexible main area.
|
|
4
|
+
* Commonly used to pair a custom sidebar (cart, dashboard) with `<ChatView />`.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <SidebarLayout sidebar={<OrderPanel />}>
|
|
9
|
+
* <ChatView />
|
|
10
|
+
* </SidebarLayout>
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export declare function SidebarLayout({ sidebar, children, width, side, className, }: {
|
|
16
|
+
sidebar: ComponentChildren;
|
|
17
|
+
children: ComponentChildren;
|
|
18
|
+
width?: string | undefined;
|
|
19
|
+
side?: "left" | "right" | undefined;
|
|
20
|
+
className?: string;
|
|
21
|
+
}): import("preact").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
3
|
+
//#region _components/sidebar-layout.tsx
|
|
4
|
+
/**
|
|
5
|
+
* A two-column layout with a fixed-width sidebar and a flexible main area.
|
|
6
|
+
* Commonly used to pair a custom sidebar (cart, dashboard) with `<ChatView />`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <SidebarLayout sidebar={<OrderPanel />}>
|
|
11
|
+
* <ChatView />
|
|
12
|
+
* </SidebarLayout>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
function SidebarLayout({ sidebar, children, width = "20rem", side = "left", className }) {
|
|
18
|
+
const sidebarEl = /* @__PURE__ */ jsx("div", {
|
|
19
|
+
class: clsx("flex-shrink-0 flex flex-col overflow-y-auto", side === "left" ? "border-r border-aai-border" : "border-l border-aai-border"),
|
|
20
|
+
style: { width },
|
|
21
|
+
children: sidebar
|
|
22
|
+
});
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
24
|
+
class: clsx("flex h-screen bg-aai-bg", className),
|
|
25
|
+
children: [
|
|
26
|
+
side === "left" && sidebarEl,
|
|
27
|
+
/* @__PURE__ */ jsx("div", {
|
|
28
|
+
class: "flex-1 min-w-0",
|
|
29
|
+
children
|
|
30
|
+
}),
|
|
31
|
+
side === "right" && sidebarEl
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { SidebarLayout };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComponentChildren } from "preact";
|
|
2
|
+
/**
|
|
3
|
+
* A centered start screen with icon, title, subtitle, and a start button.
|
|
4
|
+
* Renders `children` (the main app) once the session has started.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* function MyAgent() {
|
|
9
|
+
* return (
|
|
10
|
+
* <StartScreen icon="🍕" title="Pizza Palace" subtitle="Voice-powered ordering">
|
|
11
|
+
* <ChatView />
|
|
12
|
+
* </StartScreen>
|
|
13
|
+
* );
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export declare function StartScreen({ children, icon, title, subtitle, buttonText, className, }: {
|
|
20
|
+
children: ComponentChildren;
|
|
21
|
+
icon?: ComponentChildren | undefined;
|
|
22
|
+
title?: string | undefined;
|
|
23
|
+
subtitle?: string | undefined;
|
|
24
|
+
buttonText?: string | undefined;
|
|
25
|
+
className?: string | undefined;
|
|
26
|
+
}): import("preact").JSX.Element;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useSession } from "../signals.js";
|
|
2
|
+
import { Button } from "./button.js";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
5
|
+
//#region _components/start-screen.tsx
|
|
6
|
+
/**
|
|
7
|
+
* A centered start screen with icon, title, subtitle, and a start button.
|
|
8
|
+
* Renders `children` (the main app) once the session has started.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* function MyAgent() {
|
|
13
|
+
* return (
|
|
14
|
+
* <StartScreen icon="🍕" title="Pizza Palace" subtitle="Voice-powered ordering">
|
|
15
|
+
* <ChatView />
|
|
16
|
+
* </StartScreen>
|
|
17
|
+
* );
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
function StartScreen({ children, icon, title, subtitle, buttonText = "Start", className }) {
|
|
24
|
+
const { started, start } = useSession();
|
|
25
|
+
if (started.value) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
26
|
+
return /* @__PURE__ */ jsx("div", {
|
|
27
|
+
class: clsx("flex items-center justify-center h-screen bg-aai-bg font-aai", className),
|
|
28
|
+
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",
|
|
30
|
+
children: [
|
|
31
|
+
icon,
|
|
32
|
+
title && /* @__PURE__ */ jsx("h1", {
|
|
33
|
+
class: "font-semibold text-aai-primary m-0",
|
|
34
|
+
children: title
|
|
35
|
+
}),
|
|
36
|
+
subtitle && /* @__PURE__ */ jsx("p", {
|
|
37
|
+
class: "text-sm text-aai-text-muted m-0",
|
|
38
|
+
children: subtitle
|
|
39
|
+
}),
|
|
40
|
+
/* @__PURE__ */ jsx(Button, {
|
|
41
|
+
size: "lg",
|
|
42
|
+
className: "mt-2 w-full",
|
|
43
|
+
onClick: start,
|
|
44
|
+
children: buttonText
|
|
45
|
+
})
|
|
46
|
+
]
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { StartScreen };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
3
|
+
//#region _components/state-indicator.tsx
|
|
4
|
+
/** @public */
|
|
5
|
+
function StateIndicator({ state, className }) {
|
|
6
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7
|
+
class: clsx("inline-flex items-center justify-center gap-1.5 text-[13px] font-medium leading-[130%] text-aai-text-muted capitalize", className),
|
|
8
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
9
|
+
"data-state": state.value,
|
|
10
|
+
class: "w-2 h-2 rounded-full data-[state=disconnected]:bg-aai-state-disconnected data-[state=connecting]:bg-aai-state-connecting data-[state=ready]:bg-aai-state-ready data-[state=listening]:bg-aai-state-listening data-[state=thinking]:bg-aai-state-thinking data-[state=speaking]:bg-aai-state-speaking data-[state=error]:bg-aai-state-error"
|
|
11
|
+
}), state.value]
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { StateIndicator };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { jsx } from "preact/jsx-runtime";
|
|
3
|
+
//#region _components/thinking-indicator.tsx
|
|
4
|
+
/** @public */
|
|
5
|
+
function ThinkingIndicator({ className }) {
|
|
6
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7
|
+
class: clsx("flex items-center gap-2 text-aai-text-dim text-sm font-medium min-h-5", className),
|
|
8
|
+
children: [
|
|
9
|
+
0,
|
|
10
|
+
.16,
|
|
11
|
+
.32
|
|
12
|
+
].map((delay) => /* @__PURE__ */ jsx("div", {
|
|
13
|
+
class: "w-1.5 h-1.5 rounded-full bg-aai-text-dim",
|
|
14
|
+
style: {
|
|
15
|
+
animation: "aai-bounce 1.4s infinite ease-in-out both",
|
|
16
|
+
animationDelay: `${delay}s`
|
|
17
|
+
}
|
|
18
|
+
}, delay))
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { ThinkingIndicator };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { BoltIcon, ChatBubbleIcon, DownloadIcon, ExternalLinkIcon, SearchIcon, TerminalIcon } from "./tool-icons.js";
|
|
2
|
+
import { useMemo, useState } from "preact/hooks";
|
|
3
|
+
import clsx from "clsx";
|
|
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
|
+
/** @public */
|
|
53
|
+
function ToolCallBlock({ toolCall, className }) {
|
|
54
|
+
const [isOpen, setOpen] = useState(false);
|
|
55
|
+
const config = TOOL_CONFIG[toolCall.toolName] ?? DEFAULT_CONFIG;
|
|
56
|
+
const isPending = toolCall.status === "pending";
|
|
57
|
+
const title = config.title || toolCall.toolName;
|
|
58
|
+
const canExpand = !isPending && !!toolCall.result;
|
|
59
|
+
const formatted = useMemo(() => toolCall.result ? formatResult(toolCall.result) : "", [toolCall.result]);
|
|
60
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
61
|
+
class: clsx("flex flex-col", className),
|
|
62
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
63
|
+
type: "button",
|
|
64
|
+
"aria-expanded": canExpand ? isOpen : void 0,
|
|
65
|
+
disabled: isPending,
|
|
66
|
+
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"),
|
|
67
|
+
onClick: () => canExpand && setOpen(!isOpen),
|
|
68
|
+
children: [
|
|
69
|
+
/* @__PURE__ */ jsx(config.Icon, { class: "w-4 h-4 text-aai-text-dim shrink-0" }),
|
|
70
|
+
/* @__PURE__ */ jsx("span", {
|
|
71
|
+
class: clsx("text-sm font-medium text-aai-text", isPending && "tool-shimmer"),
|
|
72
|
+
children: title
|
|
73
|
+
}),
|
|
74
|
+
/* @__PURE__ */ jsx("span", {
|
|
75
|
+
class: "text-sm text-aai-text-dim truncate flex-1 min-w-0",
|
|
76
|
+
children: config.subtitle(toolCall.args)
|
|
77
|
+
}),
|
|
78
|
+
canExpand && /* @__PURE__ */ jsx("span", {
|
|
79
|
+
class: "text-xs text-aai-text-dim shrink-0",
|
|
80
|
+
children: isOpen ? "▾" : "▸"
|
|
81
|
+
})
|
|
82
|
+
]
|
|
83
|
+
}), isOpen && /* @__PURE__ */ jsxs("div", {
|
|
84
|
+
class: "border-x border-b border-aai-border rounded-b-aai bg-aai-surface max-h-64 overflow-auto",
|
|
85
|
+
children: [toolCall.toolName === "run_code" && toolCall.args.code && /* @__PURE__ */ jsx("pre", {
|
|
86
|
+
class: "text-xs text-aai-text p-2 whitespace-pre-wrap border-b border-aai-border font-mono",
|
|
87
|
+
children: String(toolCall.args.code)
|
|
88
|
+
}), formatted && /* @__PURE__ */ jsx("pre", {
|
|
89
|
+
class: "text-xs text-aai-text-dim p-2 whitespace-pre-wrap",
|
|
90
|
+
children: formatted
|
|
91
|
+
})]
|
|
92
|
+
})]
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { ToolCallBlock };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type * as preact from "preact";
|
|
2
|
+
type IconProps = {
|
|
3
|
+
class?: string;
|
|
4
|
+
};
|
|
5
|
+
/** Magnifying glass icon for web_search. */
|
|
6
|
+
export declare function SearchIcon(props: IconProps): preact.JSX.Element;
|
|
7
|
+
/** External link icon for visit_webpage. */
|
|
8
|
+
export declare function ExternalLinkIcon(props: IconProps): preact.JSX.Element;
|
|
9
|
+
/** Terminal icon for run_code. */
|
|
10
|
+
export declare function TerminalIcon(props: IconProps): preact.JSX.Element;
|
|
11
|
+
/** Download icon for fetch_json. */
|
|
12
|
+
export declare function DownloadIcon(props: IconProps): preact.JSX.Element;
|
|
13
|
+
/** Chat bubble icon for user_input. */
|
|
14
|
+
export declare function ChatBubbleIcon(props: IconProps): preact.JSX.Element;
|
|
15
|
+
/** Bolt/lightning icon for default/unknown tools. */
|
|
16
|
+
export declare function BoltIcon(props: IconProps): preact.JSX.Element;
|
|
17
|
+
export {};
|