Formating Ai Responses
Your AI chat is streaming beautifully, but thereβs one thing missing - formatting! π¨
Right now when AI responds with code, lists, or structured content, it all appears as plain text. But AI responses often include markdown formatting that should be rendered properly.
π¨ Step 2: Create a Message Content Component
Section titled βπ¨ Step 2: Create a Message Content ComponentβInstead of changing your main component all at once, letβs create a helper component that handles message formatting. Add this before your main App function:
// π Component: Handles message content with markdown formattingfunction MessageContent({ message }) { // If it's a user message, just show plain text if (message.isUser) { return ( <p className="text-sm leading-relaxed whitespace-pre-wrap"> {message.text} {message.isStreaming && ( <span className="inline-block w-2 h-4 bg-blue-500 ml-1 animate-pulse" /> )} </p> ) }
// For AI messages, render markdown with custom styling return ( <div className="text-sm leading-relaxed"> <ReactMarkdown components={{ // Custom styling for different markdown elements h1: ({children}) => <h1 className="text-lg font-bold mb-2 text-slate-800">{children}</h1>, h2: ({children}) => <h2 className="text-base font-bold mb-2 text-slate-800">{children}</h2>, h3: ({children}) => <h3 className="text-sm font-bold mb-1 text-slate-800">{children}</h3>, p: ({children}) => <p className="mb-2 last:mb-0 text-slate-700">{children}</p>, ul: ({children}) => <ul className="list-disc list-inside mb-2 space-y-1">{children}</ul>, ol: ({children}) => <ol className="list-decimal list-inside mb-2 space-y-1">{children}</ol>, li: ({children}) => <li className="text-slate-700">{children}</li>, code: ({inline, children}) => inline ? ( <code className="bg-slate-100 text-slate-800 px-1 py-0.5 rounded text-xs font-mono"> {children} </code> ) : ( <code className="block bg-slate-100 text-slate-800 p-3 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre"> {children} </code> ), pre: ({children}) => <div className="mb-2">{children}</div>, strong: ({children}) => <strong className="font-semibold text-slate-800">{children}</strong>, em: ({children}) => <em className="italic text-slate-700">{children}</em>, blockquote: ({children}) => ( <blockquote className="border-l-4 border-blue-200 pl-4 italic text-slate-600 mb-2"> {children} </blockquote> ), a: ({href, children}) => ( <a href={href} className="text-blue-600 hover:text-blue-800 underline" target="_blank" rel="noopener noreferrer"> {children} </a> ), }} > {message.text} </ReactMarkdown> {message.isStreaming && ( <span className="inline-block w-2 h-4 bg-blue-500 ml-1 animate-pulse" /> )} </div> )}
What this component does:
For user messages: Shows plain text (users donβt usually write markdown)
For AI messages: Renders full markdown with custom styling:
- Headers (
# ## ###
) - Proper typography hierarchy - Code blocks - Syntax highlighting with gray background
- Lists - Bulleted and numbered lists with proper spacing
- Inline code -
code snippets
with background highlighting - Emphasis - bold and italic text
- Links - Clickable, properly styled links
- Blockquotes - Indented quotes with left border
π Step 3: Update Your Message Rendering
Section titled βπ Step 3: Update Your Message RenderingβNow letβs use this new component in your chat. Find your message bubble rendering code and replace it:
Find this section in your messages area:
<div className={`max-w-xs lg:max-w-md px-4 py-3 rounded-2xl ${ message.isUser ? 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white' : 'bg-white text-slate-800 shadow-sm border border-slate-200' }`}> <p className="text-sm leading-relaxed whitespace-pre-wrap"> {message.text} {message.isStreaming && ( <span className="inline-block w-2 h-4 bg-blue-500 ml-1 animate-pulse" /> )} </p></div>
Replace it with:
<div className={`max-w-xs lg:max-w-md px-4 py-3 rounded-2xl ${ message.isUser ? 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white' : 'bg-white text-slate-800 shadow-sm border border-slate-200' }`}> {/* π Use the new MessageContent component */} <MessageContent message={message} /></div>
Why this is better: The message bubble container stays the same, but now the content inside is properly formatted based on whether itβs a user or AI message.
π¨ Step 4: Improve Code Block Styling
Section titled βπ¨ Step 4: Improve Code Block StylingβLetβs make code blocks look even more professional. Weβll add syntax highlighting colors and better spacing:
Update your MessageContent
componentβs code styling:
code: ({inline, children}) => inline ? ( // Inline code: `like this` <code className="bg-slate-100 text-red-600 px-1.5 py-0.5 rounded text-xs font-mono border"> {children} </code> ) : ( // Code blocks: ```like this``` <code className="block bg-gray-900 text-green-400 p-4 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre border-l-4 border-blue-400 shadow-sm"> {children} </code> ),
Enhanced code styling:
- Inline code - Red text with light background and border
- Code blocks - Dark background with green text (terminal-style)
- Blue accent - Left border to highlight code sections
- Better spacing - More padding for readability
π§ͺ Step 5: Test Your Formatted Responses
Section titled βπ§ͺ Step 5: Test Your Formatted ResponsesβStart your servers:
Backend:
cd openai-backendnpm run dev
Frontend:
cd openai-frontendnpm run dev
Test with these prompts that generate formatted content:
-
βWrite a Python function to calculate fibonacci numbersβ
- Should show properly highlighted code blocks
-
βExplain the benefits of React in a bulleted listβ
- Should render a nice bulleted list
-
βGive me a step-by-step guide to deploy a Node.js appβ
- Should show numbered lists and headers
-
βCompare Python vs JavaScript with examplesβ
- Should show headers, code blocks, and emphasis
Success looks like: AI responses with proper formatting, syntax highlighting, lists, headers, and styled elements instead of raw markdown text.
π― Advanced: Add Copy Code Button
Section titled βπ― Advanced: Add Copy Code ButtonβLetβs add a copy button to code blocks for better user experience. Update your code component:
// π Enhanced code component with copy functionalitycode: ({inline, children}) => { const copyToClipboard = (text) => { navigator.clipboard.writeText(text) }
if (inline) { return ( <code className="bg-slate-100 text-red-600 px-1.5 py-0.5 rounded text-xs font-mono border"> {children} </code> ) }
return ( <div className="relative group mb-2"> <code className="block bg-gray-900 text-green-400 p-4 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre border-l-4 border-blue-400 shadow-sm"> {children} </code> <button onClick={() => copyToClipboard(children)} className="absolute top-2 right-2 bg-slate-600 hover:bg-slate-500 text-white px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity" > Copy </button> </div> )},
What this adds: A βCopyβ button appears when you hover over code blocks, making it easy for users to copy code examples.
π Step 6: Your Complete Updated Component
Section titled βπ Step 6: Your Complete Updated ComponentβHereβs your complete src/App.jsx
with markdown formatting:
import { useState, useRef } from 'react'import { Send, Bot, User } from 'lucide-react'import ReactMarkdown from 'react-markdown'
// π Component: Handles message content with markdown formattingfunction MessageContent({ message }) { // If it's a user message, just show plain text if (message.isUser) { return ( <p className="text-sm leading-relaxed whitespace-pre-wrap"> {message.text} {message.isStreaming && ( <span className="inline-block w-2 h-4 bg-blue-500 ml-1 animate-pulse" /> )} </p> ) }
// For AI messages, render markdown with custom styling return ( <div className="text-sm leading-relaxed"> <ReactMarkdown className="prose prose-sm max-w-none" components={{ h1: ({children}) => <h1 className="text-lg font-bold mb-2 text-slate-800">{children}</h1>, h2: ({children}) => <h2 className="text-base font-bold mb-2 text-slate-800">{children}</h2>, h3: ({children}) => <h3 className="text-sm font-bold mb-1 text-slate-800">{children}</h3>, p: ({children}) => <p className="mb-2 last:mb-0 text-slate-700">{children}</p>, ul: ({children}) => <ul className="list-disc list-inside mb-2 space-y-1">{children}</ul>, ol: ({children}) => <ol className="list-decimal list-inside mb-2 space-y-1">{children}</ol>, li: ({children}) => <li className="text-slate-700">{children}</li>, code: ({inline, children}) => { const copyToClipboard = (text) => { navigator.clipboard.writeText(text) }
if (inline) { return ( <code className="bg-slate-100 text-red-600 px-1.5 py-0.5 rounded text-xs font-mono border"> {children} </code> ) }
return ( <div className="relative group mb-2"> <code className="block bg-gray-900 text-green-400 p-4 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre border-l-4 border-blue-400 shadow-sm"> {children} </code> <button onClick={() => copyToClipboard(children)} className="absolute top-2 right-2 bg-slate-600 hover:bg-slate-500 text-white px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity" > Copy </button> </div> ) }, pre: ({children}) => <div className="mb-2">{children}</div>, strong: ({children}) => <strong className="font-semibold text-slate-800">{children}</strong>, em: ({children}) => <em className="italic text-slate-700">{children}</em>, blockquote: ({children}) => ( <blockquote className="border-l-4 border-blue-200 pl-4 italic text-slate-600 mb-2"> {children} </blockquote> ), a: ({href, children}) => ( <a href={href} className="text-blue-600 hover:text-blue-800 underline" target="_blank" rel="noopener noreferrer"> {children} </a> ), }} > {message.text} </ReactMarkdown> {message.isStreaming && ( <span className="inline-block w-2 h-4 bg-blue-500 ml-1 animate-pulse" /> )} </div> )}
function App() { // State management (same as before) const [messages, setMessages] = useState([]) const [input, setInput] = useState('') const [isStreaming, setIsStreaming] = useState(false) const abortControllerRef = useRef(null)
// Helper functions (same as before) const createAiPlaceholder = () => { const aiMessageId = Date.now() + 1 const aiMessage = { text: "", isUser: false, id: aiMessageId, isStreaming: true, } setMessages(prev => [...prev, aiMessage]) return aiMessageId }
const readStream = async (response, aiMessageId) => { const reader = response.body.getReader() const decoder = new TextDecoder()
while (true) { const { done, value } = await reader.read() if (done) break
const chunk = decoder.decode(value, { stream: true })
setMessages(prev => prev.map(msg => msg.id === aiMessageId ? { ...msg, text: msg.text + chunk } : msg ) ) } }
const sendMessage = async () => { if (!input.trim() || isStreaming) return
const userMessage = { text: input.trim(), isUser: true, id: Date.now() } setMessages(prev => [...prev, userMessage])
const currentInput = input setInput('') setIsStreaming(true) const aiMessageId = createAiPlaceholder()
try { abortControllerRef.current = new AbortController()
const response = await fetch('http://localhost:8000/api/chat/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: currentInput }), signal: abortControllerRef.current.signal, })
if (!response.ok) throw new Error('Failed to get response')
await readStream(response, aiMessageId)
setMessages(prev => prev.map(msg => msg.id === aiMessageId ? { ...msg, isStreaming: false } : msg ) )
} catch (error) { if (error.name !== 'AbortError') { console.error('Streaming error:', error) setMessages(prev => prev.map(msg => msg.id === aiMessageId ? { ...msg, text: 'Sorry, something went wrong.', isStreaming: false } : msg ) ) } } finally { setIsStreaming(false) abortControllerRef.current = null } }
const stopStreaming = () => { if (abortControllerRef.current) { abortControllerRef.current.abort() } }
const handleKeyPress = (e) => { if (e.key === 'Enter' && !e.shiftKey && !isStreaming) { e.preventDefault() sendMessage() } }
return ( <div className="min-h-screen bg-gradient-to-br from-slate-100 to-blue-50 flex items-center justify-center p-4"> <div className="bg-white rounded-2xl shadow-2xl w-full max-w-2xl h-[700px] flex flex-col overflow-hidden">
{/* Header */} <div className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-6"> <div className="flex items-center space-x-3"> <div className="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center"> <Bot className="w-5 h-5" /> </div> <div> <h1 className="text-xl font-bold">β‘ Streaming AI Chat</h1> <p className="text-blue-100 text-sm">Now with rich formatting!</p> </div> </div> </div>
{/* Messages Area */} <div className="flex-1 overflow-y-auto p-6 space-y-4 bg-slate-50"> {messages.length === 0 ? ( <div className="text-center text-slate-500 mt-20"> <div className="w-16 h-16 bg-blue-100 rounded-2xl flex items-center justify-center mx-auto mb-4"> <Bot className="w-8 h-8 text-blue-600" /> </div> <h3 className="text-lg font-semibold text-slate-700 mb-2"> Welcome to Enhanced Chat! </h3> <p className="text-sm">Try asking for code examples or formatted lists!</p> </div> ) : ( messages.map(message => ( <div key={message.id} className={`flex items-start space-x-3 ${ message.isUser ? 'justify-end' : 'justify-start' }`} > {!message.isUser && ( <div className="w-8 h-8 bg-gradient-to-r from-blue-500 to-indigo-600 rounded-full flex items-center justify-center flex-shrink-0"> <Bot className="w-4 h-4 text-white" /> </div> )}
<div className={`max-w-xs lg:max-w-md px-4 py-3 rounded-2xl ${ message.isUser ? 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white' : 'bg-white text-slate-800 shadow-sm border border-slate-200' }`} > {/* π Use the new MessageContent component */} <MessageContent message={message} /> </div>
{message.isUser && ( <div className="w-8 h-8 bg-gradient-to-r from-slate-400 to-slate-600 rounded-full flex items-center justify-center flex-shrink-0"> <User className="w-4 h-4 text-white" /> </div> )} </div> )) )} </div>
{/* Input Area (same as before) */} <div className="bg-white border-t border-slate-200 p-4"> <div className="flex space-x-3"> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} onKeyPress={handleKeyPress} placeholder="Ask for code examples, lists, or formatted content..." disabled={isStreaming} className="flex-1 border border-slate-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-slate-100 transition-all duration-200" />
{isStreaming ? ( <button onClick={stopStreaming} className="bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white px-6 py-3 rounded-xl transition-all duration-200 flex items-center space-x-2 shadow-lg" > <span className="w-2 h-2 bg-white rounded-full"></span> <span className="hidden sm:inline">Stop</span> </button> ) : ( <button onClick={sendMessage} disabled={!input.trim()} className="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 disabled:from-slate-300 disabled:to-slate-300 text-white px-6 py-3 rounded-xl transition-all duration-200 flex items-center space-x-2 shadow-lg disabled:shadow-none" > <Send className="w-4 h-4" /> <span className="hidden sm:inline">Send</span> </button> )} </div>
{isStreaming && ( <div className="mt-3 flex items-center justify-center text-sm text-slate-500"> <div className="flex space-x-1 mr-2"> <div className="w-2 h-2 bg-blue-400 rounded-full animate-bounce"></div> <div className="w-2 h-2 bg-blue-400 rounded-full animate-bounce" style={{animationDelay: '0.1s'}}></div> <div className="w-2 h-2 bg-blue-400 rounded-full animate-bounce" style={{animationDelay: '0.2s'}}></div> </div> AI is generating response... </div> )} </div> </div> </div> )}
export default App
What this complete component now includes:
- β Rich markdown rendering - Headers, lists, code blocks, emphasis
- β Professional code highlighting - Dark theme with syntax colors
- β Copy code functionality - Hover over code blocks to copy
- β Streaming compatibility - Markdown renders in real-time as text streams
- β Responsive design - All formatting works on mobile and desktop
- β User experience - Plain text for users, rich formatting for AI
π― What Changed: Before vs After
Section titled βπ― What Changed: Before vs AfterβBefore (Plain Text):
AI: "Here's a Python function:
```pythondef hello(): print("Hello!")
Steps:
- Import the function
- Call hello()
**After (Rich Formatting):**- Proper code blocks with dark background and syntax highlighting- Formatted numbered lists with proper spacing- Headers with appropriate typography- Copy buttons on code blocks- Professional appearance that matches modern AI tools
---
## π Test Your Enhanced Chat
**Try these prompts to see the formatting in action:**
1. **"Write a React component with TypeScript"** - Should show syntax-highlighted code blocks
2. **"List the top 5 JavaScript frameworks and explain each"** - Should show numbered lists with descriptions
3. **"Create a markdown guide with examples"** - Should show headers, emphasis, and various formatting
4. **"Compare `let` vs `const` vs `var` in JavaScript"** - Should show inline code highlighting and comparisons
**Success looks like:** AI responses that are beautifully formatted, easy to read, and professional-looking with proper code highlighting and typography.
---
## β¨ Lesson Recap
Outstanding work! π You've transformed your AI chat from basic text to a professional, formatted experience.
**What you've accomplished:**- π **Rich markdown rendering** - AI responses now display with proper formatting- π» **Code syntax highlighting** - Programming examples look professional- π¨ **Custom styling** - Headers, lists, and emphasis with perfect spacing- π **Copy functionality** - Users can easily copy code examples- π **Streaming compatibility** - Formatting works in real-time with streaming
**You now understand:**- π **Markdown rendering** - How to transform markdown text into styled components- π― **Component composition** - Creating reusable components for specific purposes- π¨ **Custom styling** - Tailoring markdown elements to match your design- π‘ **User experience** - Making AI responses more readable and actionable
Your chat now provides a truly professional AI experience that rivals ChatGPT, Claude, and other commercial AI tools. The combination of streaming responses and rich formatting creates an exceptional user experience!
**π Next:** [Chat History & Persistence](./ChatHistory/) - Let's add conversation memory and save chat history!