如何在Angular中用RxJS处理NDJSON?需实现打字机效果
处理OpenAI流式NDJSON响应实现打字机效果
直接用Angular原生HttpClient就能处理,无需额外引入ndjson库,具体实现步骤如下:
- 配置请求开启流式响应模式,指定
responseType: 'text'和observe: 'events',获取原始文本数据流 - 逐块接收响应数据,处理NDJSON的行分割逻辑(注意缓存截断的不完整行)
- 解析每行JSON对象,提取OpenAI返回的内容片段
- 拼接内容片段,实现打字机效果
完整代码示例
import { HttpClient, HttpEventType } from '@angular/common/http'; import { Component } from '@angular/core'; @Component({ selector: 'app-openai-stream', template: `<div class="typed-content">{{ typedText }}</div>` }) export class OpenaiStreamComponent { typedText = ''; private incompleteLine = ''; // 缓存截断的不完整行 constructor(private http: HttpClient) {} triggerOpenAIRequest() { const openAiApiUrl = 'https://api.openai.com/v1/chat/completions'; const requestPayload = { model: 'gpt-3.5-turbo', messages: [{ role: 'user', content: '写一段关于Angular的入门介绍' }], stream: true // 必须开启流式返回开关 }; this.http.post(openAiApiUrl, requestPayload, { responseType: 'text', observe: 'events', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' } }).subscribe({ next: (event) => { if (event.type === HttpEventType.DownloadProgress && event.partialText) { // 拼接缓存的不完整行与当前chunk文本 const fullContent = this.incompleteLine + event.partialText; // 按行分割NDJSON内容 const lines = fullContent.split('\n'); // 最后一行可能不完整,缓存到下次处理 this.incompleteLine = lines.pop() || ''; lines.forEach(line => { // 过滤空行和结束标记行 if (line.trim() && line.startsWith('data: ') && line !== 'data: [DONE]') { const jsonStr = line.replace('data: ', ''); try { const responseData = JSON.parse(jsonStr); const contentFragment = responseData.choices[0]?.delta?.content; if (contentFragment) { // 直接拼接实现基础打字机效果 this.typedText += contentFragment; } } catch (error) { console.error('NDJSON解析失败:', error); } } }); } }, error: (error) => { console.error('请求出错:', error); }, complete: () => { // 处理最后剩余的不完整行 if (this.incompleteLine.trim()) { const cleanLine = this.incompleteLine.replace('data: ', ''); try { const responseData = JSON.parse(cleanLine); const contentFragment = responseData.choices[0]?.delta?.content; if (contentFragment) { this.typedText += contentFragment; } } catch (error) { console.error('剩余行解析失败:', error); } this.incompleteLine = ''; } } }); } }
优化细节
如果需要更平滑的打字机动画,可以把直接拼接的逻辑改成逐字符延迟添加:
if (contentFragment) { contentFragment.split('').forEach((char, index) => { setTimeout(() => { this.typedText += char; }, 40 * index); // 40ms间隔,可根据需求调整速度 }); }
内容的提问来源于stack exchange,提问作者ygz
相关产品推荐
相关产品推荐

