/** * AI 桥接服务 - 流式适配器 * 负责 SSE 到 AI SDK 协议转换,保持与现有系统的兼容性 */ import type { ReadableStream } from 'stream/web' // SSE 事件类型 interface SSEEvent { data: string event?: string id?: string retry?: number } // 流式数据处理配置 interface StreamAdapterConfig { enableVisibilityOptimization?: boolean enablePerformanceTracking?: boolean bufferSize?: number } /** * 流式适配器类 * 将 SSE 响应转换为 AI SDK 兼容的格式 */ export class StreamAdapter { private buffer: string = '' private isVisible: boolean = true private config: StreamAdapterConfig private performanceMetrics: { chunkCount: number totalBytes: number startTime: number } constructor(config: StreamAdapterConfig = {}) { this.config = { enableVisibilityOptimization: true, enablePerformanceTracking: true, bufferSize: 1024, ...config } this.performanceMetrics = { chunkCount: 0, totalBytes: 0, startTime: Date.now() } // 监听页面可见性变化 if (this.config.enableVisibilityOptimization) { this.setupVisibilityListener() } } /** * 设置页面可见性监听器 */ private setupVisibilityListener(): void { const handleVisibilityChange = () => { const wasVisible = this.isVisible this.isVisible = !document.hidden if (!wasVisible && this.isVisible) { console.log('[AI Bridge] 页面重新可见,重新连接流') } else if (wasVisible && !this.isVisible) { console.log('[AI Bridge] 页面进入后台,优化性能') } } document.addEventListener('visibilitychange', handleVisibilityChange) } /** * 将 SSE ReadableStream 转换为 AI SDK 可消费的异步迭代器 */ async *convertSSEResponse(sseStream: ReadableStream): AsyncIterable { console.log('[AI Bridge] 开始转换 SSE 响应') const reader = sseStream.getReader() const decoder = new TextDecoder() let isFirstChunk = true try { while (true) { const { done, value } = await reader.read() if (done) break const chunk = decoder.decode(value, { stream: true }) this.buffer += chunk this.performanceMetrics.totalBytes += chunk.length // 提取完整的 SSE 事件(以 \n\n 分隔) const events = this.buffer.split('\n\n') this.buffer = events.pop() || '' // 保存不完整的事件 for (const event of events) { if (event.trim()) { const parsedEvent = this.parseSSEEvent(event) if (parsedEvent && this.shouldProcessEvent(parsedEvent)) { const content = this.extractContent(parsedEvent) if (content) { yield content this.performanceMetrics.chunkCount++ } } } } // 第一块数据特殊处理 if (isFirstChunk) { console.log('[AI Bridge] 接收到第一块数据') isFirstChunk = false } } // 处理缓冲区中剩余的数据 if (this.buffer.trim()) { const lastEvent = this.parseSSEEvent(this.buffer) if (lastEvent) { const content = this.extractContent(lastEvent) if (content) { yield content } } } } catch (error) { console.error('[AI Bridge] SSE 转换错误:', error) throw error } finally { reader.releaseLock() this.logPerformanceMetrics() } } /** * 解析 SSE 事件 */ private parseSSEEvent(event: string): SSEEvent | null { const lines = event.split('\n') const parsedEvent: SSEEvent = { data: '' } for (const line of lines) { if (line.startsWith('data: ')) { parsedEvent.data = line.substring(6) } else if (line.startsWith('event: ')) { parsedEvent.event = line.substring(7) } else if (line.startsWith('id: ')) { parsedEvent.id = line.substring(4) } else if (line.startsWith('retry: ')) { parsedEvent.retry = parseInt(line.substring(8), 10) } } return parsedEvent.data ? parsedEvent : null } /** * 判断是否应该处理此事件 */ private shouldProcessEvent(event: SSEEvent): boolean { // 页面不可见时跳过非关键事件 if (!this.isVisible && this.config.enableVisibilityOptimization) { return event.event !== 'ping' } return true } /** * 从 SSE 事件中提取内容 */ private extractContent(event: SSEEvent): string | null { try { // 尝试解析 JSON 格式 const data = JSON.parse(event.data) // 处理不同的响应格式 if (data.content) { return data.content } else if (data.text) { return data.text } else if (data.message?.content) { return data.message.content } else if (data.receive?.content) { return data.receive.content } else if (data.delta) { return data.delta } // 如果是纯文本数据 if (typeof data === 'string') { return data } return null } catch { // 非 JSON 数据直接返回 return event.data } } /** * 处理流式增量数据 */ processStreamingDelta(newFullContent: string, previousContent: string = ''): string { if (newFullContent.startsWith(previousContent)) { // 正常情况:增量更新 return newFullContent.slice(previousContent.length) } else { // 异常情况:内容乱序,返回完整内容 console.warn('[AI Bridge] 流式内容乱序,使用完整内容') return newFullContent } } /** * 获取性能指标 */ getPerformanceMetrics() { const duration = Date.now() - this.performanceMetrics.startTime return { ...this.performanceMetrics, duration, averageChunkSize: this.performanceMetrics.chunkCount > 0 ? this.performanceMetrics.totalBytes / this.performanceMetrics.chunkCount : 0, chunksPerSecond: this.performanceMetrics.chunkCount / (duration / 1000) } } /** * 记录性能指标 */ private logPerformanceMetrics(): void { if (this.config.enablePerformanceTracking) { const metrics = this.getPerformanceMetrics() console.log('[AI Bridge] 性能指标:', { 总字节数: metrics.totalBytes, 数据块数: metrics.chunkCount, 持续时间: `${metrics.duration}ms`, 平均块大小: `${Math.round(metrics.averageChunkSize)} bytes`, 处理速度: `${metrics.chunksPerSecond.toFixed(2)} chunks/s` }) } } /** * 重置适配器状态 */ reset(): void { this.buffer = '' this.performanceMetrics = { chunkCount: 0, totalBytes: 0, startTime: Date.now() } } /** * 清理资源 */ dispose(): void { this.buffer = '' this.reset() // 移除事件监听器(在实际实现中需要保存引用以便移除) } } /** * 创建流式适配器实例的工厂函数 */ export function createStreamAdapter(config?: StreamAdapterConfig): StreamAdapter { return new StreamAdapter(config) } /** * 默认配置 */ export const DEFAULT_STREAM_CONFIG: StreamAdapterConfig = { enableVisibilityOptimization: true, enablePerformanceTracking: true, bufferSize: 1024 }