AI对话数据管理useChat 实现 SSE hook封装

useChat Hook

概述

useChat 是一个功能完整的 React Hook,用于构建支持流式响应的聊天应用。它提供了消息管理、流式数据处理、错误处理、重试机制、会话管理等核心功能。

核心架构

1. 类型系统

1.1 基础消息结构
typescript 复制代码
interface Message<TMetadata = Record<string, unknown>> {
  id: string;           // 唯一标识符
  role: "user" | "assistant";  // 消息角色
  content: string;      // 消息内容
  metadata?: TMetadata;  // 扩展元数据
}
1.2 配置选项
typescript 复制代码
interface ChatOptions<TMetadata> {
  sendFullHistory?: boolean;      // 是否发送完整历史
  maxHistoryLength?: number;      // 最大历史长度
  headers?: Record<string, string>; // 自定义请求头
  timeoutMs?: number;              // 超时时间
  maxRetries?: number;            // 最大重试次数
  retryDelayMs?: number;          // 重试延迟
  parseChunk?: (data: string) => any; // 数据解析函数
  onStatusChange?: (status: ChatStatus) => void; // 状态变更回调
  onChunkError?: (error: Error) => void; // 块错误处理
  sessionId?: string;             // 会话ID
  metadata?: TMetadata;           // 全局元数据
}
1.3 状态管理
typescript 复制代码
type ChatStatus = "ready" | "submitted" | "streaming" | "error";

interface ChatError {
  message: string;
  code: "TIMEOUT" | "ABORT" | "NETWORK" | "HTTP" | "PARSE" | "UNKNOWN";
  details?: unknown;
}

核心实现原理

2.1 流式数据处理架构

2.1.1 TransformStream 管道设计

useChat 采用现代 Web Streams API 构建了一个高效的数据处理管道:

scss 复制代码
ReadableStream (HTTP响应) 
    ↓
TextDecoderStream (二进制→文本)
    ↓
LineSplitterStream (按行分割)
    ↓
SSEParserStream (SSE解析)
    ↓
React State (消息更新)
2.1.2 LineSplitterStream 实现
typescript 复制代码
function createLineSplitterStream(): TransformStream<string, string> {
  let buffer = "";
  return new TransformStream<string, string>({
    transform(chunk, controller) {
      buffer += chunk;
      const lines = buffer.split(/\r?\n/);
      buffer = lines.pop() || "";
      for (const line of lines) {
        if (line) controller.enqueue(line);
      }
    },
    flush(controller) {
      if (buffer) controller.enqueue(buffer);
    },
  });
}

工作原理

  • 维护一个缓冲区累积接收到的文本块
  • 使用正则表达式 \r?\n 分割行
  • 保留不完整的最后一行到缓冲区
  • 流结束时推送剩余数据
2.1.3 SSEParserStream 实现
typescript 复制代码
function createSSEParserStream<TMetadata>(
  parseChunk: (data: string) => any,
  onChunkError?: (error: Error) => void
): TransformStream<string, string> {
  return new TransformStream<string, string>({
    async transform(line, controller) {
      const trimmedLine = line.trim();
      if (trimmedLine.startsWith("data:")) {
        const data = trimmedLine.slice(5).trim();
        if (data && data !== "[DONE]") {
          try {
            const content = parseChunk(data);
            if (content !== null) controller.enqueue(content);
          } catch (err) {
            onChunkError?.(err as Error);
          }
        }
      }
    },
  });
}

SSE格式解析

  • 识别 data: 前缀的行
  • 过滤空数据和 [DONE] 标记
  • 支持自定义解析函数
  • 错误隔离处理

2.2 状态管理机制

2.2.1 核心状态
typescript 复制代码
const [messages, setMessages] = useState<Message<TMetadata>[]>([]);
const [status, setStatus] = useState<ChatStatus>("ready");
const [error, setError] = useState<ChatError | null>(null);
const [currentSessionId, setCurrentSessionId] = useState<string | undefined>();
2.2.2 引用管理
typescript 复制代码
const abortControllerRef = useRef<AbortController | null>(null);
const lastRequestRef = useRef<{
  message: string;
  options: Partial<ChatOptions<TMetadata>>;
} | null>(null);

2.3 消息发送流程

2.3.1 完整生命周期
  1. 验证阶段:检查状态和消息有效性
  2. 状态更新:设置 "submitted" 状态
  3. 消息构建:创建用户消息和空助手消息
  4. 配置合并:合并全局配置和局部配置
  5. 历史处理:根据配置决定发送的历史消息
  6. 重试循环:支持指数退避重试
  7. 流式处理:通过管道处理响应数据
  8. 状态同步:实时更新消息内容和状态
2.3.2 流式数据处理流程
typescript 复制代码
// 1. 创建桥接流
const readableStream = new ReadableStream<Uint8Array>({
  start(controller) {
    const pump = async () => {
      try {
        const { done, value } = await reader.read();
        if (done) controller.close();
        else {
          controller.enqueue(value);
          pump();
        }
      } catch (err) {
        controller.error(err);
      }
    };
    pump();
  },
});

// 2. 构建处理管道
const pipeline = readableStream
  .pipeThrough(new TextDecoderStream())
  .pipeThrough(createLineSplitterStream())
  .pipeThrough(createSSEParserStream(...));

// 3. 消费处理结果
const processedReader = pipeline.getReader();
while (true) {
  const { done, value } = await processedReader.read();
  if (done) break;
  if (value) {
    setMessages(prev => prev.map(msg => 
      msg.id === assistantMessageId 
        ? { ...msg, content: msg.content + value }
        : msg
    ));
  }
}

2.4 错误处理机制

