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 小时前
IObit Uninstaller Pro专业卸载,免激活版本,卸载清理注册表,彻底告别软件残留
前端·windows·后端
前端程序媛-Tian3 小时前
【dropdown组件填坑指南】—怎么实现下拉框的位置计算
前端·javascript·vue
嘉琪0013 小时前
实现视频实时马赛克
linux·前端·javascript
烛阴4 小时前
Smoothstep
前端·webgl
若梦plus4 小时前
Eslint中微内核&插件化思想的应用
前端·eslint
爱分享的程序员4 小时前
前端面试专栏-前沿技术:30.跨端开发技术(React Native、Flutter)
前端·javascript·面试
超级土豆粉4 小时前
Taro 位置相关 API 介绍
前端·javascript·react.js·taro
若梦plus4 小时前
Webpack中微内核&插件化思想的应用
前端·webpack
若梦plus4 小时前
微内核&插件化设计思想
前端
柯北(jvxiao)4 小时前
搞前端还有出路吗?如果有,在哪里?
前端·程序人生