Skip to content

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.

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 formatting
function 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

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.


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

Start your servers:

Backend:

Terminal window
cd openai-backend
npm run dev

Frontend:

Terminal window
cd openai-frontend
npm run dev

Test with these prompts that generate formatted content:

  1. β€œWrite a Python function to calculate fibonacci numbers”

    • Should show properly highlighted code blocks
  2. β€œExplain the benefits of React in a bulleted list”

    • Should render a nice bulleted list
  3. β€œGive me a step-by-step guide to deploy a Node.js app”

    • Should show numbered lists and headers
  4. β€œ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.


Let’s add a copy button to code blocks for better user experience. Update your code component:

// πŸ†• Enhanced code component with copy functionality
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>
)
},

What this adds: A β€œCopy” button appears when you hover over code blocks, making it easy for users to copy code examples.


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 formatting
function 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

Before (Plain Text):

AI: "Here's a Python function:
```python
def hello():
print("Hello!")

Steps:

  1. Import the function
  2. 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!