byt-lingxiao-ai 0.3.5 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,314 +1,135 @@
1
1
  export class StreamParser {
2
2
  constructor(options = {}) {
3
3
  this.options = {
4
- updateInterval: 16, // 更新间隔(ms),默认约60fps
5
- batchSize: 100, // 批处理大小
6
- debug: false, // 是否开启调试
4
+ updateInterval: 16, // 每16ms刷新一次(约60fps
5
+ debug: false,
7
6
  ...options
8
7
  };
9
-
10
8
  this.reset();
11
9
  }
12
10
 
13
- /**
14
- * 重置解析器状态
15
- */
11
+ // 重置解析器状态
16
12
  reset() {
17
13
  this.buffer = '';
18
- this.contentBuffer = []; // 使用数组缓冲,避免频繁字符串拼接
14
+ this.contentBuffer = [];
19
15
  this.thinkingBuffer = [];
20
16
  this.inTag = false;
21
17
  this.tagBuffer = '';
22
18
  this.updateTimer = null;
23
19
  this.status = 'output'; // thinking | output
24
- this.metrics = {
25
- startTime: Date.now(),
26
- chunks: 0,
27
- events: 0,
28
- chars: 0
29
- };
30
20
  }
31
21
 
32
- /**
33
- * 处理流数据块
34
- * @param {string} chunk - 接收到的数据块
35
- * @param {Function} callback - 更新回调函数
36
- */
22
+ // 处理接收到的流数据块
37
23
  processChunk(chunk, callback) {
38
- this.metrics.chunks++;
39
- this.buffer += chunk;
40
-
41
- if (this.options.debug) {
42
- console.log('[StreamParser] 收到chunk:', chunk.substring(0, 100));
43
- }
44
-
45
- if (!this.buffer.includes('data:') && !this.buffer.includes('\n\n')) {
46
- // 纯文本流,直接处理
47
- if (this.options.debug) {
48
- console.log('[StreamParser] 检测到纯文本流');
49
- }
50
- this.processPlainText(callback);
51
- return;
52
- }
53
-
54
- // 尝试解析为 SSE 格式
55
- if (this.buffer.includes('data:')) {
56
- this.processSSEFormat(callback);
57
- } else {
58
- // 直接处理纯文本流
59
- this.processPlainText(callback);
60
- }
61
- }
62
-
63
- /**
64
- * 处理标准 SSE 格式
65
- */
66
- processSSEFormat(callback) {
67
- // SSE 格式:事件由双换行符分隔
68
- const events = this.buffer.split('\n\n');
69
-
70
- // 保留最后一个可能不完整的事件
71
- this.buffer = events.pop() || '';
72
-
73
- // 处理完整的事件
74
- for (const event of events) {
75
- if (event.trim()) {
76
- this.metrics.events++;
77
- this.processSSEEvent(event, callback);
78
- }
79
- }
80
- }
81
-
82
- /**
83
- * 处理纯文本流格式
84
- */
85
- processPlainText(callback) {
86
- const content = this.buffer;
87
- this.buffer = ''; // 清空缓冲区
88
-
89
- if (content) {
90
- this.metrics.chars += content.length;
91
-
92
- if (this.options.debug) {
93
- console.log('[StreamParser] 处理纯文本:', content);
94
- }
95
-
96
- this.parseContent(content, callback);
97
- }
98
- }
99
-
100
- /**
101
- * 处理单个SSE事件
102
- */
103
- processSSEEvent(eventStr, callback) {
104
- const lines = eventStr.split('\n');
105
-
106
- for (const line of lines) {
107
- // 解析 data: 行
108
- if (line.startsWith('data:')) {
109
- const data = line.substring(5).trim();
110
-
111
- if (data === '[DONE]') {
112
- this.flush(callback);
113
- return;
114
- }
115
-
116
- try {
117
- const parsed = JSON.parse(data);
118
- const content = parsed?.choices?.[0]?.delta?.content;
119
-
120
- if (content) {
121
- this.metrics.chars += content.length;
122
-
123
- if (this.options.debug) {
124
- console.log('[StreamParser] 解析SSE内容:', content);
125
- }
126
-
127
- this.parseContent(content, callback);
128
- }
129
- } catch (error) {
130
- if (this.options.debug) {
131
- console.warn('[StreamParser] JSON解析失败:', data, error);
132
- }
133
- }
134
- }
135
- }
24
+ if (!chunk) return;
25
+ this.parseContent(chunk, callback);
136
26
  }
137
27
 
138
- /**
139
- * 使用状态机解析内容和标签
140
- */
28
+ // 核心内容解析,支持 <think> 标签
141
29
  parseContent(content, callback) {
142
30
  let i = 0;
143
-
31
+
144
32
  while (i < content.length) {
145
33
  if (this.inTag) {
146
- // 在标签内部,查找标签结束
147
34
  const endIndex = content.indexOf('>', i);
148
-
149
35
  if (endIndex !== -1) {
150
- // 找到标签结束
151
36
  this.tagBuffer += content.substring(i, endIndex + 1);
152
37
  this.handleTag(this.tagBuffer);
153
38
  this.tagBuffer = '';
154
39
  this.inTag = false;
155
40
  i = endIndex + 1;
156
41
  } else {
157
- // 标签未结束,缓存剩余部分
42
+ // 标签未闭合,超过50字符强制输出,防止阻塞
158
43
  this.tagBuffer += content.substring(i);
44
+ if (this.tagBuffer.length > 50) {
45
+ this.appendText(this.tagBuffer);
46
+ this.tagBuffer = '';
47
+ this.inTag = false;
48
+ }
159
49
  break;
160
50
  }
161
51
  } else {
162
- // 不在标签内,查找标签开始
163
52
  const startIndex = content.indexOf('<', i);
164
-
165
53
  if (startIndex !== -1) {
166
- // 找到标签开始,先处理前面的文本
167
- if (startIndex > i) {
168
- this.appendText(content.substring(i, startIndex));
169
- }
170
-
171
- // 检查是否是完整标签
54
+ if (startIndex > i) this.appendText(content.substring(i, startIndex));
55
+
172
56
  const endIndex = content.indexOf('>', startIndex);
173
57
  if (endIndex !== -1) {
174
- // 完整标签
175
58
  const tag = content.substring(startIndex, endIndex + 1);
176
59
  this.handleTag(tag);
177
60
  i = endIndex + 1;
178
61
  } else {
179
- // 不完整标签,标记进入标签状态
180
- this.inTag = true;
181
- this.tagBuffer = content.substring(startIndex);
182
- break;
62
+ const nextChar = content[startIndex + 1];
63
+ if (!/[a-zA-Z/]/.test(nextChar)) {
64
+ // 很可能不是标签,直接当文本输出
65
+ this.appendText('<');
66
+ i = startIndex + 1;
67
+ } else {
68
+ this.inTag = true;
69
+ this.tagBuffer = content.substring(startIndex);
70
+ break;
71
+ }
183
72
  }
184
73
  } else {
185
- // 没有标签,全部是文本
186
74
  this.appendText(content.substring(i));
187
75
  break;
188
76
  }
189
77
  }
190
78
  }
191
79
 
192
- // 定时批量更新
193
80
  this.scheduleUpdate(callback);
194
81
  }
195
82
 
196
- /**
197
- * 处理标签
198
- */
83
+ // 处理标签
199
84
  handleTag(tag) {
200
- const tagName = tag.toLowerCase();
201
-
202
- if (this.options.debug) {
203
- console.log('[StreamParser] 处理标签:', tag);
204
- }
205
-
206
- if (tagName === '<think>') {
207
- this.status = 'thinking';
208
- } else if (tagName === '</think>') {
209
- this.status = 'output';
210
- }
211
- // 可扩展:支持更多标签类型
85
+ const t = tag.toLowerCase();
86
+ if (t === '<think>') this.status = 'thinking';
87
+ else if (t === '</think>') this.status = 'output';
212
88
  }
213
89
 
214
- /**
215
- * 添加文本到缓冲区
216
- */
90
+ // 添加文本到缓冲区
217
91
  appendText(text) {
218
92
  if (!text) return;
219
-
220
- if (this.status === 'thinking') {
221
- this.thinkingBuffer.push(text);
222
- } else {
223
- this.contentBuffer.push(text);
224
- }
93
+ if (this.status === 'thinking') this.thinkingBuffer.push(text);
94
+ else this.contentBuffer.push(text);
225
95
  }
226
96
 
227
- /**
228
- * 计划更新(防抖)
229
- */
97
+ // 防抖刷新
230
98
  scheduleUpdate(callback) {
231
99
  if (this.updateTimer) return;
232
-
233
100
  this.updateTimer = setTimeout(() => {
234
101
  this.flush(callback);
235
102
  this.updateTimer = null;
236
103
  }, this.options.updateInterval);
237
104
  }
238
105
 
239
- /**
240
- * 立即刷新缓冲区
241
- */
106
+ // 刷新缓冲区
242
107
  flush(callback) {
243
- if (!callback) {
244
- console.warn('[StreamParser] flush: callback 为空');
245
- return;
246
- }
247
-
248
- const hasThinking = this.thinkingBuffer.length > 0;
249
- const hasContent = this.contentBuffer.length > 0;
250
-
251
- if (!hasThinking && !hasContent) return;
252
-
253
- // 使用 join 比字符串拼接性能更好
108
+ if (!callback) return;
254
109
  const result = {
255
- thinking: hasThinking ? this.thinkingBuffer.join('') : null,
256
- content: hasContent ? this.contentBuffer.join('') : null,
110
+ thinking: this.thinkingBuffer.length ? this.thinkingBuffer.join('') : null,
111
+ content: this.contentBuffer.length ? this.contentBuffer.join('') : null,
257
112
  status: this.status
258
113
  };
259
-
260
- if (this.options.debug) {
261
- console.log('[StreamParser] 刷新缓冲区:', {
262
- thinking: result.thinking?.length || 0,
263
- content: result.content?.length || 0,
264
- status: result.status,
265
- thinkingPreview: result.thinking?.substring(0, 50),
266
- contentPreview: result.content?.substring(0, 50)
267
- });
268
- }
269
-
270
- // 清空缓冲区
271
114
  this.thinkingBuffer = [];
272
115
  this.contentBuffer = [];
273
-
274
- // 回调更新
275
- try {
276
- callback(result);
277
- } catch (error) {
278
- console.error('[StreamParser] 回调执行错误:', error);
279
- }
116
+ callback(result);
280
117
  }
281
118
 
282
- /**
283
- * 完成解析
284
- */
119
+ // 完成解析
285
120
  finish(callback) {
286
- this.flush(callback);
287
-
288
- if (this.updateTimer) {
289
- clearTimeout(this.updateTimer);
290
- this.updateTimer = null;
291
- }
292
-
293
- if (this.options.debug) {
294
- const duration = Date.now() - this.metrics.startTime;
295
- console.log('[StreamParser] 解析完成:', {
296
- 耗时: `${duration}ms`,
297
- 数据块: this.metrics.chunks,
298
- 事件数: this.metrics.events,
299
- 字符数: this.metrics.chars,
300
- 平均速度: `${(this.metrics.chars / duration * 1000).toFixed(0)} chars/s`
301
- });
121
+ if (this.inTag && this.tagBuffer) {
122
+ this.appendText(this.tagBuffer);
123
+ this.tagBuffer = '';
124
+ this.inTag = false;
302
125
  }
126
+ this.flush(callback);
127
+ if (this.updateTimer) clearTimeout(this.updateTimer);
303
128
  }
304
129
 
305
- /**
306
- * 销毁解析器
307
- */
130
+ // 销毁解析器
308
131
  destroy() {
309
- if (this.updateTimer) {
310
- clearTimeout(this.updateTimer);
311
- }
132
+ if (this.updateTimer) clearTimeout(this.updateTimer);
312
133
  this.reset();
313
134
  }
314
- }
135
+ }