"use client"; import Link from "next/link"; import { CopyIconButton } from "./CopyIconButton"; import { Markdown } from "./Markdown"; import { ThinkingRow } from "./ThinkingRow"; import { Button } from "@/components/ui"; import { ModelErrorActions } from "@/components/ModelErrorActions"; import { PencilIcon, PlusIcon, RefreshIcon, SparklesIcon } from "@/components/icons"; import { useI18n } from "@/lib/i18n"; import type { ChatMessage } from "@/lib/chat"; /** * One turn: user bubbles on the right, assistant answers as full-width text * (no bubble) with the Thinking row, sources, actions and error recovery. */ export function MessageRow({ message, streaming, isLastAssistant, isLastUser, busy, onRegenerate, onEdit, onRetry, onToFlashcards, onSaveNotes, }: { message: ChatMessage; streaming?: boolean; isLastAssistant?: boolean; isLastUser?: boolean; busy?: boolean; onRegenerate?: () => void; onEdit?: () => void; onRetry?: () => void; onToFlashcards?: () => void; onSaveNotes?: () => void; }) { const { t } = useI18n(); if (message.role === "user") { return (

{message.content}

{message.attachments && message.attachments.length > 0 ? (

{message.attachments.map((attachment) => attachment.name).join(" · ")}

) : null}
{isLastUser && !busy && onEdit ? ( ) : null}
); } return (
{message.thinking ? : null} {message.content ? (
) : streaming ? ( {[0, 1, 2].map((dot) => ( ))} ) : null} {message.error ? (

{message.error.message}

{onRetry ? ( ) : null}
) : null} {!streaming && message.content && !message.error ? (
{isLastAssistant && !busy && onRegenerate ? ( ) : null} {onToFlashcards ? ( ) : null} {onSaveNotes ? ( ) : null} {message.model ? {message.model} : null}
) : null} {message.sources && message.sources.length > 0 && !streaming ? (
{t("chat.sources")} {message.sources.slice(0, 5).map((source, index) => ( {source.deckTitle} · {source.section} ))}
) : null}
); }