2.4.1 错误分类
  • TIMEOUT: 请求超时
  • ABORT: 用户主动取消
  • NETWORK: 网络错误
  • HTTP: HTTP状态错误
  • PARSE: 数据解析错误
  • UNKNOWN: 未知错误
2.4.2 重试策略
typescript 复制代码
let attempt = 0;
while (attempt < mergedOptions.maxRetries) {
  try {
    // 执行请求
    break; // 成功则退出循环
  } catch (err) {
    if (isAbortOrTimeout(err)) {
      // 不可重试错误,直接返回
      return;
    }
    attempt++;
    if (attempt < maxRetries) {
      await delay(retryDelayMs);
    }
  }
}

2.5 会话管理

2.5.1 会话ID处理
  • 支持通过 options.sessionId 初始化会话
  • 动态切换会话时自动清空消息
  • 支持通过 setMessages 更新会话ID
2.5.2 会话持久化
typescript 复制代码
useEffect(() => {
  if (options.sessionId !== currentSessionId) {
    clearMessages();
    setCurrentSessionId(options.sessionId);
  }
}, [options.sessionId]);

高级特性

3.1 元数据支持

通过 TypeScript 泛型支持任意类型的元数据:

typescript 复制代码
interface MyMetadata {
  confidence?: number;
  sources?: string[];
  timestamp?: Date;
}

const { sendMessage } = useChat<MyMetadata>('/api/chat', {
  metadata: { confidence: 0.95 }
});

3.2 自定义数据解析

支持自定义 parseChunk 函数处理不同格式的响应:

typescript 复制代码
const parseChunk = (data: string) => {
  const parsed = JSON.parse(data);
  if (parsed.type === 'delta') {
    return parsed.content;
  }
  return null;
};

3.3 生命周期钩子

  • onStatusChange: 状态变更通知
  • onChunkError: 块级错误处理

使用示例

4.1 基础用法

typescript 复制代码
import useChat from '@/hooks/use-chat';

function ChatComponent() {
  const { messages, status, error, sendMessage } = useChat('/api/chat');

  const handleSend = async (message: string) => {
    await sendMessage(message);
  };

  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id} className={msg.role}>
          {msg.content}
        </div>
      ))}
    </div>
  );
}

4.2 高级配置

typescript 复制代码
const { messages, status, sendMessage, abortRequest } = useChat('/api/chat', {
  sendFullHistory: true,
  maxHistoryLength: 50,
  timeoutMs: 10000,
  maxRetries: 3,
  retryDelayMs: 1000,
  headers: { 'Authorization': 'Bearer token' },
  onStatusChange: (status) => console.log('Status:', status),
  parseChunk: (data) => {
    try {
      const parsed = JSON.parse(data);
      return parsed.choices[0]?.delta?.content || null;
    } catch {
      return null;
    }
  }
});

性能优化

5.1 内存管理

  • 自动清理超时的 AbortController
  • 组件卸载时自动中止请求
  • 使用 useCallback 优化重渲染

5.2 流式处理优势

  • 内存占用恒定,不受响应大小影响
  • 实时更新,无需等待完整响应
  • 支持任意大小的响应数据

错误调试

6.1 调试技巧

typescript 复制代码
const { error, status } = useChat('/api/chat', {
  onStatusChange: console.log,
  onChunkError: console.error,
});

// 错误处理
if (error) {
  console.error('Chat error:', error.code, error.message, error.details);
}

6.2 常见问题

  1. 网络超时 :调整 timeoutMs 配置
  2. CORS错误:检查服务器配置
  3. 解析错误 :验证 parseChunk 函数实现
  4. 状态异常 :检查 status 状态机

扩展指南

7.1 集成其他UI库

支持无缝集成各种UI组件库,只需处理返回的 messagesstatus

7.2 自定义存储

通过 setMessagesgetMessages 实现自定义存储:

typescript 复制代码
// 保存到localStorage
useEffect(() => {
  localStorage.setItem('chat', JSON.stringify(messages));
}, [messages]);

// 从localStorage恢复
const loadMessages = () => {
  const saved = localStorage.getItem('chat');
  if (saved) setMessages(JSON.parse(saved));
};

API 参考

useChat 参数

参数名 类型 默认值 描述
apiUrl string - 聊天API端点
options ChatOptions {} 配置选项

返回值

属性名 类型 描述
messages Message[] 当前消息列表
status ChatStatus 当前状态
error ChatError | null 错误信息
sendMessage function 发送消息函数
abortRequest function 中止当前请求
clearMessages function 清空消息列表
retry function 重试上次请求
setMessages function 设置消息列表
resetConversation function 重置会话
相关推荐
鹿心肺语3 小时前
前端HTML转PDF的两种主流方案深度解析
前端·javascript
海石3 小时前
去到比北方更北的地方—2025年终总结
前端·ai编程·年终总结
一个懒人懒人3 小时前
Promise async/await与fetch的概念
前端·javascript·html
Mintopia3 小时前
Web 安全与反编译源码下的权限设计:构筑前后端一致的防护体系
前端·安全
输出输入3 小时前
前端核心技术
开发语言·前端
Mintopia4 小时前
Web 安全与反编译源码下的权限设计:构建前后端一体的信任防线
前端·安全·编译原理
林深现海4 小时前
Jetson Orin nano/nx刷机后无法打开chrome/firefox浏览器
前端·chrome·firefox
黄诂多4 小时前
APP原生与H5互调Bridge技术原理及基础使用
前端
前端市界4 小时前
用 React 手搓一个 3D 翻页书籍组件,呼吸海浪式翻页,交互体验带感!
前端·架构·github
文艺理科生4 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构