🎯 基于 Dify 提取的极简流式聊天核心模块 | 高度可自定义渲染效果和 UI | 核心流程已搭建,快速开发定制化聊天界面
- 轻量级设计:只包含流式聊天的核心逻辑,无冗余代码
- 开箱即用:核心流程已完整搭建,可直接使用或快速定制
- 高可复用:模块化设计,轻松集成到任何 React 项目中
- UI 完全可控:所有组件均可替换和自定义样式
- 渲染效果可定制:支持自定义 Markdown 渲染、代码块样式、消息气泡等
- 灵活的组件系统:基于 React Context,轻松扩展功能
- ✅ SSE 解析:完整的流式数据解析逻辑
- ✅ Token 流式传输:实时更新消息内容
- ✅ 状态管理:完整的对话状态和会话管理
- ✅ Markdown 渲染:支持丰富的 Markdown 块渲染
// 核心解析逻辑已封装,支持:
- 实时解析 SSE 流式数据
- 增量数据缓冲和处理
- 自动处理不完整的数据包
- 支持 message 和 agent_message 事件类型// 流畅的流式体验:
- 实时接收和显示流式 token
- 增量更新消息内容
- 自动管理消息 ID 和会话 ID// 完整的状态管理:
- 对话列表管理
- 响应状态跟踪(isResponding)
- 会话 ID 自动管理
- 消息时间戳记录// 丰富的渲染能力:
- 代码块高亮(支持多种语言)
- LaTeX 数学公式(KaTeX)
- ECharts 图表渲染
- 交互式组件(选择框、按钮组)
- GitHub Flavored Markdown- React 19 + TypeScript - 现代化开发体验
- Vite - 极速构建工具
- Tailwind CSS - 实用优先的 CSS 框架(可替换)
- react-markdown - Markdown 渲染(可自定义)
chat-stream-core/
├── src/
│ ├── components/ # ⚡ 可完全自定义的 UI 组件
│ │ ├── answer.tsx # 答案消息组件(可替换)
│ │ ├── question.tsx # 问题消息组件(可替换)
│ │ ├── chat.tsx # 聊天列表组件(可替换)
│ │ ├── chat-input.tsx # 输入框组件(可替换)
│ │ ├── react-markdown.tsx # Markdown 渲染器(可自定义)
│ │ └── markdown-blocks/ # Markdown 块组件(可扩展)
│ ├── contexts/
│ │ └── ChatContext.tsx # 聊天上下文(核心状态)
│ ├── hook/
│ │ └── useChat.ts # 🎯 核心 Hook(核心逻辑)
│ ├── service/
│ │ └── chat-api.ts # 🎯 SSE 解析服务(核心逻辑)
│ └── types/
│ └── message.ts # 类型定义
npm install
# 或
pnpm installnpm run devimport ChatWrapper from '@/components/chat-wrapper'
function App() {
return <ChatWrapper />
}import useChat from '@/hook/useChat'
import { ChatProvider } from '@/contexts/ChatContext'
function MyCustomChat() {
const chatContext = useChat()
// 自定义发送逻辑
const handleSend = async (message: string) => {
await chatContext.handleSend('/api/v1/chat-messages', {
query: message,
// 添加自定义参数
})
}
return (
<ChatProvider value={chatContext}>
{/* 🎨 完全自定义的 UI */}
<div className="my-custom-chat-container">
{chatContext.chatList.map(msg => (
<div key={msg.id} className={msg.isAnswer ? 'ai-message' : 'user-message'}>
{/* 自定义消息渲染 */}
{msg.content}
</div>
))}
<input
onSend={handleSend}
disabled={chatContext.isResponding}
/>
</div>
</ChatProvider>
)
}// 1. 使用核心 Hook
import useChat from '@/hook/useChat'
import { ChatProvider } from '@/contexts/ChatContext'
// 2. 自定义答案组件
function MyCustomAnswer({ content }: { content: string }) {
return (
<div className="my-answer-style">
{/* 完全自定义的样式和布局 */}
{content}
</div>
)
}
// 3. 在 Chat 组件中使用
function MyChat() {
const chatContext = useChat()
return (
<ChatProvider value={chatContext}>
{chatContext.chatList.map(msg =>
msg.isAnswer ? (
<MyCustomAnswer key={msg.id} content={msg.content} />
) : (
<Question key={msg.id} content={msg.content} />
)
)}
</ChatProvider>
)
}import ReactMarkdownWrapper from '@/components/react-markdown'
// 自定义代码块样式
const customComponents = {
code: ({ className, children }: any) => {
const language = className?.replace('language-', '')
return (
<div className="my-custom-code-block">
<span className="language-tag">{language}</span>
<pre>{children}</pre>
</div>
)
},
// 自定义其他组件...
}
<ReactMarkdownWrapper
latexContent={content}
customComponents={customComponents}
/>// 替换 answer.tsx 或 question.tsx
function CustomAnswer({ content }: { content: string }) {
return (
<div className="custom-answer-bubble">
{/* 你的自定义样式 */}
<div className="avatar">AI</div>
<div className="content">{content}</div>
</div>
)
}// 替换 chat-input.tsx
function CustomInput({ onSend, disabled }: any) {
const [value, setValue] = useState('')
return (
<div className="custom-input-wrapper">
<textarea
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="输入消息..."
/>
<button onClick={() => onSend(value)} disabled={disabled}>
发送
</button>
</div>
)
}const {
chatList, // Message[] - 消息列表
handleSend, // (url, data) => Promise<void> - 发送消息
currentConversationId, // string - 当前会话 ID
isResponding // boolean - 是否正在响应
} = useChat()await handleSend(
'/api/v1/chat-messages', // API 端点
{
query: '用户消息', // 必需:用户消息
// 其他自定义参数...
}
)interface Message {
id: string // 消息 ID
content: string // 消息内容
isAnswer: boolean // 是否为 AI 回答
timestamp?: number // 时间戳
}```javascript
console.log('Hello, World!')
``````echarts
{
"xAxis": { "type": "category", "data": ["Mon", "Tue", "Wed"] },
"yAxis": { "type": "value" },
"series": [{ "data": [120, 200, 150], "type": "bar" }]
}
``````select
{
"options": [
{ "label": "选项1", "value": "value1" },
{ "label": "选项2", "value": "value2" }
],
"apiUrl": "/api/v1/chat-messages"
}
``````button
[
{ "text": "确认", "action": "confirm" },
{ "text": "取消", "action": "cancel" }
]
```export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://your-api-server:port',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})如果需要添加认证信息(如 Authorization token),有两种方式:
方式一:修改 src/service/chat-api.ts,添加默认 headers
export const ssePost = async (url: string, options: SSEPostOptions = {}) => {
const { body, onData, onCompleted, getAbortController, headers = {} } = options
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 添加认证信息(建议使用环境变量)
'Authorization': `Bearer ${import.meta.env.VITE_API_TOKEN || ''}`,
...headers, // 允许通过 options.headers 覆盖
},
// ...
})
}方式二:在调用 ssePost 时传递 headers
修改 src/hook/useChat.ts 中的 ssePost 调用:
await ssePost(url, {
body: bodyParams,
headers: {
'Authorization': 'Bearer your-token-here'
},
onData: (chunk, isFirst, info) => { /* ... */ },
onCompleted: () => { /* ... */ }
})推荐使用环境变量:创建 .env 文件(不要提交到版本控制)
VITE_API_TOKEN=your-token-here
VITE_API_BASE_URL=http://your-api-server:portAPI 应返回以下格式的 SSE 数据:
data: {"event": "message", "answer": "token", "conversation_id": "xxx", "id": "msg_id"}
data: {"event": "message", "answer": " token", "conversation_id": "xxx", "id": "msg_id"}
...
| 特性 | 说明 |
|---|---|
| 🎯 极简核心 | 只包含流式聊天核心逻辑,无冗余代码 |
| 🎨 高度可定制 | 所有 UI 组件均可替换和自定义 |
| 🚀 开箱即用 | 核心流程已搭建,可直接使用 |
| 🔧 高可复用 | 模块化设计,轻松集成到任何项目 |
| 📦 轻量级 | 依赖精简,构建体积小 |
| ⚡ TypeScript | 完整的类型支持,开发体验好 |
用户输入
↓
useChat.handleSend()
↓
chat-api.ssePost() → SSE 流式请求
↓
handleStream() → 解析 SSE 数据
↓
onData() → 增量更新消息内容
↓
setChatList() → 更新状态
↓
React 重新渲染 → 显示流式内容
- 快速原型:直接使用
ChatWrapper组件 - 渐进式定制:先替换单个组件(如
answer.tsx) - 完全自定义:使用
useChatHook 构建自己的 UI - 扩展功能:在
markdown-blocks中添加新的块类型
本项目基于 Dify 的核心逻辑提取,请遵循相应的开源许可证。
如果您认为本项目中的内容侵犯了您的权益,请通过以下方式联系我们:
- Issue: 在 GitHub 仓库中提交 Issue,说明侵权内容的具体位置和原因
- 邮箱: 请通过 GitHub Issue 联系我们
我们会在收到通知后尽快核实并处理。如确认存在侵权,我们将立即删除相关内容。
欢迎提交 Issue 和 Pull Request!
- Dify - 原始项目
- React Markdown - Markdown 渲染库
- ECharts - 图表库