byt-lingxiao-ai 0.3.23 → 0.3.25

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,135 +1,181 @@
1
1
  export class StreamParser {
2
2
  constructor(options = {}) {
3
3
  this.options = {
4
- updateInterval: 16, // 每16ms刷新一次(约60fps)
5
- debug: false,
4
+ updateInterval: 16,
6
5
  ...options
7
- };
8
- this.reset();
6
+ }
7
+ this.reset()
9
8
  }
10
9
 
11
- // 重置解析器状态
12
10
  reset() {
13
- this.buffer = '';
14
- this.contentBuffer = [];
15
- this.thinkingBuffer = [];
16
- this.inTag = false;
17
- this.tagBuffer = '';
18
- this.updateTimer = null;
19
- this.status = 'output'; // thinking | output
11
+ this.inTag = false
12
+ this.tagBuffer = ''
13
+ this.currentType = 'content'
14
+ this.currentEvent = null
15
+ this.updateTimer = null
20
16
  }
21
17
 
22
- // 处理接收到的流数据块
23
- processChunk(chunk, callback) {
24
- if (!chunk) return;
25
- this.parseContent(chunk, callback);
18
+ processChunk(chunk, emit) {
19
+ if (!chunk) return
20
+ this.parse(chunk, emit)
26
21
  }
27
22
 
28
- // 核心内容解析,支持 <think> 标签
29
- parseContent(content, callback) {
30
- let i = 0;
23
+ parse(text, emit) {
24
+ let i = 0
31
25
 
32
- while (i < content.length) {
26
+ while (i < text.length) {
33
27
  if (this.inTag) {
34
- const endIndex = content.indexOf('>', i);
35
- if (endIndex !== -1) {
36
- this.tagBuffer += content.substring(i, endIndex + 1);
37
- this.handleTag(this.tagBuffer);
38
- this.tagBuffer = '';
39
- this.inTag = false;
40
- i = endIndex + 1;
28
+ const end = text.indexOf('>', i)
29
+ if (end !== -1) {
30
+ this.tagBuffer += text.slice(i, end + 1)
31
+ this.handleTag(this.tagBuffer, emit)
32
+ this.tagBuffer = ''
33
+ this.inTag = false
34
+ i = end + 1
41
35
  } else {
42
- // 标签未闭合,超过50字符强制输出,防止阻塞
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
- }
49
- break;
36
+ this.tagBuffer += text.slice(i)
37
+ break
50
38
  }
51
39
  } else {
52
- const startIndex = content.indexOf('<', i);
53
- if (startIndex !== -1) {
54
- if (startIndex > i) this.appendText(content.substring(i, startIndex));
55
-
56
- const endIndex = content.indexOf('>', startIndex);
57
- if (endIndex !== -1) {
58
- const tag = content.substring(startIndex, endIndex + 1);
59
- this.handleTag(tag);
60
- i = endIndex + 1;
40
+ const lt = text.indexOf('<', i)
41
+ if (lt !== -1) {
42
+ if (lt > i) this.append(text.slice(i, lt), emit)
43
+
44
+ const gt = text.indexOf('>', lt)
45
+ if (gt !== -1) {
46
+ this.handleTag(text.slice(lt, gt + 1), emit)
47
+ i = gt + 1
61
48
  } else {
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
- }
49
+ this.inTag = true
50
+ this.tagBuffer = text.slice(lt)
51
+ break
72
52
  }
73
53
  } else {
74
- this.appendText(content.substring(i));
75
- break;
54
+ this.append(text.slice(i), emit)
55
+ break
76
56
  }
77
57
  }
78
58
  }
79
59
 
80
- this.scheduleUpdate(callback);
60
+ this.scheduleFlush(emit)
81
61
  }
82
62
 
83
- // 处理标签
84
- handleTag(tag) {
85
- const t = tag.toLowerCase();
86
- if (t === '<think>') this.status = 'thinking';
87
- else if (t === '</think>') this.status = 'output';
63
+ handleTag(tag, emit) {
64
+ const t = tag.toLowerCase().trim()
65
+
66
+ if (t.startsWith('<think')) {
67
+ this.startThinking(emit)
68
+ }
69
+ else if (t.startsWith('</think')) {
70
+ this.closeThinking()
71
+ this.currentType = 'content'
72
+ this.currentEvent = null
73
+ }
74
+ else if (t.startsWith('<tool_call')) {
75
+ const attrs = this.parseTagAttributes(tag)
76
+ this.switchType('tool_call', emit, attrs.name)
77
+ }
78
+ else if (t.startsWith('</tool_call')) {
79
+ this.switchType('content', emit)
80
+ }
81
+ else if (t.startsWith('<tool_result')) {
82
+ this.switchType('tool_result', emit)
83
+ }
84
+ else if (t.startsWith('</tool_result')) {
85
+ this.switchType('content', emit)
86
+ }
87
+ else {
88
+ this.append(tag, emit)
89
+ }
90
+ }
91
+
92
+
93
+ startThinking(emit) {
94
+ // 如果上一个 thinking 还没结算,先结算
95
+ this.closeThinking()
96
+
97
+ this.currentType = 'thinking'
98
+ this.currentEvent = {
99
+ id: this.uuid(),
100
+ type: 'thinking',
101
+ content: '',
102
+ startTime: Date.now(),
103
+ endTime: null,
104
+ duration: null
105
+ }
106
+
107
+ emit({ type: 'create', event: this.currentEvent })
88
108
  }
89
109
 
90
- // 添加文本到缓冲区
91
- appendText(text) {
92
- if (!text) return;
93
- if (this.status === 'thinking') this.thinkingBuffer.push(text);
94
- else this.contentBuffer.push(text);
110
+ closeThinking() {
111
+ if (
112
+ this.currentEvent &&
113
+ this.currentEvent.type === 'thinking' &&
114
+ !this.currentEvent.endTime
115
+ ) {
116
+ this.currentEvent.endTime = Date.now()
117
+ this.currentEvent.duration = (
118
+ (this.currentEvent.endTime - this.currentEvent.startTime) / 1000
119
+ ).toFixed(2)
120
+ console.log(this.currentEvent.endTime - this.currentEvent.startTime)
121
+ }
95
122
  }
96
123
 
97
- // 防抖刷新
98
- scheduleUpdate(callback) {
99
- if (this.updateTimer) return;
100
- this.updateTimer = setTimeout(() => {
101
- this.flush(callback);
102
- this.updateTimer = null;
103
- }, this.options.updateInterval);
124
+ switchType(type) {
125
+ // 如果是从 thinking 切走,结算时间
126
+ if (this.currentEvent?.type === 'thinking') {
127
+ this.closeThinking()
128
+ }
129
+
130
+ this.currentType = type
131
+ this.currentEvent = null
104
132
  }
105
133
 
106
- // 刷新缓冲区
107
- flush(callback) {
108
- if (!callback) return;
109
- const result = {
110
- thinking: this.thinkingBuffer.length ? this.thinkingBuffer.join('') : null,
111
- content: this.contentBuffer.length ? this.contentBuffer.join('') : null,
112
- status: this.status
113
- };
114
- this.thinkingBuffer = [];
115
- this.contentBuffer = [];
116
- callback(result);
134
+ append(text, emit) {
135
+ if (!text) return
136
+
137
+ if (!this.currentEvent || this.currentEvent.type !== this.currentType) {
138
+ this.currentEvent = {
139
+ id: this.uuid(),
140
+ type: this.currentType,
141
+ content: ''
142
+ }
143
+ emit({ type: 'create', event: this.currentEvent })
144
+ }
145
+
146
+ this.currentEvent.content += text
147
+ emit({ type: 'append', event: this.currentEvent })
117
148
  }
118
149
 
119
- // 完成解析
120
- finish(callback) {
121
- if (this.inTag && this.tagBuffer) {
122
- this.appendText(this.tagBuffer);
123
- this.tagBuffer = '';
124
- this.inTag = false;
150
+ parseTagAttributes(tag) {
151
+ const attrRegex = /(\w+)=['"]([^'"]+)['"]/g
152
+ const attrs = {}
153
+ let match
154
+
155
+ while ((match = attrRegex.exec(tag)) !== null) {
156
+ attrs[match[1]] = match[2]
125
157
  }
126
- this.flush(callback);
127
- if (this.updateTimer) clearTimeout(this.updateTimer);
158
+
159
+ return attrs
160
+ }
161
+
162
+ scheduleFlush(emit) {
163
+ if (this.updateTimer) return
164
+ this.updateTimer = setTimeout(() => {
165
+ emit({ type: 'flush' })
166
+ this.updateTimer = null
167
+ }, this.options.updateInterval)
168
+ }
169
+
170
+ finish(emit) {
171
+ // 结束时如果还在 thinking,也要结算
172
+ this.closeThinking()
173
+ emit({ type: 'flush' })
174
+ if (this.updateTimer) clearTimeout(this.updateTimer)
175
+ this.reset()
128
176
  }
129
177
 
130
- // 销毁解析器
131
- destroy() {
132
- if (this.updateTimer) clearTimeout(this.updateTimer);
133
- this.reset();
178
+ uuid() {
179
+ return Math.random().toString(36).slice(2)
134
180
  }
135
- }
181
+ }