import { ChevronDown, ChevronUp, Loader, Check } from 'lucide-react' import { create } from 'zustand' import { RenderMarkdown } from './RenderMarkdown' // import { useAppState } from '@/hooks/useAppState' import { useTranslation } from '@/i18n/react-i18next-compat' import { useMemo } from 'react' import { cn } from '@/lib/utils' // Define ThoughtStep type type ThoughtStep = { type: 'thought' | 'tool_call' | 'tool_output' | 'done' content: string metadata?: any time?: number } interface Props { text: string id: string steps?: ThoughtStep[] loading?: boolean duration?: number } // Zustand store for thinking block state type ThinkingBlockState = { thinkingState: { [id: string]: boolean } setThinkingState: (id: string, expanded: boolean) => void } const useThinkingStore = create((set) => ({ thinkingState: {}, setThinkingState: (id, expanded) => set((state) => ({ thinkingState: { ...state.thinkingState, [id]: expanded, }, })), })) // Helper to format duration in seconds const formatDuration = (ms: number) => { if (ms > 0) { return Math.round(ms / 1000) } return 0 } const ThinkingBlock = ({ id, // text, // Unused internally steps = [], loading: propLoading, duration, }: Props) => { const thinkingState = useThinkingStore((state) => state.thinkingState) const setThinkingState = useThinkingStore((state) => state.setThinkingState) const { t } = useTranslation() // Actual loading state comes from prop, determined by whether final text started streaming (Req 2) const loading = propLoading // Set default expansion state: expanded if loading, collapsed if done. // If loading transitions to false (textSegment starts), this defaults to collapsed if state is absent. const isExpanded = thinkingState[id] ?? (loading ? true : false) // Filter out the 'done' step for streaming display const stepsWithoutDone = useMemo( () => steps.filter((step) => step.type !== 'done'), [steps] ) const N = stepsWithoutDone.length // Determine the step to display in the condensed streaming view (Req 3) // Show step N-2 when N >= 2 (i.e., when step N-1 is streaming, show the previously finished step) const stepToRenderWhenStreaming = useMemo(() => { if (!loading) return null // Only apply this logic when actively loading if (N >= 2) { // Show the penultimate step (index N-2) return stepsWithoutDone[N - 2] } return null }, [loading, N, stepsWithoutDone]) // Determine if the block is truly empty (streaming started but no content/steps yet) const isStreamingEmpty = loading && N === 0 // If loading started but no content or steps have arrived yet, display the non-expandable 'Thinking...' block if (isStreamingEmpty) { return (
{t('thinking')}...
) } // If not loading, and there are no steps, hide the block entirely. const hasContent = steps.length > 0 if (!loading && !hasContent) return null const handleClick = () => { // Only allow toggling expansion if not currently loading if (!loading) { setThinkingState(id, !isExpanded) } } // --- Rendering Functions for Expanded View --- const renderStepContent = (step: ThoughtStep, index: number) => { if (step.type === 'done') { const timeInSeconds = formatDuration(step.time ?? 0) const timeDisplay = timeInSeconds > 0 ? `(${t('for')} ${timeInSeconds} ${t('seconds')})` : '' return (
{t('done')} {timeDisplay && ( {timeDisplay} )}
) } let contentDisplay if (step.type === 'tool_call') { const args = step.metadata ? step.metadata : '' contentDisplay = ( <>

Tool Call: {step.content}

{args && (
)} ) } else if (step.type === 'tool_output') { contentDisplay = ( <>

Tool Output:

) } else { // thought contentDisplay = ( ) } return (
{contentDisplay}
) } const headerTitle = useMemo(() => { if (loading) return t('thinking') const timeInSeconds = formatDuration(duration ?? 0) if (timeInSeconds > 0) { return `${t('thought')} ${t('for')} ${timeInSeconds} ${t('seconds')}` } return t('thought') }, [loading, duration, t]) return (
{loading && ( )}
{/* Streaming/Condensed View - shows previous finished step */} {loading && stepToRenderWhenStreaming && (
{/* Bullet point */}
{/* Previous completed step content */} {renderStepContent(stepToRenderWhenStreaming, N - 2)}
)} {/* Expanded View - shows all steps */} {isExpanded && !loading && (
{steps.map((step, index) => (
{/* Bullet point/Icon position relative to line */}
{/* Step Content */} {renderStepContent(step, index)}
))}
)}
) } export default ThinkingBlock