项目地址
- 在线预览:frontend-ai-assistant-two.vercel.app/
- GitHub 源码:github.com/hewq/dify-c…
说明:当前在线版本部署在 Vercel,国内网络访问可能不稳定。如果打不开,可以直接查看 GitHub 源码并在本地启动。
前言
上一篇我们实现了 SSE 流式输出。
现在 AI 回答已经可以像 ChatGPT 一样逐步显示了:
用户提问
↓
Dify streaming
↓
Express 转发 SSE
↓
前端逐步追加回答
但是当前页面还有一个体验问题:
AI 回答只是普通纯文本展示。
这在简单问答里还能接受,但只要 AI 返回复杂内容,体验就会变差。
比如 AI 很可能返回:
# 标题
- 列表项
- 列表项
```ts
const message = 'hello'
| 字段 | 说明 |
|---|---|
| role | 消息角色 |
如果我们仍然用:
```tsx
<div>{content}</div>
这些 Markdown 内容就不会正确渲染。
所以这一篇我们来做:
Markdown 渲染 + GitHub 风格表格 + 代码高亮。
本篇目标
完成后,AI 回答支持:
1. Markdown 标题
2. 段落
3. 有序 / 无序列表
4. 表格
5. 行内代码
6. 代码块
7. 代码高亮
8. 流式输出时也能逐步渲染 Markdown
我们会使用:
react-markdown
remark-gfm
rehype-highlight
highlight.js
为什么 AI 应用需要 Markdown 渲染?
AI 应用里,Markdown 几乎是标配。
原因是 AI 经常会返回结构化内容:
- 技术解释
- 步骤说明
- 代码示例
- 表格对比
- 配置片段
- 错误排查
- 总结列表
如果只是纯文本展示,用户很难阅读。
尤其是代码块,如果没有高亮,会非常影响体验。
一个前端 AI 助手如果不能很好展示 Markdown,就会显得很像测试 Demo,而不是正式产品。
第一步:安装依赖
在项目根目录执行:
npm install react-markdown remark-gfm rehype-highlight highlight.js
几个依赖分别做什么:
react-markdown:把 Markdown 渲染成 React 组件
remark-gfm:支持 GitHub Flavored Markdown,比如表格、任务列表
rehype-highlight:代码高亮插件
highlight.js:代码高亮库和主题样式
第二步:引入代码高亮样式
在:
src/main.tsx
或者:
src/App.tsx
引入 highlight.js 的样式:
import 'highlight.js/styles/github.css'
如果你的页面是暗色主题,也可以换成:
import 'highlight.js/styles/github-dark.css'
当前项目先用浅色主题,所以选择:
import 'highlight.js/styles/github.css'
第三步:抽出 ChatMessage 组件
之前消息可能直接写在 App.tsx 里:
{messages.map((message, index) => (
<div key={index}>
<strong>{message.role === 'user' ? '你' : 'AI'}:</strong>
<div>{message.content}</div>
</div>
))}
这样后面会越来越难维护。
我们先把消息展示抽成组件。
新建:
src/components/ChatMessage.tsx
写入:
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
type ChatMessageProps = {
role: 'user' | 'assistant'
content: string
}
export function ChatMessage({ role, content }: ChatMessageProps) {
const isUser = role === 'user'
return (
<div className={`message ${isUser ? 'user' : 'ai'}`}>
<strong>{isUser ? '你' : 'AI'}:</strong>
{isUser ? (
<div className="message-text">{content}</div>
) : (
<div className="markdown-body">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight]}
>
{content}
</ReactMarkdown>
</div>
)}
</div>
)
}
这里我们做了一个区分:
用户消息:按普通文本展示
AI 消息:按 Markdown 渲染
为什么用户消息不渲染 Markdown?
因为用户输入通常就是普通问题,直接保留原始文本更符合预期。
第四步:在 App 中使用 ChatMessage
打开:
src/App.tsx
引入组件:
import { ChatMessage } from './components/ChatMessage'
把原来的消息渲染:
{messages.map((message, index) => (
<div
key={index}
className={`message ${message.role === 'user' ? 'user' : 'ai'}`}
>
<strong>{message.role === 'user' ? '你' : 'AI'}:</strong>
<div>{message.content}</div>
</div>
))}
改成:
{messages.map((message, index) => (
<ChatMessage
key={index}
role={message.role}
content={message.content}
/>
))}
这样 App.tsx 会变干净一些。
第五步:补充 Markdown 样式
在你的 CSS 文件中增加:
.message-text {
white-space: pre-wrap;
}
.markdown-body {
line-height: 1.7;
}
.markdown-body p {
margin: 0 0 10px;
}
.markdown-body p:last-child {
margin-bottom: 0;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 22px;
margin: 8px 0;
}
.markdown-body li {
margin: 4px 0;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3 {
margin: 16px 0 8px;
line-height: 1.4;
}
.markdown-body h1:first-child,
.markdown-body h2:first-child,
.markdown-body h3:first-child {
margin-top: 0;
}
.markdown-body code {
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
font-size: 0.92em;
}
.markdown-body :not(pre) > code {
background: #f3f4f6;
padding: 2px 5px;
border-radius: 4px;
}
.markdown-body pre {
margin: 12px 0;
padding: 12px;
overflow-x: auto;
border-radius: 8px;
background: #f6f8fa;
}
.markdown-body pre code {
background: transparent;
padding: 0;
}
.markdown-body table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
font-size: 14px;
}
.markdown-body th,
.markdown-body td {
border: 1px solid #e5e7eb;
padding: 8px 10px;
text-align: left;
}
.markdown-body th {
background: #f9fafb;
}
这不是最终 UI,只是一版基础 Markdown 样式。
后面做 UI 优化时,还会进一步调整整体聊天布局。
第六步:测试 Markdown 渲染
启动项目:
npm run dev:all
然后问 AI:
请用 Markdown 列出前端架构主要包括哪些内容
如果 Dify 的 Prompt 没有限制得太死,AI 可能会返回列表形式:
前端架构主要包括:
- 项目分层
- 组件设计
- 状态管理
- 权限控制
- 性能优化
- 工程化
- 可观测性
页面应该正确显示为列表,而不是一整段纯文本。
再测试代码块:
请给我一个简单的 TypeScript 类型示例
期望返回类似:
type Message = {
role: 'user' | 'assistant'
content: string
}
页面应该能显示代码块,并有基础高亮。
第七步:流式输出和 Markdown 渲染会冲突吗?
不会。
因为我们前端的内容仍然是一个字符串:
content: current.content + chunk
每次 chunk 更新后,React 会重新渲染:
<ReactMarkdown>{content}</ReactMarkdown>
所以 Markdown 会随着内容逐步变完整。
不过流式 Markdown 有一个小现象:
当代码块还没输出完整时,比如只输出到:
```ts
const a =
这时 Markdown 可能暂时渲染得不完整。
等后续内容继续输出,代码块闭合后,展示会恢复正常。
这是流式 Markdown 的正常现象。
第八步:为什么选择 react-markdown?
在 React 项目里渲染 Markdown 有很多方案。
我这里选择 react-markdown,原因是:
1. React 生态常用
2. 插件体系比较成熟
3. 可以配合 remark / rehype
4. 支持自定义组件
5. 后续扩展方便
比如后续你可以自定义:
- a 标签打开方式
- code 代码块样式
- table 容器滚动
- 图片渲染
- 引用块样式
- 复制代码按钮
当前阶段先用基础能力即可。
第九步:为什么需要 remark-gfm?
默认 Markdown 不一定完整支持 GitHub 风格语法。
remark-gfm 可以支持:
1. 表格
2. 删除线
3. 任务列表
4. 自动链接
AI 回答里很容易生成表格,比如:
| 能力 | 说明 |
| --- | --- |
| 流式输出 | 提升等待体验 |
| 引用来源 | 增强可信度 |
| 多会话 | 支持长期使用 |
没有 remark-gfm 的话,表格可能不会按表格渲染。
第十步:为什么需要代码高亮?
技术类 AI 助手经常输出代码。
如果没有代码高亮,代码块只是普通文本,可读性比较差。
我们这里使用:
rehype-highlight
highlight.js
它会自动识别代码语言:
```ts
const message: string = 'hello'
并添加对应高亮样式。
---
## 当前代码结构
这一篇结束后,项目大概变成:
```text
src/
api/
dify.ts
difyStream.ts
components/
ChatMessage.tsx
types/
chat.ts
App.tsx
App.css
main.tsx
相比之前,多了:
components/ChatMessage.tsx
消息展示逻辑从 App.tsx 抽了出来。
这也是后续组件拆分的第一步。
常见问题
1. 代码块没有高亮
检查是否引入了样式:
import 'highlight.js/styles/github.css'
没有样式的话,即使 rehype-highlight 生效了,也看不出高亮效果。
2. 表格没有渲染
检查是否添加了:
remarkPlugins={[remarkGfm]}
3. TypeScript 报 children 类型问题
确认 ReactMarkdown 的用法是:
<ReactMarkdown>{content}</ReactMarkdown>
不要写成旧版本 API。
4. 流式输出时代码块闪动
这是正常现象。
因为 AI 内容还没输出完整时,Markdown 结构可能暂时不闭合。
等输出结束后会稳定。
当前版本还有什么不足?
这一篇解决了 Markdown 展示问题,但 RAG 产品还有一个更重要的问题:
答案到底来自哪里?
现在用户只能看到 AI 回答,看不到引用来源。
而一个知识库问答系统,最好能告诉用户:
这段回答引用自 frontend-notes.md
Dify 在流式结束事件中会返回 retriever_resources,我们下一篇就来解析它,并在回答下方展示引用来源。
本篇总结
这一篇我们完成了 AI 回答展示体验的升级:
1. 安装 react-markdown
2. 支持 GitHub Flavored Markdown
3. 接入 rehype-highlight
4. 引入 highlight.js 样式
5. 抽出 ChatMessage 组件
6. 用户消息按纯文本展示
7. AI 消息按 Markdown 渲染
8. 支持代码块和表格展示
现在项目已经不再只是“能显示回答”,而是开始具备正式 AI 产品的阅读体验。
下一篇我们继续做 RAG 产品最关键的可信度能力:
展示知识库引用来源。