最近在写一个ai聊天对话的项目,在网上没有找到很好的资料,自己琢磨了半天,写点教程方便其他人使用。我的方法是是使用react-markdown
再配上一些插件像remark-gfm
,rehype-highlight
,highlight.js
,和一些css样式实现转换。
转化前后


代码的实现
创建一个组件ChatMessage.jsx
,在compontents文件夹
安装依赖npm install react-markdown remark-gfm
需安装 react-markdown 与 remark-gfm 这两个库。react-markdown 能把 Markdown 文本转换为 React 组件,remark-gfm 可支持 GitHub Flavored Markdown(GFM),像表格、任务列表这类。
创建一个组件用来处理消息,传入两个参数,message传入的消息,role消息的产生者
jsx
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
const ChatMessage = ({ message, role }) => {
return (
role === 'assistant' ?
<div className="chat-message">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{message}
</ReactMarkdown>
</div> :
<div className="user-message">
{message}
</div>
);
};
export default ChatMessage;
在ai聊天页面中使用这个组件
jsx
import ChatMessage from '@/compontents/ChatMessage';
{/* 消息区域 - 添加固定高度和滚动 */}
<div className="flex-1 overflow-y-auto p-4 w-full max-w-4xl mx-auto">
<div className="space-y-4 min-h-[calc(100vh-200px)]">
{messages.map(message => (
<div
key={message.id}
className={`flex flex-row ${message?.role === 'assistant' ? 'justify-start' : 'justify-end'} mb-4`}>
<div className={`max-w-[80%] rounded-2xl px-4 py-3 ${message?.role === 'assistant' ?
'bg-blue-100 rounded-bl-none' :
'bg-indigo-100 rounded-br-none'}`}>
<div className={`text-gray-800 ${message?.role === 'assistant' ? 'text-left' : 'text-right'}`}>
<ChatMessage message={message.content} role={message.role}/>
</div>
</div>
</div>
))}
<div ref={endRef} className="h-8" />
</div>
</div>
处理后的makedown格式,代码块有点丑,并且代码段
很丑,我们写一些css样式去美化我们的
美化makedown格式
创建一个markdownStyles.css
的css文件,并引入到组件ChatMessage
,觉得不太好看的话可以稍作修改。
css
/* 消息容器 - 添加平滑过渡和更现代的阴影 */
.chat-message {
margin: 12px 0;
padding: 16px;
transition: all 0.2s ease;
}
/* 标题 - 添加渐变颜色和更好的间距 */
.chat-message h1 {
color: #1a1a1a;
border-bottom: 1px solid #eaeaea;
padding-bottom: 8px;
margin-bottom: 16px;
}
.chat-message h2 {
color: #1e40af;
margin-top: 24px;
}
/* 段落 - 优化行高和字体 */
.chat-message p {
color: #4b5563;
line-height: 1.8;
font-size: 16px;
margin-bottom: 16px;
}
/* 列表 - 更精致的样式 */
.chat-message ul {
list-style-type: '• ';
padding-left: 24px;
}
.chat-message ol {
padding-left: 24px;
}
/* 代码块 - 更专业的语法高亮样式 */
.chat-message pre {
background-color: #1e293b;
color: #f8fafc;
border-radius: 8px;
font-family: 'SF Mono', Menlo, monospace;
font-size: 14px;
line-height: 1.5;
margin: 16px 0;
}
/* 内联代码 - 更突出的样式 */
.chat-message code:not(pre code) {
background-color: #f3f4f6;
color: #dc2626;
border-radius: 4px;
font-size: 90%;
}
/* 链接 - 更明显的交互效果 */
.chat-message a {
color: #2563eb;
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
.chat-message a:hover {
color: #1d4ed8;
text-decoration: underline;
}
/* 表格样式 - 新增 */
.chat-message table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
}
.chat-message th,
.chat-message td {
padding: 12px;
border: 1px solid #e5e7eb;
}
.chat-message th {
background-color: #f3f4f6;
}
/* 引用样式 - 新增 */
.chat-message blockquote {
border-left: 4px solid #2563eb;
background-color: #f8fafc;
padding: 12px 16px;
margin: 16px 0;
color: #4b5563;
}
jsx
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import './markdownStyles.css';
const ChatMessage = ({ message, role }) => {
return (
role === 'assistant' ?
<div className="chat-message">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{message}
</ReactMarkdown>
</div> :
<div className="user-message">
{message}
</div>
);
};
export default ChatMessage;

处理代码块
输入npm install highlight.js
输入npm install rehype-highlight
为了让代码在聊天界面中有不同颜色的高亮显示,你可以使用 rehype-highlight
插件,它可以帮助你实现代码块的语法高亮。
为了在代码块中实现更美观、更细致的代码高亮效果,我们可以使用 highlight.js
库。highlight.js
是一个流行的语法高亮库,支持多种编程语言。
将rehype-highlight
和highlight.js
引入组件
jsx
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
import hljs from 'highlight.js';
import './code.css';//引入代码段的css样式
import './markdownStyles.css';
const highlightCode = (node) => {//处理代码段的函数
if (node.tagName === 'pre') {
const codeNode = node.children[0];
if (codeNode.tagName === 'code') {
const language = codeNode.properties.className?.[0]?.replace('language-', '');
if (language && hljs.getLanguage(language)) {
const highlightedCode = hljs.highlight(codeNode.children[0].value, { language }).value;
codeNode.children[0].value = highlightedCode;
}
}
}
return node;
};
const ChatMessage = ({ message, role }) => {
return role === 'user' ? (
<div>{message}</div>
) : (
<div className="chat-message">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight, () => highlightCode]}
>
{message}
</ReactMarkdown>
</div>
);
};
export default ChatMessage;
这是样式code.css
,来源于highlight.js/styles/atom-one-dark.css
做出了一点修改,让代码块不是很突兀
css
pre code.hljs {
display: block;
overflow-x: auto;
border-radius: 1em;
padding: 1em;
}
code.hljs {
padding: 3px 5px
}
/*
Atom One Dark by Daniel Gamage
Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax
base: #282c34
mono-1: #abb2bf
mono-2: #818896
mono-3: #5c6370
hue-1: #56b6c2
hue-2: #61aeee
hue-3: #c678dd
hue-4: #98c379
hue-5: #e06c75
hue-5-2: #be5046
hue-6: #d19a66
hue-6-2: #e6c07b
*/
.hljs {
color: #abb2bf;
background: #282c34
}
.hljs-comment,
.hljs-quote {
color: #5c6370;
font-style: italic
}
.hljs-doctag,
.hljs-keyword,
.hljs-formula {
color: #c678dd
}
.hljs-section,
.hljs-name,
.hljs-selector-tag,
.hljs-deletion,
.hljs-subst {
color: #e06c75
}
.hljs-literal {
color: #56b6c2
}
.hljs-string,
.hljs-regexp,
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string {
color: #98c379
}
.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-type,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-number {
color: #d19a66
}
.hljs-symbol,
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-title {
color: #61aeee
}
.hljs-built_in,
.hljs-title.class_,
.hljs-class .hljs-title {
color: #e6c07b
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: bold
}
.hljs-link {
text-decoration: underline
}
在安装的highlight.js
库中有许多代码块的样式,可以直接引用或者稍作修改后引入,我是稍微修改后放入code.css
然后引入的 这是最后的成品
结语
如果有什么问题可以随时问我,我很高兴帮忙解决你的问题。