import { makeAssistantDataUI, useAuiState } from "@assistant-ui/react"; import { ChevronRightIcon } from "lucide-react"; import type { FC } from "react"; import { useCallback, useEffect, useState } from "react"; import { ChainOfThoughtItem } from "@/components/prompt-kit/chain-of-thought"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { cn } from "@/lib/utils"; export interface ThinkingStep { id: string; title: string; items: string[]; status: "pending" | "in_progress" | "completed"; } /** * Chain of thought display component - single collapsible dropdown design */ export const ThinkingStepsDisplay: FC<{ steps: ThinkingStep[]; isThreadRunning?: boolean }> = ({ steps, isThreadRunning = true, }) => { const [isOpen, setIsOpen] = useState(true); const getEffectiveStatus = useCallback( (step: ThinkingStep): "pending" | "in_progress" | "completed" => { if (step.status === "in_progress" && !isThreadRunning) { return "completed"; } return step.status; }, [isThreadRunning] ); const inProgressStep = steps.find((s) => getEffectiveStatus(s) === "in_progress"); const allCompleted = steps.length > 0 && !isThreadRunning && steps.every((s) => getEffectiveStatus(s) === "completed"); const isProcessing = isThreadRunning && !allCompleted; useEffect(() => { if (allCompleted) { setIsOpen(false); } }, [allCompleted]); if (steps.length === 0) return null; const getHeaderText = () => { if (allCompleted) { return "Reviewed"; } if (inProgressStep) { return inProgressStep.title; } if (isProcessing) { return "Processing"; } return "Reviewed"; }; return (
{steps.map((step, index) => { const effectiveStatus = getEffectiveStatus(step); const isLast = index === steps.length - 1; return (
{!isLast && (
)}
{effectiveStatus === "in_progress" ? ( ) : ( )}
{step.title}
{step.items && step.items.length > 0 && (
{step.items.map((item) => ( {item} ))}
)}
); })}
); }; /** * assistant-ui data UI component that renders thinking steps from message content. * Registered globally via makeAssistantDataUI — renders inside MessagePrimitive.Parts * at the position of the data part in the content array. */ function ThinkingStepsDataRenderer({ data }: { name: string; data: unknown }) { const isThreadRunning = useAuiState(({ thread }) => thread.isRunning); const isLastMessage = useAuiState(({ message }) => message?.isLast ?? false); const isMessageStreaming = isThreadRunning && isLastMessage; const steps = (data as { steps: ThinkingStep[] } | null)?.steps ?? []; if (steps.length === 0) return null; return (
); } export const ThinkingStepsDataUI = makeAssistantDataUI({ name: "thinking-steps", render: ThinkingStepsDataRenderer, });