mirror of
https://github.com/willchen96/mike.git
synced 2026-06-14 20:55:13 +02:00
74 lines
3 KiB
TypeScript
74 lines
3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { ChevronDown } from "lucide-react";
|
|
|
|
export function PreResponseWrapper({
|
|
children,
|
|
stepCount,
|
|
shouldMinimize,
|
|
isStreaming,
|
|
compact = false,
|
|
}: {
|
|
children: React.ReactNode;
|
|
stepCount: number;
|
|
shouldMinimize: boolean;
|
|
isStreaming: boolean;
|
|
/** Tighter typography + child gap for narrow side panels (e.g. TR chat). */
|
|
compact?: boolean;
|
|
}) {
|
|
const [userToggled, setUserToggled] = useState(false);
|
|
const [isOpen, setIsOpen] = useState(!shouldMinimize);
|
|
// Once content has streamed in (shouldMinimize=true even once), stay
|
|
// minimized even if a later render briefly evaluates shouldMinimize=false.
|
|
// Without this latch, the wrapper visibly pops open when isStreaming
|
|
// flips off at the end of the response.
|
|
const hasMinimizedRef = useRef(shouldMinimize);
|
|
|
|
useEffect(() => {
|
|
if (shouldMinimize) hasMinimizedRef.current = true;
|
|
if (userToggled) return;
|
|
setIsOpen(!shouldMinimize && !hasMinimizedRef.current);
|
|
}, [shouldMinimize, userToggled]);
|
|
|
|
const stepWord = `step${stepCount === 1 ? "" : "s"}`;
|
|
const label = isStreaming
|
|
? "Working"
|
|
: `Completed in ${stepCount} ${stepWord}`;
|
|
|
|
const buttonTextClass = compact ? "text-xs" : "text-sm";
|
|
const childrenGapClass = compact ? "gap-2.5" : "gap-4";
|
|
|
|
return (
|
|
<div className="rounded-xl border border-white/70 bg-white/55 px-3 py-2 shadow-[0_3px_9px_rgba(15,23,42,0.03),inset_0_1px_0_rgba(255,255,255,0.9),inset_0_-4px_9px_rgba(255,255,255,0.05)] backdrop-blur-2xl">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setUserToggled(true);
|
|
setIsOpen((v) => !v);
|
|
}}
|
|
className={`w-full flex items-center justify-between font-serif text-gray-500 hover:text-gray-700 transition-colors ${buttonTextClass}`}
|
|
>
|
|
<span className="flex items-baseline min-w-0">
|
|
<span className="truncate">{label}</span>
|
|
{isStreaming && (
|
|
<span className="inline-flex ml-1 shrink-0 items-baseline">
|
|
<span className="w-0.5 h-0.5 rounded-full bg-gray-400 mr-0.5 animate-[bounce_1.4s_infinite_0s]" />
|
|
<span className="w-0.5 h-0.5 rounded-full bg-gray-400 mr-0.5 animate-[bounce_1.4s_infinite_0.2s]" />
|
|
<span className="w-0.5 h-0.5 rounded-full bg-gray-400 animate-[bounce_1.4s_infinite_0.4s]" />
|
|
</span>
|
|
)}
|
|
</span>
|
|
<ChevronDown
|
|
size={12}
|
|
className={`relative top-px shrink-0 ml-2 transition-transform duration-200 ${isOpen ? "" : "-rotate-90"}`}
|
|
/>
|
|
</button>
|
|
{isOpen && (
|
|
<div className={`mt-3 flex flex-col ${childrenGapClass}`}>
|
|
{children}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|