import { type JSX, For, createSignal, splitProps, Show } from "solid-js"; import { Tooltip } from "../ui/tooltip"; import { Copy, Check } from "lucide-solid"; import { cn } from "../utils/cn"; import { Markdown } from "./markdown"; import { Button } from "../ui/button"; import { actionIcon, BUILTIN_ACTION_LABEL } from "../ui/action-icons"; import type { ChatMessageAction, CustomAction } from "../elements/chat-types"; import { useChatConfig, textClass } from "../primitives/chat-config"; // --- Message --- export interface MessageProps extends JSX.HTMLAttributes { children: JSX.Element; } function Message(props: MessageProps) { const [local, rest] = splitProps(props, ["children", "class"]); return (
{local.children}
); } // --- MessageAvatar --- export interface MessageAvatarProps { src: string; alt: string; fallback?: string; class?: string; } function MessageAvatar(props: MessageAvatarProps) { return (
{props.fallback}
} > {props.alt}
); } // --- MessageContent --- export interface MessageContentProps extends JSX.HTMLAttributes { children: JSX.Element | string; markdown?: boolean; } function MessageContent(props: MessageContentProps) { const [local, rest] = splitProps(props, ["children", "markdown", "class"]); const config = useChatConfig(); const classNames = () => cn( "min-w-0 rounded-lg p-2 text-foreground bg-secondary max-w-none break-words whitespace-normal", textClass(config.proseSize()), local.class, ); return ( {local.children} } > ); } // --- MessageActions --- export interface MessageActionsProps extends JSX.HTMLAttributes { children: JSX.Element; } function MessageActions(props: MessageActionsProps) { const [local, rest] = splitProps(props, ["children", "class"]); return (
{local.children}
); } // --- MessageActionBar --- export interface MessageActionBarProps { /** Built-in action names and/or custom action descriptors, in order. */ actions: (ChatMessageAction | CustomAction)[]; /** `'always'` (default) keeps the bar visible; `'hover'` reveals it on * parent `.group` hover. */ reveal?: 'always' | 'hover'; /** Fired with the built-in name or the custom action id when a button is clicked. */ onAction: (id: string) => void; class?: string; } /** Normalize a bar entry (string built-in or custom object) to a uniform shape. */ function normalizeAction(a: ChatMessageAction | CustomAction) { if (typeof a === 'string') { return { id: a, label: BUILTIN_ACTION_LABEL[a], Icon: actionIcon(a) }; } return { id: a.id, label: a.label, Icon: actionIcon(a.icon) }; } /** * The shared message action toolbar. Renders one ghost icon button per entry — * built-in names pull their label+icon from the curated registry; custom * descriptors use their `label` plus `actionIcon(icon)` (label-only when the * icon is unknown or absent). `reveal="hover"` makes the bar fade in on the * parent `.group`'s hover. */ function MessageActionBar(props: MessageActionBarProps) { return ( {(a) => { const item = normalizeAction(a); const tooltipText = () => (typeof a !== 'string' && a.tooltip) ? a.tooltip : item.label; // Factory (not a shared node): each Show branch gets its own Button so // the eager DOM node is never referenced from two places. const button = () => ( ); // Icon-only buttons get a tooltip; label-only buttons (text already // visible) don't. return ( {button()} ); }} ); } // --- MessageAction --- export interface MessageActionProps { tooltip: string; children: JSX.Element; side?: "top" | "bottom" | "left" | "right"; class?: string; } function MessageAction(props: MessageActionProps) { return <>{props.children}; } // --- MessageCopyButton --- export interface MessageCopyButtonProps { content: string; size?: number; class?: string; } function MessageCopyButton(props: MessageCopyButtonProps) { const [copied, setCopied] = createSignal(false); const iconSize = () => props.size ?? 14; return ( ); } export { Message, MessageAvatar, MessageContent, MessageActions, MessageActionBar, MessageAction, MessageCopyButton };