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 { useCopy } 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 ( ) } /** Copies a message's text, with a short "copied" state */ function CopyButton({ text }: { text: string }) { const dict = useDictionary() const { copied, copy } = useCopy(() => toast.error(dict.chat.failedToCopyDetail), ) return ( copy(text)} > {copied ? : } ) } 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 }: { text: string }) { 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 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 [saveTemplate, setSaveTemplate] = useState(false) 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 = async () => { if (!editText.trim()) return // Kept open, with its text, when it could not go now if (await engine.editMessage(index, editText.trim())) setEditing(false) } if (editing) { return (