Project: Markdown-Enabled Stream Chatbot
In this project, we will build a production-ready streaming chatbot. The interface will render raw markdown outputs (including tables and lists) and syntax-highlight code snippets with a copy-to-clipboard action button.
1. Installation of Rendering libraries
To render markdown and highlight code syntax, install these packages:
# Install react-markdown and code highlighter plugins
npm install react-markdown remark-gfm2. Implementing the React Chat Component
Create the chat interface module:
// app/chatbot/MarkdownChat.tsx
"use client";
import React from "react";
import { useChat } from "ai/react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
export default function MarkdownChat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/chat",
});
// Helper function to handle code clipboard copying
function copyToClipboard(text: string) {
navigator.clipboard.writeText(text);
alert("Code copied to clipboard!");
}
return (
<div className="flex flex-col h-[650px] max-w-2xl mx-auto border rounded-2xl bg-white shadow-lg p-6">
<h2 className="text-xl font-bold border-b pb-3 mb-4">Stream Chat Console</h2>
{/* Message History Container */}
<div className="flex-1 overflow-y-auto space-y-6 pr-2">
{messages.map((message) => (
<div key={message.id} className={`flex ${message.role === "user" ? "justify-end" : "justify-start"}`}>
<div className={`p-4 rounded-2xl text-sm leading-relaxed max-w-xl ${message.role === "user" ? "bg-indigo-600 text-white" : "bg-gray-50 text-gray-800 border"}`}>
<span className="font-semibold block text-xs uppercase opacity-75 mb-2">
{message.role === "user" ? "You" : "AI Assistant"}
</span>
{/* Render Markdown with GFM plugins */}
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
code({ node, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || "");
const rawCodeText = String(children).replace(/\n$/, "");
return match ? (
<div className="relative my-2 rounded-lg bg-gray-950 text-gray-200 p-4 overflow-x-auto text-xs">
<button
onClick={() => copyToClipboard(rawCodeText)}
className="absolute right-2 top-2 bg-gray-800 text-gray-400 hover:text-white px-2 py-1 rounded text-[10px]"
>
Copy
</button>
<pre className="font-mono">{rawCodeText}</pre>
</div>
) : (
<code className="bg-gray-200 px-1 py-0.5 rounded font-mono text-xs" {...props}>
{children}
</code>
);
}
}}
>
{message.content}
</ReactMarkdown>
</div>
</div>
))}
</div>
{/* Input panel form */}
<form onSubmit={handleSubmit} className="mt-4 flex gap-2 border-t pt-4">
<input
value={input}
onChange={handleInputChange}
className="flex-1 border px-4 py-3 rounded-xl text-sm focus:outline-none focus:border-indigo-600"
placeholder="Ask for sample code structures..."
/>
<button type="submit" className="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold px-6 py-3 rounded-xl text-sm transition">
Submit
</button>
</form>
</div>
);
}Published on Last updated: