Skip to content

AI核心流式聊天逻辑的整体解决方案: 支持自定义任何类型富文本渲染模板,支持开箱即用的快速迭代发布

Notifications You must be signed in to change notification settings

yaderick/ai-chat-core

Repository files navigation

Chat Stream Core

🎯 基于 Dify 提取的极简流式聊天核心模块 | 高度可自定义渲染效果和 UI | 核心流程已搭建,快速开发定制化聊天界面

🌟 核心优势

✨ 极简核心模块

  • 轻量级设计:只包含流式聊天的核心逻辑,无冗余代码
  • 开箱即用:核心流程已完整搭建,可直接使用或快速定制
  • 高可复用:模块化设计,轻松集成到任何 React 项目中

🎨 高度可自定义

  • UI 完全可控:所有组件均可替换和自定义样式
  • 渲染效果可定制:支持自定义 Markdown 渲染、代码块样式、消息气泡等
  • 灵活的组件系统:基于 React Context,轻松扩展功能

🚀 核心流程已搭建

  • SSE 解析:完整的流式数据解析逻辑
  • Token 流式传输:实时更新消息内容
  • 状态管理:完整的对话状态和会话管理
  • Markdown 渲染:支持丰富的 Markdown 块渲染

📦 核心功能

1. SSE 解析 (Server-Sent Events)

// 核心解析逻辑已封装,支持:
- 实时解析 SSE 流式数据
- 增量数据缓冲和处理
- 自动处理不完整的数据包
- 支持 message  agent_message 事件类型

2. Token 流式传输

// 流畅的流式体验:
- 实时接收和显示流式 token
- 增量更新消息内容
- 自动管理消息 ID 和会话 ID

3. 问题/答案状态管理

// 完整的状态管理:
- 对话列表管理
- 响应状态跟踪(isResponding)
- 会话 ID 自动管理
- 消息时间戳记录

4. Markdown 块渲染

// 丰富的渲染能力:
- 代码块高亮(支持多种语言)
- 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 install

开发

npm run dev

💡 使用方式

方式一:直接使用(开箱即用)

import ChatWrapper from '@/components/chat-wrapper'

function App() {
  return <ChatWrapper />
}

方式二:使用核心 Hook(高度自定义)

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

🎨 自定义渲染示例

自定义 Markdown 渲染

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

🔧 核心 API

useChat Hook

const {
  chatList,              // Message[] - 消息列表
  handleSend,            // (url, data) => Promise<void> - 发送消息
  currentConversationId, // string - 当前会话 ID
  isResponding          // boolean - 是否正在响应
} = useChat()

handleSend 方法

await handleSend(
  '/api/v1/chat-messages',  // API 端点
  {
    query: '用户消息',        // 必需:用户消息
    // 其他自定义参数...
  }
)

Message 类型

interface Message {
  id: string           // 消息 ID
  content: string      // 消息内容
  isAnswer: boolean    // 是否为 AI 回答
  timestamp?: number   // 时间戳
}

📖 Markdown 块示例

代码块

```javascript
console.log('Hello, World!')
```

ECharts 图表

```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" }
]
```

⚙️ 配置

API 代理(vite.config.ts)

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

SSE 数据格式

API 应返回以下格式的 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 重新渲染 → 显示流式内容

📝 开发建议

  1. 快速原型:直接使用 ChatWrapper 组件
  2. 渐进式定制:先替换单个组件(如 answer.tsx
  3. 完全自定义:使用 useChat Hook 构建自己的 UI
  4. 扩展功能:在 markdown-blocks 中添加新的块类型

📄 许可证

本项目基于 Dify 的核心逻辑提取,请遵循相应的开源许可证。

⚖️ 侵权联系删除

如果您认为本项目中的内容侵犯了您的权益,请通过以下方式联系我们:

  • Issue: 在 GitHub 仓库中提交 Issue,说明侵权内容的具体位置和原因
  • 邮箱: 请通过 GitHub Issue 联系我们

我们会在收到通知后尽快核实并处理。如确认存在侵权,我们将立即删除相关内容。

🤝 贡献

欢迎提交 Issue 和 Pull Request!

📚 相关链接

About

AI核心流式聊天逻辑的整体解决方案: 支持自定义任何类型富文本渲染模板,支持开箱即用的快速迭代发布

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages