Ir al contenido

Formating Ai Responses

Esta página aún no está disponible en tu idioma.

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

🔄 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.


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:

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.


📋 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 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!