Documents
Home>Documents>Dev>Frontend

ThinkBlock: Visualizing AI Chain-of-Thought Reasoning

5 min readAug 7, 2025Feb 22, 2026

Showing How the AI Thinks

Recent LLMs can walk through their reasoning step by step using Chain-of-Thought. Claude's <thinking> block is a prime example. In XGen, we built a ThinkBlock component to visualize this reasoning process.

What Is ThinkBlock

ThinkBlock is a component that renders the AI's reasoning as a collapsible block. Users can read just the final response, or expand the block to see exactly how the AI worked through the problem.

Component Implementation

interface ThinkBlockProps {
  thinking: string;
  isStreaming?: boolean;
}

const ThinkBlock: React.FC<ThinkBlockProps> = ({ thinking, isStreaming }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  
  return (
    <div className="my-3 border border-purple-200 rounded-lg overflow-hidden">
      <button
        className="w-full px-4 py-2 flex items-center gap-2 bg-purple-50 hover:bg-purple-100 transition-colors"
        onClick={() => setIsExpanded(!isExpanded)}
      >
        <BrainIcon className="w-4 h-4 text-purple-500" />
        <span className="text-sm font-medium text-purple-700">
          AI 추론 과정
        </span>
        {isStreaming && (
          <span className="text-xs text-purple-400 animate-pulse">
            생각 중...
          </span>
        )}
        <ChevronIcon
          className={`ml-auto w-4 h-4 transition-transform ${
            isExpanded ? 'rotate-180' : ''
          }`}
        />
      </button>
      
      {isExpanded && (
        <div className="px-4 py-3 text-sm text-gray-600 bg-purple-25 border-t border-purple-100">
          <MarkdownRenderer content={thinking} />
        </div>
      )}
    </div>
  );
};

Handling ThinkBlock During Streaming

When a <thinking> tag appears in an SSE stream, we create a ThinkBlock and append content to the thinking region until the tag closes. Once the closing tag arrives, all subsequent text is treated as the normal response.

function parseStreamChunk(buffer: string): ParseResult {
  const thinkingMatch = buffer.match(/<thinking>([\s\S]*?)<\/thinking>/);
  
  if (thinkingMatch) {
    const thinking = thinkingMatch[1];
    const answer = buffer.replace(/<thinking>[\s\S]*?<\/thinking>/, '').trim();
    return { thinking, answer, isThinkingComplete: true };
  }
  
  // thinking 태그가 아직 닫히지 않은 경우
  const openMatch = buffer.match(/<thinking>([\s\S]*)/);
  if (openMatch) {
    return { thinking: openMatch[1], answer: '', isThinkingComplete: false };
  }
  
  return { thinking: '', answer: buffer, isThinkingComplete: true };
}

Design Decision: Collapsed vs. Expanded by Default

We initially defaulted ThinkBlock to expanded, but user testing showed that most users care about the final answer rather than the reasoning trace, so we switched to collapsed by default. The exception is developer mode, where the block is expanded by default.

Visualizing Multi-Step Reasoning

In complex workflows, multiple nodes may reason sequentially. Nesting each node's ThinkBlock lets users follow the full reasoning flow from start to finish.

Animation Details

We used framer-motion's AnimatePresence to animate the height transition as ThinkBlock collapses and expands. It's a small detail, but it meaningfully improves UX polish.

Tags
ThinkBlockChain-of-ThoughtAI reasoningUX componentstreaming