// src/components/chat/ChatMessage.tsx import React, { useState, useMemo } from 'react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import rehypeRaw from 'rehype-raw' import { cn } from '@/lib/utils' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible' import { ChevronDown, Brain } from 'lucide-react' interface ChatMessageProps { content: string className?: string } export const ChatMessage: React.FC = ({ content, className, }) => { // Extract thinking content and actual response const { processedContent, thinkingBlocks } = useMemo(() => { const blocks: { id: number; content: string }[] = []; let thinkBlockCounter = 0; // Extract thinking content between tags const contentWithoutThinking = content.replace( /([\s\S]*?)<\/think>/g, (_, thinkContent) => { blocks.push({ id: thinkBlockCounter++, content: thinkContent.trim() }); return ''; // Remove thinking content from the main message } ); // Continue processing source tags const processedText = contentWithoutThinking.replace( //g, (_match, path) => { const filename = path .split('/') .pop()! .replace(/\.[^/.]+$/, '') return ` ${filename} `; } ); return { processedContent: processedText.trim(), thinkingBlocks: blocks }; }, [content]); return (
{/* First render the thinking blocks if any */} {thinkingBlocks.length > 0 && (
Thoughts
{thinkingBlocks.map((block, index) => ( {block.content} ))}
)} {/* Then render the actual response content */} {processedContent && ( href && href.endsWith('.md') ? ( {children} ) : ( {children} ), }} > {processedContent} )}
) }