import type { UIMessage } from "ai" import { AlertCircle, BookmarkPlus, Check, ChevronDown, Copy, FileCode, FileText, Link, Pencil, RotateCcw, ThumbsDown, ThumbsUp, } from "lucide-react" import { useEffect, useRef, useState } from "react" import ReactMarkdown from "react-markdown" import { toast } from "sonner" import { Reasoning, ReasoningContent, ReasoningTrigger, } from "@/components/ai-elements/reasoning" import { Shimmer } from "@/components/ai-elements/shimmer" import { TemplateCreateDialog } from "@/components/chat/TemplateCreateDialog" import type { ToolPartLike } from "@/components/chat/types" import { ValidationCard } from "@/components/chat/ValidationCard" import Image from "@/components/image-with-basepath" import { useDictionary } from "@/hooks/use-dictionary" import { getApiEndpoint } from "@/lib/base-path" import { copyText } from "@/lib/clipboard" import { cn } from "@/lib/utils" import { useUiStore } from "@/stores/ui-store" import { useChatEngine } from "./chat-engine" import { getMessageTextContent, getUserOriginalText, splitTextIntoFileSections, type TextSection, } from "./message-text" import { ToolActivity } from "./tool-activity" function ActionButton({ label, onClick, active, children, }: { label: string onClick: () => void active?: boolean children: React.ReactNode }) { return ( ) } function AttachedSection({ section }: { section: TextSection }) { const [open, setOpen] = useState(false) const dict = useDictionary() const Icon = section.fileType === "pdf" ? FileText : section.fileType === "url" ? Link : FileCode const chars = section.charCount && section.charCount >= 1000 ? `${(section.charCount / 1000).toFixed(1)}k` : section.charCount return (
{open && (
                    {section.content}
                
)}
) } function MessageText({ text, isUser }: { text: string; isUser: boolean }) { const sections = splitTextIntoFileSections(text) return (
{sections.map((section, index) => section.type === "text" ? (
*:first-child]:mt-0 [&>*:last-child]:mb-0", "prose-p:my-1.5 prose-pre:bg-surface-2 prose-pre:text-foreground prose-code:before:content-none prose-code:after:content-none", !isUser && "dark:prose-invert", isUser && "dark:prose-invert", )} > {section.content}
) : ( ), )}
) } function UserMessage({ message, index, isLast, }: { message: UIMessage index: number isLast: boolean }) { const dict = useDictionary() const engine = useChatEngine() const [editing, setEditing] = useState(false) const [editText, setEditText] = useState("") const [copied, setCopied] = useState(false) const [saveTemplate, setSaveTemplate] = useState(false) const bumpTemplates = useUiStore((s) => s.bumpTemplates) const textareaRef = useRef(null) const text = getMessageTextContent(message) const canEdit = isLast && !engine.isBusy useEffect(() => { if (editing) textareaRef.current?.focus() }, [editing]) const startEdit = () => { if (!canEdit) return setEditText(getUserOriginalText(message)) setEditing(true) } const submitEdit = () => { if (!editText.trim()) return engine.editMessage(index, editText.trim()) setEditing(false) } if (editing) { return (