@aichatkit/ui
Version:
Composable chat UI components for AI powered apps
952 lines (941 loc) • 35 kB
JavaScript
// src/components/button/button.tsx
import { Slot } from "@radix-ui/react-slot";
import { cva } from "class-variance-authority";
import { forwardRef } from "react";
// src/utils/cn.ts
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
function cn(...inputs) {
return twMerge(clsx(inputs));
}
// src/components/button/button.tsx
import { jsx } from "react/jsx-runtime";
var buttonVariants = cva(
"inline-flex items-center whitespace-nowrap rounded-lg font-medium text-sm transition-colors disabled:pointer-events-none disabled:[&_svg]:fill-icon-disabled",
{
variants: {
variant: {
primary: "justify-center bg-button-surface-primary text-text-button-primary ring-offset-primary-200 hover:bg-primary-400 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-primary-200 focus-visible:ring-offset-0 active:bg-primary-600 disabled:bg-button-surface-disabled disabled:text-text-disabled dark:active:bg-primary-400 dark:focus-visible:ring-primary-700 dark:hover:bg-primary-600 [&_svg]:fill-icon-button-primary",
secondary: "[&_svg]:icon-fill-text-button-secondary justify-center text-text-button-secondary outline outline-1 outline-button-border-primary hover:bg-primary-25 focus-visible:ring-2 focus-visible:ring-primary-200 focus-visible:ring-primary-200 active:bg-primary-50 disabled:text-text-disabled disabled:outline-border-disabled dark:active:bg-primary-850 dark:focus-visible:ring-primary-700 dark:hover:bg-primary-900",
ghost: "justify-center text-text-action hover:bg-primary-25 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-primary-200 focus-visible:ring-offset-0 active:bg-primary-50 disabled:text-text-disabled dark:active:bg-primary-850 dark:active:text-primary-50 dark:focus-visible:ring-primary-700 dark:hover:bg-primary-900 dark:hover:text-primary-50",
warning: "justify-center bg-warning-400 text-neutral-0 hover:bg-warning focus-visible:ring-2 focus-visible:ring-warning-300 active:bg-warning-400 active:outline-error-600 disabled:cursor-not-allowed disabled:text-text-disabled dark:active:bg-error-600 dark:active:outline-error-400 dark:focus-visible:ring-warning-700 dark:hover:bg-warning-700",
warning_outline: "justify-center text-warning outline outline-1 outline-button-border-warning hover:bg-warning-100 focus-visible:ring-2 focus-visible:ring-warning-300 active:bg-warning-400 active:outline-warning-600 disabled:cursor-not-allowed disabled:text-text-disabled dark:active:bg-warning-600 dark:active:outline-warning-400 dark:focus-visible:ring-warning-700 dark:hover:bg-warning-700",
destructive: "justify-center bg-button-border-error text-neutral-0 hover:bg-error-400 focus-visible:ring-2 focus-visible:ring-error-300 active:bg-error-400 active:outline-error-600 disabled:cursor-not-allowed disabled:text-text-disabled dark:active:bg-error-600 dark:active:outline-error-400 dark:focus-visible:ring-error-700 dark:hover:bg-error-700",
destructive_outline: "justify-center text-button-text-error outline outline-1 outline-button-border-error hover:bg-error-100 focus-visible:ring-2 focus-visible:ring-error-300 active:bg-error-400 active:outline-error-600 disabled:cursor-not-allowed disabled:text-text-disabled dark:active:bg-error-600 dark:active:outline-error-400 dark:focus-visible:ring-error-700 dark:hover:bg-error-700",
input: "flex h-8 justify-between rounded-lg border border-form-border-primary bg-form-surface-primary p-2 text-left text-sm ring-none ring-inset transition-colors file:font-medium file:text-sm placeholder:text-text-secondary hover:bg-neutral-25 focus-visible:border-border-action focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-primary-200 disabled:cursor-not-allowed disabled:border-form-border-disabled disabled:bg-form-surface-disabled disabled:text-text-disabled dark:focus-visible:ring-primary-700 dark:hover:bg-form-surface-disabled dark:hover:bg-neutral-800 focus-visible:[&_svg]:fill-icon-tertiary disabled:[&_svg]:fill-icon-disabled"
},
size: {
default: "h-8 px-4",
icon: "h-8 px-1.5",
sm: "h-6 px-3 text-xs",
lg: "h-10 px-5"
}
},
defaultVariants: {
variant: "primary",
size: "default"
}
}
);
var Button = forwardRef(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return /* @__PURE__ */ jsx(
Comp,
{
className: cn(buttonVariants({ variant, size, className })),
ref,
...props
}
);
}
);
Button.displayName = "Button";
// src/components/input/input.tsx
import { forwardRef as forwardRef2 } from "react";
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
var Input = forwardRef2(
({ className, type, ...props }, ref) => {
return /* @__PURE__ */ jsx2(
"input",
{
type,
className: cn(
"flex h-8 w-full rounded-lg border border-form-border-primary bg-form-surface-primary p-2 text-sm ring-none ring-inset transition-colors file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-text-secondary hover:bg-neutral-25 focus-visible:border-border-action focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-primary-200 disabled:cursor-not-allowed disabled:border-form-border-disabled disabled:bg-form-surface-disabled disabled:text-text-disabled dark:focus-visible:ring-primary-700 dark:hover:bg-form-surface-disabled dark:hover:bg-neutral-800 focus-visible:[&_svg]:fill-icon-tertiary disabled:[&_svg]:fill-icon-disabled",
className
),
ref,
...props
}
);
}
);
Input.displayName = "Input";
var DefaultHypermodeInputArea = ({
input,
setInput,
loading,
onSubmit,
inputPlaceholder,
sendButtonIcon,
inputAreaClassName
}) => /* @__PURE__ */ jsx2(
"form",
{
onSubmit,
className: cn("border-hypermode-border border-t p-4", inputAreaClassName),
children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
/* @__PURE__ */ jsx2(
Input,
{
type: "text",
value: input,
onChange: (e) => setInput(e.target.value),
placeholder: inputPlaceholder,
className: "w-full rounded-md border border-hypermode-border bg-hypermode-input px-4 py-2 pr-10 text-white placeholder-neutral-500 focus:border-hypermode-accent focus:ring-hypermode-accent"
}
),
/* @__PURE__ */ jsx2(
Button,
{
type: "submit",
disabled: loading || !input.trim(),
className: "-translate-y-1/2 absolute top-1/2 right-2 transform rounded-md p-1.5 text-hypermode-accent hover:bg-hypermode-hover disabled:cursor-not-allowed disabled:opacity-50",
children: typeof sendButtonIcon === "string" ? sendButtonIcon : sendButtonIcon
}
)
] })
}
);
var DefaultStandardInputArea = ({
input,
setInput,
loading,
onSubmit,
inputPlaceholder,
sendButtonIcon,
inputAreaClassName
}) => /* @__PURE__ */ jsx2(
"form",
{
onSubmit,
className: cn(
"border-border-primary border-t bg-white p-4",
inputAreaClassName
),
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx2(
Input,
{
type: "text",
value: input,
onChange: (e) => setInput(e.target.value),
placeholder: inputPlaceholder,
className: "flex-1"
}
),
/* @__PURE__ */ jsx2(Button, { type: "submit", disabled: loading, children: typeof sendButtonIcon === "string" ? sendButtonIcon : sendButtonIcon })
] })
}
);
// src/components/chat/chat-bubble.tsx
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
function ChatBubble({
content,
role,
timestamp,
avatar,
className,
bubbleClassName,
hypermodeStyle = false,
...props
}) {
const defaultAvatar = role === "assistant" ? /* @__PURE__ */ jsx3("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-hypermode-accent text-sm text-white", children: "A" }) : /* @__PURE__ */ jsx3("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-hypermode-accent-light text-sm text-white", children: "U" });
const safeTimestamp = timestamp ? timestamp instanceof Date ? timestamp.toISOString() : String(timestamp) : void 0;
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs2("div", { className: cn("flex items-start gap-3", className), ...props, children: [
avatar || defaultAvatar,
/* @__PURE__ */ jsxs2("div", { className: "flex max-w-[85%] flex-col", children: [
/* @__PURE__ */ jsx3(
"div",
{
className: cn(
"whitespace-pre-wrap break-words rounded-lg px-4 py-2",
role === "user" ? "bg-hypermode-accent text-white" : "bg-hypermode-card text-white",
bubbleClassName
),
children: content
}
),
safeTimestamp && /* @__PURE__ */ jsx3("span", { className: "mt-1 ml-1 text-neutral-500 text-xs", children: safeTimestamp })
] })
] });
}
return /* @__PURE__ */ jsx3(
"div",
{
className: cn(
"flex",
role === "user" ? "justify-end" : "justify-start",
className
),
...props,
children: /* @__PURE__ */ jsxs2(
"div",
{
className: cn(
"relative max-w-[85%] px-4 py-2",
role === "user" ? "rounded-[1.15rem] rounded-br-none bg-primary-500 text-text-inverse" : "rounded-[1.15rem] rounded-bl-none bg-neutral-750 text-text-primary",
"whitespace-pre-wrap break-words",
bubbleClassName
),
children: [
content,
safeTimestamp && /* @__PURE__ */ jsx3("span", { className: "mt-1 block text-xs opacity-70", children: safeTimestamp })
]
}
)
}
);
}
ChatBubble.displayName = "ChatBubble";
// src/components/chat/chat-interface.tsx
import { useEffect, useRef, useState } from "react";
// src/components/chat/loading-indicator.tsx
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
function LoadingDot({ delay, className, ...props }) {
return /* @__PURE__ */ jsx4(
"div",
{
className: cn("h-2 w-2 rounded-full bg-neutral-600", className),
style: {
animation: `loadingDotBounce 0.6s infinite ease-in-out ${delay}s`
},
...props
}
);
}
function LoadingIndicator({
dots = 3,
className,
dotClassName,
...props
}) {
return /* @__PURE__ */ jsx4("div", { className: cn("flex space-x-1", className), ...props, children: Array.from({ length: dots }).map((_, index) => /* @__PURE__ */ jsx4(LoadingDot, { delay: index * 0.2, className: dotClassName }, index)) });
}
function LoadingChatBubble({
className,
bubbleClassName,
indicatorClassName,
hypermodeStyle = false,
avatar,
...props
}) {
const defaultAvatar = /* @__PURE__ */ jsx4("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-hypermode-accent text-sm text-white", children: "A" });
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs3(
"div",
{
className: cn("flex items-start gap-3", className),
style: {
animation: "fadeIn 0.3s ease-out"
},
...props,
children: [
avatar || defaultAvatar,
/* @__PURE__ */ jsx4(
"div",
{
className: cn(
"rounded-lg bg-hypermode-card px-4 py-3",
bubbleClassName
),
children: /* @__PURE__ */ jsx4(
LoadingIndicator,
{
className: indicatorClassName,
dotClassName: "bg-neutral-500"
}
)
}
)
]
}
);
}
return /* @__PURE__ */ jsx4(
"div",
{
className: cn("flex justify-start", className),
style: {
animation: "fadeIn 0.3s ease-out"
},
...props,
children: /* @__PURE__ */ jsx4(
"div",
{
className: cn(
"relative max-w-[85%] rounded-[1.15rem] rounded-bl-none bg-neutral-100 px-4 py-3 text-text-primary",
bubbleClassName
),
children: /* @__PURE__ */ jsx4(LoadingIndicator, { className: indicatorClassName })
}
)
}
);
}
LoadingChatBubble.displayName = "LoadingChatBubble";
// src/components/avatar/avatar.tsx
import { jsx as jsx5 } from "react/jsx-runtime";
function Avatar({
initial,
role = "assistant",
className,
size = "md",
hypermodeStyle = false,
...props
}) {
const sizeClasses = {
sm: "w-6 h-6 text-xs",
md: "w-8 h-8 text-sm",
lg: "w-10 h-10 text-base"
};
if (hypermodeStyle) {
return /* @__PURE__ */ jsx5(
"div",
{
className: cn(
"flex items-center justify-center rounded-full text-white",
sizeClasses[size],
role === "user" ? "bg-hypermode-accent-light" : "bg-hypermode-accent",
className
),
...props,
children: initial
}
);
}
return /* @__PURE__ */ jsx5(
"div",
{
className: cn(
"flex items-center justify-center rounded-full text-white",
sizeClasses[size],
role === "user" ? "bg-primary-300" : "bg-primary-500",
className
),
...props,
children: initial
}
);
}
Avatar.displayName = "Avatar";
// src/components/chat/sidebar.tsx
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
function ChatSidebar({
title = "Chat",
conversations = [],
currentConversationId,
onCreateConversation,
onDeleteConversation,
onSelectConversation,
username = "User",
className,
headerClassName,
conversationsClassName,
profileClassName,
headerContent,
profileContent,
newConversationIcon = "+",
deleteConversationIcon = "\xD7",
settingsIcon,
hypermodeStyle = false
}) {
const defaultHypermodeHeader = /* @__PURE__ */ jsxs4(
"div",
{
className: cn(
"flex items-center justify-between border-hypermode-border border-b p-3",
headerClassName
),
children: [
/* @__PURE__ */ jsx6("div", { className: "font-semibold text-lg", children: title }),
settingsIcon && /* @__PURE__ */ jsx6(
Button,
{
variant: "ghost",
size: "icon",
className: "text-neutral-400 hover:bg-hypermode-hover hover:text-white",
children: settingsIcon
}
)
]
}
);
const defaultHypermodeProfile = /* @__PURE__ */ jsxs4(
"div",
{
className: cn(
"flex items-center justify-between border-hypermode-border border-t p-3",
profileClassName
),
children: [
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx6(
Avatar,
{
initial: username.charAt(0),
role: "user",
hypermodeStyle: true
}
),
/* @__PURE__ */ jsx6("div", { className: "text-sm", children: username })
] }),
settingsIcon && /* @__PURE__ */ jsx6(
Button,
{
variant: "ghost",
size: "icon",
className: "text-neutral-400 hover:bg-hypermode-hover hover:text-white",
children: settingsIcon
}
)
]
}
);
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs4(
"div",
{
className: cn(
"flex h-full w-64 flex-col border-hypermode-border border-r bg-hypermode-card",
className
),
children: [
headerContent || defaultHypermodeHeader,
/* @__PURE__ */ jsx6("div", { className: "p-3", children: /* @__PURE__ */ jsxs4(
Button,
{
onClick: onCreateConversation,
className: "flex w-full items-center justify-center gap-2 bg-hypermode-accent text-white hover:bg-hypermode-accent-light",
children: [
typeof newConversationIcon === "string" ? newConversationIcon : newConversationIcon,
/* @__PURE__ */ jsx6("span", { children: "New Conversation" })
]
}
) }),
/* @__PURE__ */ jsxs4("div", { className: cn("flex-1 overflow-y-auto", conversationsClassName), children: [
/* @__PURE__ */ jsx6("div", { className: "hypermode-sidebar-title", children: "Conversations" }),
/* @__PURE__ */ jsx6("div", { className: "space-y-1 px-2", children: conversations.map((conv) => /* @__PURE__ */ jsxs4("div", { className: "group flex items-center", children: [
/* @__PURE__ */ jsx6(
"button",
{
onClick: () => onSelectConversation(conv.id),
className: cn(
"hypermode-sidebar-item flex-1",
conv.id === currentConversationId ? "active" : ""
),
children: conv.title
}
),
/* @__PURE__ */ jsx6(
Button,
{
variant: "ghost",
size: "icon",
onClick: (e) => {
e.stopPropagation();
onDeleteConversation(conv.id);
},
className: "p-1 text-neutral-400 opacity-0 hover:text-white group-hover:opacity-100",
"aria-label": "Delete conversation",
children: typeof deleteConversationIcon === "string" ? deleteConversationIcon : deleteConversationIcon
}
)
] }, conv.id)) })
] }),
profileContent || defaultHypermodeProfile
]
}
);
}
return /* @__PURE__ */ jsxs4(
"div",
{
className: cn(
"h-full w-64 border-border-primary border-r bg-modal-surface-body-primary p-4",
className
),
children: [
/* @__PURE__ */ jsxs4(
Button,
{
onClick: onCreateConversation,
className: "mb-4 flex w-full items-center justify-center gap-2",
children: [
typeof newConversationIcon === "string" ? newConversationIcon : newConversationIcon,
"New Conversation"
]
}
),
/* @__PURE__ */ jsx6("div", { className: "flex-1 space-y-2 overflow-y-auto", children: conversations.map((conv) => /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx6(
"button",
{
onClick: () => onSelectConversation(conv.id),
className: cn(
"flex-1 rounded-lg p-2 text-left transition-colors",
conv.id === currentConversationId ? "bg-primary-300 text-text-inverse" : "hover:bg-neutral-100"
),
children: conv.title
}
),
/* @__PURE__ */ jsx6(
Button,
{
variant: "ghost",
size: "icon",
onClick: (e) => {
e.stopPropagation();
onDeleteConversation(conv.id);
},
className: "opacity-50 hover:opacity-100",
children: typeof deleteConversationIcon === "string" ? deleteConversationIcon : deleteConversationIcon
}
)
] }, conv.id)) }),
profileContent || /* @__PURE__ */ jsxs4("div", { className: "mt-4 flex items-center gap-2 border-border-primary border-t pt-4", children: [
/* @__PURE__ */ jsx6(Avatar, { initial: username.charAt(0), role: "user" }),
/* @__PURE__ */ jsx6("div", { className: "font-medium text-sm", children: username })
] })
]
}
);
}
ChatSidebar.displayName = "ChatSidebar";
// src/components/chat/chat-interface.tsx
import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
function ChatInterface({
networkAdapter,
storageAdapter,
showSidebar = true,
showInputArea = true,
className,
chatAreaClassName,
sidebarClassName,
inputAreaClassName,
headerClassName,
headerContent,
sidebarHeaderContent,
userProfileContent,
inputAreaContent,
username = "User",
inputPlaceholder = "Type a message...",
sendButtonIcon = "\u2192",
newConversationIcon = "+",
deleteConversationIcon = "\xD7",
hypermodeStyle = false,
userAvatar,
assistantAvatar
}) {
const [conversations, setConversations] = useState([]);
const [currentConversationId, setCurrentConversationId] = useState("");
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [initialized, setInitialized] = useState(false);
const messagesEndRef = useRef(null);
const currentConversation = conversations.find(
(c) => c.id === currentConversationId
);
const messages = (currentConversation == null ? void 0 : currentConversation.messages) || [];
useEffect(() => {
if (initialized) return;
const loadConversations = async () => {
if (storageAdapter) {
try {
if ("syncAllConversationsWithBackend" in storageAdapter) {
console.log("Syncing all conversations with backend on app load...");
await storageAdapter.syncAllConversationsWithBackend();
}
const loadedConversations = await storageAdapter.getAllConversations();
if (loadedConversations.length > 0) {
const uniqueConversations = loadedConversations.filter(
(conv, index, arr) => arr.findIndex((c) => c.id === conv.id) === index
);
setConversations(uniqueConversations);
let activeId = "";
if ("getActiveConversationId" in storageAdapter) {
activeId = storageAdapter.getActiveConversationId();
}
if (activeId && uniqueConversations.some((c) => c.id === activeId)) {
setCurrentConversationId(activeId);
} else {
setCurrentConversationId(uniqueConversations[0].id);
if ("setActiveConversationId" in storageAdapter) {
;
storageAdapter.setActiveConversationId(
uniqueConversations[0].id
);
}
}
}
} catch (error) {
console.error("Error loading conversations:", error);
} finally {
setInitialized(true);
}
} else {
setInitialized(true);
}
};
loadConversations();
}, [storageAdapter, initialized]);
useEffect(() => {
if (storageAdapter && "setActiveConversationId" in storageAdapter && currentConversationId) {
;
storageAdapter.setActiveConversationId(currentConversationId);
const syncCurrentConversation = async () => {
if (currentConversationId && storageAdapter) {
try {
console.log(
`Syncing conversation ${currentConversationId} with backend...`
);
const latestMessages = await storageAdapter.getConversationHistory(
currentConversationId
);
setConversations(
(prev) => prev.map(
(conv) => conv.id === currentConversationId ? { ...conv, messages: latestMessages } : conv
)
);
} catch (error) {
console.error(
`Failed to sync conversation ${currentConversationId}:`,
error
);
}
}
};
syncCurrentConversation();
}
}, [currentConversationId, storageAdapter]);
const scrollToBottom = () => {
var _a;
(_a = messagesEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
};
useEffect(scrollToBottom, [messages]);
const createNewConversation = async () => {
if (!networkAdapter || !storageAdapter) {
console.error("Missing required adapters");
return;
}
try {
const agentId = await networkAdapter.startChatAgent();
const conversationId = `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const newConversation = {
id: conversationId,
title: `Conversation ${conversations.length + 1}`,
messages: []
};
await storageAdapter.saveConversation(newConversation);
await storageAdapter.setConversationAgent(conversationId, agentId);
setConversations((prev) => {
if (prev.some((c) => c.id === conversationId)) {
return prev;
}
return [...prev, newConversation];
});
setCurrentConversationId(conversationId);
return newConversation;
} catch (error) {
console.error("Error creating new conversation:", error);
}
};
const deleteConversation = async (id) => {
if (!storageAdapter) return;
try {
if (networkAdapter) {
const agentId = await storageAdapter.getConversationAgent(id);
if (agentId) {
await networkAdapter.stopChatAgent(agentId);
}
}
await storageAdapter.deleteConversation(id);
setConversations((prev) => prev.filter((conv) => conv.id !== id));
if (id === currentConversationId) {
const remainingConvs = conversations.filter((conv) => conv.id !== id);
if (remainingConvs.length > 0) {
setCurrentConversationId(remainingConvs[0].id);
} else {
setCurrentConversationId("");
}
}
} catch (error) {
console.error("Error deleting conversation:", error);
}
};
const sendMessage = async (messageText) => {
if (!messageText.trim() || !networkAdapter || !storageAdapter || !currentConversationId)
return;
const userMessage = {
id: Date.now().toString(),
content: messageText,
role: "user",
timestamp: (/* @__PURE__ */ new Date()).toString()
};
setConversations(
(prev) => prev.map(
(conv) => conv.id === currentConversationId ? {
...conv,
messages: [...conv.messages, userMessage]
} : conv
)
);
await storageAdapter.addMessage(currentConversationId, userMessage);
setLoading(true);
try {
const agentId = await storageAdapter.getConversationAgent(
currentConversationId
);
if (!agentId) {
throw new Error("No agent found for conversation");
}
const responseMessage = await networkAdapter.sendMessage(
agentId,
messageText
);
setConversations(
(prev) => prev.map(
(conv) => conv.id === currentConversationId ? {
...conv,
messages: [...conv.messages, responseMessage]
} : conv
)
);
await storageAdapter.addMessage(currentConversationId, responseMessage);
} catch (error) {
console.error("Error sending message:", error);
const errorMessage = {
id: Date.now().toString(),
content: "Sorry, there was an error sending your message.",
role: "assistant",
timestamp: (/* @__PURE__ */ new Date()).toString()
};
setConversations(
(prev) => prev.map(
(conv) => conv.id === currentConversationId ? {
...conv,
messages: [...conv.messages, errorMessage]
} : conv
)
);
await storageAdapter.addMessage(currentConversationId, errorMessage);
} finally {
setLoading(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
await sendMessage(input);
setInput("");
};
const inputAreaContext = {
input,
setInput,
loading,
sendMessage,
currentConversationId,
inputPlaceholder,
sendButtonIcon
};
const EmptyState = () => /* @__PURE__ */ jsxs5("div", { className: "flex flex-1 flex-col items-center justify-center p-8 text-center", children: [
/* @__PURE__ */ jsx7("div", { className: "mb-4 text-neutral-400", children: hypermodeStyle ? /* @__PURE__ */ jsx7("div", { className: "mb-4 text-6xl", children: "\u{1F4AC}" }) : /* @__PURE__ */ jsx7("div", { className: "mb-4 text-6xl", children: "\u{1F4AC}" }) }),
/* @__PURE__ */ jsx7(
"h2",
{
className: cn(
"mb-2 font-medium text-xl",
hypermodeStyle ? "text-white" : "text-text-primary"
),
children: "Welcome to Chat"
}
),
/* @__PURE__ */ jsx7(
"p",
{
className: cn(
"mb-6 max-w-md",
hypermodeStyle ? "text-neutral-400" : "text-text-secondary"
),
children: "Start a new conversation to begin chatting with the AI assistant."
}
),
/* @__PURE__ */ jsxs5(
Button,
{
onClick: createNewConversation,
className: cn(
"flex items-center gap-2",
hypermodeStyle ? "bg-hypermode-accent hover:bg-hypermode-accent-light" : ""
),
children: [
typeof newConversationIcon === "string" ? newConversationIcon : newConversationIcon,
"Start New Conversation"
]
}
)
] });
const defaultHypermodeHeader = /* @__PURE__ */ jsx7("div", { className: "flex items-center justify-between border-hypermode-border border-b p-3", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center", children: [
assistantAvatar || /* @__PURE__ */ jsx7("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-hypermode-accent text-sm text-white", children: "A" }),
/* @__PURE__ */ jsx7("span", { className: "ml-2 font-medium", children: "Assistant" })
] }) });
const renderDefaultInputArea = () => {
if (hypermodeStyle) {
return /* @__PURE__ */ jsx7(
DefaultHypermodeInputArea,
{
input,
setInput,
loading,
onSubmit: handleSubmit,
inputPlaceholder,
sendButtonIcon,
inputAreaClassName
}
);
} else {
return /* @__PURE__ */ jsx7(
DefaultStandardInputArea,
{
input,
setInput,
loading,
onSubmit: handleSubmit,
inputPlaceholder,
sendButtonIcon,
inputAreaClassName
}
);
}
};
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs5("div", { className: cn("flex h-full bg-hypermode-bg text-white", className), children: [
showSidebar && /* @__PURE__ */ jsx7(
ChatSidebar,
{
conversations,
currentConversationId,
onCreateConversation: createNewConversation,
onDeleteConversation: deleteConversation,
onSelectConversation: setCurrentConversationId,
username,
className: sidebarClassName,
headerContent: sidebarHeaderContent,
profileContent: userProfileContent,
newConversationIcon,
deleteConversationIcon,
hypermodeStyle: true
}
),
/* @__PURE__ */ jsxs5(
"div",
{
className: cn(
"flex flex-1 flex-col bg-hypermode-bg",
chatAreaClassName
),
children: [
headerContent || defaultHypermodeHeader,
!currentConversationId ? /* @__PURE__ */ jsx7(EmptyState, {}) : /* @__PURE__ */ jsxs5(Fragment, { children: [
/* @__PURE__ */ jsxs5("div", { className: cn("flex-1 space-y-6 overflow-y-auto p-4"), children: [
messages.map((message) => /* @__PURE__ */ jsx7(
ChatBubble,
{
content: message.content,
role: message.role,
timestamp: message.timestamp,
hypermodeStyle: true,
avatar: message.role === "user" ? userAvatar : assistantAvatar
},
message.id
)),
loading && /* @__PURE__ */ jsx7(
LoadingChatBubble,
{
hypermodeStyle: true,
avatar: assistantAvatar
}
),
/* @__PURE__ */ jsx7("div", { ref: messagesEndRef })
] }),
showInputArea && (inputAreaContent ? (
// Handle both ReactNode and function types
typeof inputAreaContent === "function" ? inputAreaContent(inputAreaContext) : inputAreaContent
) : renderDefaultInputArea())
] })
]
}
)
] });
}
return /* @__PURE__ */ jsxs5("div", { className: cn("flex h-full bg-white", className), children: [
showSidebar && /* @__PURE__ */ jsx7(
ChatSidebar,
{
conversations,
currentConversationId,
onCreateConversation: createNewConversation,
onDeleteConversation: deleteConversation,
onSelectConversation: setCurrentConversationId,
username,
className: sidebarClassName,
headerContent: sidebarHeaderContent,
profileContent: userProfileContent,
newConversationIcon,
deleteConversationIcon,
hypermodeStyle: false
}
),
/* @__PURE__ */ jsxs5("div", { className: cn("flex flex-1 flex-col bg-white", chatAreaClassName), children: [
/* @__PURE__ */ jsx7("div", { className: cn("bg-white p-4 shadow-xs", headerClassName), children: headerContent }),
!currentConversationId ? /* @__PURE__ */ jsx7(EmptyState, {}) : /* @__PURE__ */ jsxs5(Fragment, { children: [
/* @__PURE__ */ jsxs5("div", { className: "flex-1 space-y-4 overflow-y-auto bg-white p-4", children: [
messages.map((message) => /* @__PURE__ */ jsx7(
ChatBubble,
{
content: message.content,
role: message.role,
timestamp: message.timestamp,
avatar: message.role === "user" ? userAvatar : assistantAvatar
},
message.id
)),
loading && /* @__PURE__ */ jsx7(LoadingChatBubble, {}),
/* @__PURE__ */ jsx7("div", { ref: messagesEndRef })
] }),
showInputArea && (inputAreaContent ? (
// Handle both ReactNode and function types
typeof inputAreaContent === "function" ? inputAreaContent(inputAreaContext) : inputAreaContent
) : renderDefaultInputArea())
] })
] })
] });
}
ChatInterface.displayName = "ChatInterface";
// src/index.ts
var CHATKIT_CSS_PATH = "./styles/base.css";
export {
Avatar,
Button,
CHATKIT_CSS_PATH,
ChatBubble,
ChatInterface,
ChatSidebar,
DefaultHypermodeInputArea,
DefaultStandardInputArea,
Input,
LoadingChatBubble,
LoadingDot,
LoadingIndicator,
cn
};