"use client"; import { useEffect, useRef, useState } from "react"; import { BrainCircuit, ChevronDown, Loader2 } from "lucide-react"; import { useTranslation } from "react-i18next"; import MarkdownRenderer from "./MarkdownRenderer"; interface ModelThinkingCardProps { /** Inner text of a single ... block (already trimmed). */ content: string; /** * False while the model is still streaming inside the open tag, so * the card stays expanded and shows a spinner. Once the closing tag arrives * the card auto-collapses (default-folded) unless the user has pinned it * open or closed manually. */ closed: boolean; } /** * Collapsible card that surfaces a reasoning model's raw scratchpad * in a way that visually echoes the system trace panels (subtle border, * muted typography) without being mistaken for one. * * Behaviour: * - Default-open while the model is still writing the scratchpad so the * user can watch reasoning happen live. * - Auto-collapses the moment the closing tag arrives. * - Once the user toggles the card themselves, their preference wins for * the rest of the message lifetime. */ export default function ModelThinkingCard({ content, closed, }: ModelThinkingCardProps) { const { t } = useTranslation(); const [userToggled, setUserToggled] = useState(null); const detailsRef = useRef(null); const open = userToggled !== null ? userToggled : !closed; // Keep the underlying
element in sync with our derived `open` // state. We do not bind `open` as a controlled prop because React strips // the boolean attribute on `false`, which races with the browser's // built-in toggle handling and produces a flicker the first time `closed` // flips during streaming. useEffect(() => { const el = detailsRef.current; if (el && el.open !== open) { el.open = open; } }, [open]); const handleToggle = (event: React.SyntheticEvent) => { const next = event.currentTarget.open; if (next !== open) { setUserToggled(next); } }; const hasBody = content.trim().length > 0; const placeholder = `${t("Thinking...")}`; return (
{t("Model thinking")} {!closed && ( )}
{hasBody ? ( ) : (
{placeholder}
)}
); }