"use client"; import clsx from "clsx"; import { BookOpen, Check, ChevronDown, ChevronRight, Copy, Database, ExternalLink, Globe, GraduationCap, LibraryBig, Loader2, Pencil, RefreshCw, Sparkles, Terminal, Wrench, } from "lucide-react"; import type { ComponentType, SVGProps } from "react"; import { useEffect, useRef, useState, type ReactNode, type RefObject, } from "react"; import { MarkdownBlock } from "@/components/markdown-block"; import type { ActivityItem, MessageCitation, TutorMessage, TutorMessageBlock, TutorMessagePart, } from "@/lib/chat-ui"; import { buildActivityItems, buildCitationNumbers, buildCitationResolutions, citationNumberFor, getMessageCitations, getOrderedMessageBlocks, isHttpUrl, prettifyToolName, toolInputSummary, } from "@/lib/chat-ui"; type ChatMessageProps = { message: TutorMessage; editDraft?: string; isEditing?: boolean; isStreaming?: boolean; onAssistantCopy?: (message: TutorMessage) => void; onAssistantRedo?: (messageId: string) => void; onEditCancel?: () => void; onEditChange?: (value: string) => void; onEditSave?: (messageId: string) => void; onUserEdit?: (messageId: string) => void; actionDisabled?: boolean; copied?: boolean; showAssistantActions?: boolean; }; export function ChatMessage({ message, editDraft = "", isEditing = false, isStreaming = false, onAssistantCopy, onAssistantRedo, onEditCancel, onEditChange, onEditSave, onUserEdit, actionDisabled = false, copied = false, showAssistantActions = true, }: ChatMessageProps) { const isAssistant = message.role === "assistant"; const contentBlocks = getOrderedMessageBlocks(message); const citations = isAssistant && !isStreaming ? getMessageCitations(message) : []; const citationResolutions = isAssistant ? buildCitationResolutions(message) : undefined; const citationNumbers = isAssistant ? buildCitationNumbers(message, citationResolutions) : undefined; const editTextareaRef = useRef(null); useEffect(() => { if (!isEditing || isAssistant) { return; } const textarea = editTextareaRef.current; if (!textarea) { return; } textarea.focus(); const length = textarea.value.length; textarea.setSelectionRange(length, length); }, [isAssistant, isEditing]); useEffect(() => { if (!isEditing || isAssistant) { return; } const textarea = editTextareaRef.current; if (!textarea) { return; } textarea.style.height = "0px"; textarea.style.height = `${Math.min(textarea.scrollHeight, 240)}px`; }, [editDraft, isAssistant, isEditing]); return (
{isEditing && !isAssistant ? ( onEditSave?.(message.id)} /> ) : (
{contentBlocks.map((block, index) => { const isLastBlock = index === contentBlocks.length - 1; const isActive = isStreaming && isLastBlock && block.kind !== "text"; return ( ); })} {citations.length > 0 ? ( ) : null}
)}
{isAssistant && showAssistantActions ? (
onAssistantRedo?.(message.id)} disabled={actionDisabled} > onAssistantCopy?.(message)} > {copied ? ( ) : ( )}
) : !isAssistant ? ( !isEditing ? (
onUserEdit?.(message.id)} disabled={actionDisabled} >
) : null ) : null}
); } function InlineEditor({ disabled, onCancel, onChange, onSave, textareaRef, value, }: { disabled: boolean; onCancel?: () => void; onChange?: (value: string) => void; onSave?: () => void; textareaRef: RefObject; value: string; }) { const trimmed = value.trim(); return (