import { useState } from "react"; import type { ChatMessage } from "../../types/message"; import { useAvatars } from "../../context/AvatarContext"; import { downloadTextFile, messageMarkdownFilename, singleAssistantMarkdown, } from "../../utils/exportConversation"; import { renderMarkdown } from "../../utils/markdown"; import { imageToDataUrl } from "../../utils/images"; import { ToolCallBlock } from "./ToolCallBlock"; import styles from "./MessageList.module.css"; interface MessageBubbleProps { message: ChatMessage; showAgentAvatar?: boolean; agentAvatarSpacer?: boolean; } function ChatAvatar({ src, className }: { src: string; className: string }) { const [failed, setFailed] = useState(false); if (!src || failed) return null; return ( setFailed(true)} /> ); } function AssistantActions({ content }: { content: string }) { const [copied, setCopied] = useState(false); const markdown = singleAssistantMarkdown(content); const handleCopy = async () => { try { await navigator.clipboard.writeText(markdown); setCopied(true); window.setTimeout(() => setCopied(false), 1500); } catch { /* ignore */ } }; const handleDownload = () => { downloadTextFile(messageMarkdownFilename(content), markdown, "text/markdown"); }; return (
); } export function MessageBubble({ message, showAgentAvatar = false, agentAvatarSpacer = false, }: MessageBubbleProps) { const { userAvatarUrl, agentAvatarUrl } = useAvatars(); if (message.role === "tool_call") { return (
{showAgentAvatar ? ( ) : agentAvatarSpacer ? ( ); } const classNames = [styles.msg]; if (message.role === "user") classNames.push(styles.user); if (message.role === "assistant") classNames.push(styles.assistant); if (message.role === "system") classNames.push(styles.system); if (message.streaming) classNames.push(styles.streaming); if (message.role === "assistant") { const showActions = !message.streaming && message.content.trim().length > 0; return (
{showActions ? : null}
); } if (message.role === "user") { return (
{message.content ?
{message.content}
: null} {message.images?.length ? (
{message.images.map((img, index) => ( ))}
) : null}
); } return
{message.content}
; }