@aichatkit/ui
Version:
Composable chat UI components for AI powered apps
1,247 lines (1,234 loc) • 46.2 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/chat/tool-call.tsx
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
function ToolCallDisplay({
toolCall,
className,
hypermodeStyle = false
}) {
const getStatusColor = (status) => {
switch (status) {
case "pending":
return hypermodeStyle ? "text-yellow-400" : "text-yellow-600";
case "executing":
return hypermodeStyle ? "text-blue-400" : "text-blue-600";
case "completed":
return hypermodeStyle ? "text-green-400" : "text-green-600";
case "error":
return hypermodeStyle ? "text-red-400" : "text-red-600";
default:
return hypermodeStyle ? "text-neutral-400" : "text-neutral-600";
}
};
const getStatusIcon = (status) => {
switch (status) {
case "pending":
return "\u23F3";
case "executing":
return "\u26A1";
case "completed":
return "\u2705";
case "error":
return "\u274C";
default:
return "\u{1F527}";
}
};
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs4(
"div",
{
className: cn(
"rounded-lg border border-hypermode-border bg-hypermode-card p-4",
className
),
children: [
/* @__PURE__ */ jsxs4("div", { className: "mb-2 flex items-center gap-2", children: [
/* @__PURE__ */ jsx5("span", { className: "text-lg", children: getStatusIcon(toolCall.status) }),
/* @__PURE__ */ jsx5("span", { className: "font-mono text-hypermode-accent text-sm", children: toolCall.name }),
/* @__PURE__ */ jsx5(
"span",
{
className: cn(
"font-semibold text-xs",
getStatusColor(toolCall.status)
),
children: toolCall.status.toUpperCase()
}
)
] }),
Object.keys(toolCall.arguments).length > 0 && /* @__PURE__ */ jsxs4("div", { className: "mb-2", children: [
/* @__PURE__ */ jsx5("div", { className: "mb-1 text-neutral-400 text-xs", children: "Arguments:" }),
/* @__PURE__ */ jsx5("pre", { className: "overflow-x-auto rounded bg-hypermode-bg p-2 text-neutral-300 text-xs", children: JSON.stringify(toolCall.arguments, null, 2) })
] }),
toolCall.result && /* @__PURE__ */ jsxs4("div", { className: "mb-2", children: [
/* @__PURE__ */ jsx5("div", { className: "mb-1 text-neutral-400 text-xs", children: "Result:" }),
/* @__PURE__ */ jsx5("div", { className: "rounded bg-hypermode-hover p-2 text-sm text-white", children: typeof toolCall.result === "string" ? toolCall.result : JSON.stringify(toolCall.result, null, 2) })
] }),
toolCall.error && /* @__PURE__ */ jsxs4("div", { children: [
/* @__PURE__ */ jsx5("div", { className: "mb-1 text-red-400 text-xs", children: "Error:" }),
/* @__PURE__ */ jsx5("div", { className: "rounded border border-red-800 bg-red-900/20 p-2 text-red-400 text-sm", children: toolCall.error })
] })
]
}
);
}
return /* @__PURE__ */ jsxs4(
"div",
{
className: cn(
"rounded-lg border border-neutral-200 bg-neutral-50 p-4",
className
),
children: [
/* @__PURE__ */ jsxs4("div", { className: "mb-2 flex items-center gap-2", children: [
/* @__PURE__ */ jsx5("span", { className: "text-lg", children: getStatusIcon(toolCall.status) }),
/* @__PURE__ */ jsx5("span", { className: "font-mono text-primary-600 text-sm", children: toolCall.name }),
/* @__PURE__ */ jsx5(
"span",
{
className: cn(
"font-semibold text-xs",
getStatusColor(toolCall.status)
),
children: toolCall.status.toUpperCase()
}
)
] }),
Object.keys(toolCall.arguments).length > 0 && /* @__PURE__ */ jsxs4("div", { className: "mb-2", children: [
/* @__PURE__ */ jsx5("div", { className: "mb-1 text-neutral-600 text-xs", children: "Arguments:" }),
/* @__PURE__ */ jsx5("pre", { className: "overflow-x-auto rounded border bg-white p-2 text-neutral-800 text-xs", children: JSON.stringify(toolCall.arguments, null, 2) })
] }),
toolCall.result && /* @__PURE__ */ jsxs4("div", { className: "mb-2", children: [
/* @__PURE__ */ jsx5("div", { className: "mb-1 text-neutral-600 text-xs", children: "Result:" }),
/* @__PURE__ */ jsx5("div", { className: "rounded border bg-white p-2 text-neutral-800 text-sm", children: typeof toolCall.result === "string" ? toolCall.result : JSON.stringify(toolCall.result, null, 2) })
] }),
toolCall.error && /* @__PURE__ */ jsxs4("div", { children: [
/* @__PURE__ */ jsx5("div", { className: "mb-1 text-red-600 text-xs", children: "Error:" }),
/* @__PURE__ */ jsx5("div", { className: "rounded border border-red-200 bg-red-50 p-2 text-red-800 text-sm", children: toolCall.error })
] })
]
}
);
}
function CardDisplay({
card,
onAction,
className,
hypermodeStyle = false
}) {
const handleAction = (action) => {
if (onAction) {
onAction(action);
} else if (action.type === "link" && action.action) {
window.open(action.action, "_blank");
}
};
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs4(
"div",
{
className: cn(
"rounded-lg border border-hypermode-border bg-hypermode-card p-4",
className
),
children: [
card.title && /* @__PURE__ */ jsx5("h3", { className: "mb-3 font-semibold text-white", children: card.title }),
/* @__PURE__ */ jsx5("div", { className: "space-y-3", children: Object.entries(card.content).map(([key, value]) => /* @__PURE__ */ jsxs4("div", { children: [
/* @__PURE__ */ jsxs4("div", { className: "mb-1 text-neutral-400 text-xs capitalize", children: [
key.replace(/_/g, " "),
":"
] }),
/* @__PURE__ */ jsx5("div", { className: "text-sm text-white", children: typeof value === "string" ? value : JSON.stringify(value) })
] }, key)) }),
card.actions && card.actions.length > 0 && /* @__PURE__ */ jsx5("div", { className: "mt-4 flex gap-2", children: card.actions.map((action) => /* @__PURE__ */ jsx5(
Button,
{
size: "sm",
variant: action.type === "button" ? "primary" : "secondary",
onClick: () => handleAction(action),
className: cn(
hypermodeStyle && "bg-hypermode-accent text-white hover:bg-hypermode-accent-light"
),
children: action.label
},
action.id
)) })
]
}
);
}
return /* @__PURE__ */ jsxs4(
"div",
{
className: cn(
"rounded-lg border border-neutral-200 bg-white p-4 shadow-sm",
className
),
children: [
card.title && /* @__PURE__ */ jsx5("h3", { className: "mb-3 font-semibold text-neutral-900", children: card.title }),
/* @__PURE__ */ jsx5("div", { className: "space-y-3", children: Object.entries(card.content).map(([key, value]) => /* @__PURE__ */ jsxs4("div", { children: [
/* @__PURE__ */ jsxs4("div", { className: "mb-1 text-neutral-600 text-xs capitalize", children: [
key.replace(/_/g, " "),
":"
] }),
/* @__PURE__ */ jsx5("div", { className: "text-neutral-900 text-sm", children: typeof value === "string" ? value : JSON.stringify(value) })
] }, key)) }),
card.actions && card.actions.length > 0 && /* @__PURE__ */ jsx5("div", { className: "mt-4 flex gap-2", children: card.actions.map((action) => /* @__PURE__ */ jsx5(
Button,
{
size: "sm",
variant: action.type === "button" ? "primary" : "secondary",
onClick: () => handleAction(action),
children: action.label
},
action.id
)) })
]
}
);
}
// src/components/chat/response-item.tsx
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
function ResponseItem({
item,
userAvatar,
assistantAvatar,
hypermodeStyle = false,
className,
onCardAction
}) {
const containerClassName = cn("animate-fade-in", className);
switch (item.type) {
case "message":
return /* @__PURE__ */ jsx6("div", { className: containerClassName, children: /* @__PURE__ */ jsx6(
ChatBubble,
{
content: item.content,
role: item.role,
timestamp: item.timestamp,
hypermodeStyle,
avatar: item.role === "user" ? userAvatar : assistantAvatar
}
) });
case "tool_call":
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs5("div", { className: cn("flex items-start gap-3", containerClassName), children: [
assistantAvatar || /* @__PURE__ */ jsx6("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-hypermode-accent text-sm text-white", children: "\u{1F527}" }),
/* @__PURE__ */ jsx6("div", { className: "flex-1", children: /* @__PURE__ */ jsx6(
ToolCallDisplay,
{
toolCall: item.toolCall,
hypermodeStyle
}
) })
] });
}
return /* @__PURE__ */ jsx6("div", { className: containerClassName, children: /* @__PURE__ */ jsx6(
ToolCallDisplay,
{
toolCall: item.toolCall,
hypermodeStyle
}
) });
case "card":
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs5("div", { className: cn("flex items-start gap-3", containerClassName), children: [
assistantAvatar || /* @__PURE__ */ jsx6("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-hypermode-accent text-sm text-white", children: "\u{1F4C4}" }),
/* @__PURE__ */ jsx6("div", { className: "flex-1", children: /* @__PURE__ */ jsx6(
CardDisplay,
{
card: item.card,
onAction: onCardAction,
hypermodeStyle
}
) })
] });
}
return /* @__PURE__ */ jsx6("div", { className: containerClassName, children: /* @__PURE__ */ jsx6(
CardDisplay,
{
card: item.card,
onAction: onCardAction,
hypermodeStyle
}
) });
default:
return null;
}
}
function ResponseItemList({
items,
userAvatar,
assistantAvatar,
hypermodeStyle = false,
className,
onCardAction
}) {
return /* @__PURE__ */ jsx6("div", { className: cn("space-y-6", className), children: items.map((item) => /* @__PURE__ */ jsx6(
ResponseItem,
{
item,
userAvatar,
assistantAvatar,
hypermodeStyle,
onCardAction
},
item.id
)) });
}
// src/components/avatar/avatar.tsx
import { jsx as jsx7 } 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__ */ jsx7(
"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__ */ jsx7(
"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 jsx8, jsxs as jsxs6 } 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__ */ jsxs6(
"div",
{
className: cn(
"flex items-center justify-between border-hypermode-border border-b p-3",
headerClassName
),
children: [
/* @__PURE__ */ jsx8("div", { className: "font-semibold text-lg", children: title }),
settingsIcon && /* @__PURE__ */ jsx8(
Button,
{
variant: "ghost",
size: "icon",
className: "text-neutral-400 hover:bg-hypermode-hover hover:text-white",
children: settingsIcon
}
)
]
}
);
const defaultHypermodeProfile = /* @__PURE__ */ jsxs6(
"div",
{
className: cn(
"flex items-center justify-between border-hypermode-border border-t p-3",
profileClassName
),
children: [
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx8(
Avatar,
{
initial: username.charAt(0),
role: "user",
hypermodeStyle: true
}
),
/* @__PURE__ */ jsx8("div", { className: "text-sm", children: username })
] }),
settingsIcon && /* @__PURE__ */ jsx8(
Button,
{
variant: "ghost",
size: "icon",
className: "text-neutral-400 hover:bg-hypermode-hover hover:text-white",
children: settingsIcon
}
)
]
}
);
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs6(
"div",
{
className: cn(
"flex h-full w-64 flex-col border-hypermode-border border-r bg-hypermode-card",
className
),
children: [
headerContent || defaultHypermodeHeader,
/* @__PURE__ */ jsx8("div", { className: "p-3", children: /* @__PURE__ */ jsxs6(
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__ */ jsx8("span", { children: "New Conversation" })
]
}
) }),
/* @__PURE__ */ jsxs6("div", { className: cn("flex-1 overflow-y-auto", conversationsClassName), children: [
/* @__PURE__ */ jsx8("div", { className: "hypermode-sidebar-title", children: "Conversations" }),
/* @__PURE__ */ jsx8("div", { className: "space-y-1 px-2", children: conversations.map((conv) => /* @__PURE__ */ jsxs6("div", { className: "group flex items-center", children: [
/* @__PURE__ */ jsx8(
"button",
{
onClick: () => onSelectConversation(conv.id),
className: cn(
"hypermode-sidebar-item flex-1",
conv.id === currentConversationId ? "active" : ""
),
children: conv.title
}
),
/* @__PURE__ */ jsx8(
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__ */ jsxs6(
"div",
{
className: cn(
"h-full w-64 border-border-primary border-r bg-modal-surface-body-primary p-4",
className
),
children: [
/* @__PURE__ */ jsxs6(
Button,
{
onClick: onCreateConversation,
className: "mb-4 flex w-full items-center justify-center gap-2",
children: [
typeof newConversationIcon === "string" ? newConversationIcon : newConversationIcon,
"New Conversation"
]
}
),
/* @__PURE__ */ jsx8("div", { className: "flex-1 space-y-2 overflow-y-auto", children: conversations.map((conv) => /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx8(
"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__ */ jsx8(
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__ */ jsxs6("div", { className: "mt-4 flex items-center gap-2 border-border-primary border-t pt-4", children: [
/* @__PURE__ */ jsx8(Avatar, { initial: username.charAt(0), role: "user" }),
/* @__PURE__ */ jsx8("div", { className: "font-medium text-sm", children: username })
] })
]
}
);
}
ChatSidebar.displayName = "ChatSidebar";
// src/components/chat/chat-interface.tsx
import { Fragment, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
function ChatInterface({
networkAdapter,
storageAdapter,
showSidebar = true,
showInputArea = true,
className,
chatAreaClassName,
sidebarClassName,
inputAreaClassName,
headerClassName,
headerContent,
sidebarHeaderContent,
userProfileContent,
inputAreaContent,
username = "User",
onCardAction,
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 items = (currentConversation == null ? void 0 : currentConversation.items) || [];
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 latestItems = await storageAdapter.getConversationItems(
currentConversationId
);
setConversations(
(prev) => prev.map(
(conv) => conv.id === currentConversationId ? { ...conv, items: latestItems } : 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, [items]);
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}`,
items: []
};
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(),
type: "message",
content: messageText,
role: "user",
timestamp: (/* @__PURE__ */ new Date()).toISOString()
};
setConversations(
(prev) => prev.map(
(conv) => conv.id === currentConversationId ? {
...conv,
items: [...conv.items, userMessage]
} : conv
)
);
await storageAdapter.addItem(currentConversationId, userMessage);
setLoading(true);
try {
const agentId = await storageAdapter.getConversationAgent(
currentConversationId
);
if (!agentId) {
throw new Error("No agent found for conversation");
}
const response = await networkAdapter.sendMessage(agentId, messageText);
setConversations(
(prev) => prev.map(
(conv) => conv.id === currentConversationId ? {
...conv,
items: [...conv.items, ...response.items]
} : conv
)
);
for (const item of response.items) {
await storageAdapter.addItem(currentConversationId, item);
}
} catch (error) {
console.error("Error sending message:", error);
const errorMessage = {
id: Date.now().toString(),
type: "message",
content: "Sorry, there was an error sending your message.",
role: "assistant",
timestamp: (/* @__PURE__ */ new Date()).toISOString()
};
setConversations(
(prev) => prev.map(
(conv) => conv.id === currentConversationId ? {
...conv,
items: [...conv.items, errorMessage]
} : conv
)
);
await storageAdapter.addItem(currentConversationId, errorMessage);
} finally {
setLoading(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
await sendMessage(input);
setInput("");
};
const handleCardAction = (action) => {
if (onCardAction) {
onCardAction(action);
} else {
console.log("Card action triggered:", action);
if (action.type === "link") {
window.open(action.action, "_blank");
}
}
};
const inputAreaContext = {
input,
setInput,
loading,
sendMessage,
currentConversationId,
inputPlaceholder,
sendButtonIcon
};
const EmptyState = () => /* @__PURE__ */ jsxs7("div", { className: "flex flex-1 flex-col items-center justify-center p-8 text-center", children: [
/* @__PURE__ */ jsx9("div", { className: "mb-4 text-neutral-400", children: hypermodeStyle ? /* @__PURE__ */ jsx9("div", { className: "mb-4 text-6xl", children: "\u{1F4AC}" }) : /* @__PURE__ */ jsx9("div", { className: "mb-4 text-6xl", children: "\u{1F4AC}" }) }),
/* @__PURE__ */ jsx9(
"h2",
{
className: cn(
"mb-2 font-medium text-xl",
hypermodeStyle ? "text-white" : "text-text-primary"
),
children: "Welcome to Chat"
}
),
/* @__PURE__ */ jsx9(
"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__ */ jsxs7(
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__ */ jsx9("div", { className: "flex items-center justify-between border-hypermode-border border-b p-3", children: /* @__PURE__ */ jsxs7("div", { className: "flex items-center", children: [
assistantAvatar || /* @__PURE__ */ jsx9("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-hypermode-accent text-sm text-white", children: "A" }),
/* @__PURE__ */ jsx9("span", { className: "ml-2 font-medium", children: "Assistant" })
] }) });
const renderDefaultInputArea = () => {
if (hypermodeStyle) {
return /* @__PURE__ */ jsx9(
DefaultHypermodeInputArea,
{
input,
setInput,
loading,
onSubmit: handleSubmit,
inputPlaceholder,
sendButtonIcon,
inputAreaClassName
}
);
} else {
return /* @__PURE__ */ jsx9(
DefaultStandardInputArea,
{
input,
setInput,
loading,
onSubmit: handleSubmit,
inputPlaceholder,
sendButtonIcon,
inputAreaClassName
}
);
}
};
if (hypermodeStyle) {
return /* @__PURE__ */ jsxs7("div", { className: cn("flex h-full bg-hypermode-bg text-white", className), children: [
showSidebar && /* @__PURE__ */ jsx9(
ChatSidebar,
{
conversations,
currentConversationId,
onCreateConversation: createNewConversation,
onDeleteConversation: deleteConversation,
onSelectConversation: setCurrentConversationId,
username,
className: sidebarClassName,
headerContent: sidebarHeaderContent,
profileContent: userProfileContent,
newConversationIcon,
deleteConversationIcon,
hypermodeStyle: true
}
),
/* @__PURE__ */ jsxs7(
"div",
{
className: cn(
"flex flex-1 flex-col bg-hypermode-bg",
chatAreaClassName
),
children: [
headerContent || defaultHypermodeHeader,
!currentConversationId ? /* @__PURE__ */ jsx9(EmptyState, {}) : /* @__PURE__ */ jsxs7(Fragment, { children: [
/* @__PURE__ */ jsxs7("div", { className: cn("flex-1 space-y-6 overflow-y-auto p-4"), children: [
/* @__PURE__ */ jsx9(
ResponseItemList,
{
items,
userAvatar,
assistantAvatar,
hypermodeStyle: true,
onCardAction: handleCardAction
}
),
loading && /* @__PURE__ */ jsx9(
LoadingChatBubble,
{
hypermodeStyle: true,
avatar: assistantAvatar
}
),
/* @__PURE__ */ jsx9("div", { ref: messagesEndRef })
] }),
showInputArea && (inputAreaContent ? (
// Handle both ReactNode and function types
typeof inputAreaContent === "function" ? inputAreaContent(inputAreaContext) : inputAreaContent
) : renderDefaultInputArea())
] })
]
}
)
] });
}
return /* @__PURE__ */ jsxs7("div", { className: cn("flex h-full bg-white", className), children: [
showSidebar && /* @__PURE__ */ jsx9(
ChatSidebar,
{
conversations,
currentConversationId,
onCreateConversation: createNewConversation,
onDeleteConversation: deleteConversation,
onSelectConversation: setCurrentConversationId,
username,
className: sidebarClassName,
headerContent: sidebarHeaderContent,
profileContent: userProfileContent,
newConversationIcon,
deleteConversationIcon,
hypermodeStyle: false
}
),
/* @__PURE__ */ jsxs7("div", { className: cn("flex flex-1 flex-col bg-white", chatAreaClassName), children: [
/* @__PURE__ */ jsx9("div", { className: cn("bg-white p-4 shadow-xs", headerClassName), children: headerContent }),
!currentConversationId ? /* @__PURE__ */ jsx9(EmptyState, {}) : /* @__PURE__ */ jsxs7(Fragment, { children: [
/* @__PURE__ */ jsxs7("div", { className: "flex-1 space-y-4 overflow-y-auto bg-white p-4", children: [
/* @__PURE__ */ jsx9(
ResponseItemList,
{
items,
userAvatar,
assistantAvatar,
hypermodeStyle: false,
onCardAction: handleCardAction
}
),
loading && /* @__PURE__ */ jsx9(LoadingChatBubble, {}),
/* @__PURE__ */ jsx9("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,
CardDisplay,
ChatBubble,
ChatInterface,
ChatSidebar,
DefaultHypermodeInputArea,
DefaultStandardInputArea,
Input,
LoadingChatBubble,
LoadingDot,
LoadingIndicator,
ResponseItem,
ResponseItemList,
ToolCallDisplay,
cn
};