"use client"; import { Check } from "lucide-react"; import { motion, useReducedMotion, type Variants } from "motion/react"; import { useEffect, useState } from "react"; import type { AgentTranscript as AgentTranscriptModel } from "@/lib/connectors-marketing/types"; import { cn } from "@/lib/utils"; // Custom ease-out: fast start, gentle settle. Never ease-in for entrances. const EASE_OUT: [number, number, number, number] = [0.16, 1, 0.3, 1]; const container: Variants = { hidden: {}, show: { transition: { staggerChildren: 0.06, delayChildren: 0.08 } }, }; // Enter from a small offset + fade. Never from scale(0). Under 300ms per item. const item: Variants = { hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0, transition: { duration: 0.24, ease: EASE_OUT } }, }; /** Reveal a string one character at a time. Returns the full string instantly when disabled. */ function useTypedText(text: string, enabled: boolean, speedMs = 16) { const [count, setCount] = useState(enabled ? 0 : text.length); useEffect(() => { if (!enabled) { setCount(text.length); return; } setCount(0); let i = 0; const id = window.setInterval(() => { i += 1; setCount(i); if (i >= text.length) window.clearInterval(id); }, speedMs); return () => window.clearInterval(id); }, [text, enabled, speedMs]); return { shown: text.slice(0, count), done: count >= text.length }; } export function AgentTranscript({ transcript, className, }: { transcript: AgentTranscriptModel; className?: string; }) { const reduce = useReducedMotion() ?? false; const animate = !reduce; const { shown: typedPrompt, done: promptDone } = useTypedText(transcript.prompt, animate); const revealed = promptDone; return (
{/* Window chrome */}
agent ยท surfsense
{/* Prompt line (typed) */}

$ {typedPrompt} {animate && !promptDone && ( )}

{/* Tool call + results reveal only after the prompt is typed */} {transcript.toolCall}
    {transcript.rows.map((row) => ( {row.primary} {row.secondary} {row.tag && ( {row.tag} )} ))}
{transcript.resultSummary}
); }