byt-lingxiao-ai 0.2.8 → 0.3.2

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.
Files changed (42) hide show
  1. package/components/AiMessage.vue +357 -8
  2. package/components/ChatAvatar.vue +9 -2
  3. package/components/ChatMessageList.vue +29 -5
  4. package/components/ChatWindow.vue +110 -5
  5. package/components/ChatWindowDialog.vue +5 -0
  6. package/components/ChatWindowHeader.vue +1 -0
  7. package/components/assets/arrow.png +0 -0
  8. package/components/assets/empty.png +0 -0
  9. package/components/assets/entering.png +0 -0
  10. package/components/assets/logo.png +0 -0
  11. package/components/assets/normal.png +0 -0
  12. package/components/assets/output.png +0 -0
  13. package/components/assets/speaking.png +0 -0
  14. package/components/assets/think.png +0 -0
  15. package/components/assets/thinking.png +0 -0
  16. package/components/assets/waiting.png +0 -0
  17. package/components/config/index.js +4 -0
  18. package/components/mixins/messageMixin.js +25 -8
  19. package/components/mixins/webSocketMixin.js +3 -1
  20. package/components/utils/StreamParser.js +67 -39
  21. package/dist/img/empty.f36cb82e.png +0 -0
  22. package/dist/img/entering.4ef198fb.png +0 -0
  23. package/dist/img/normal.30197a82.png +0 -0
  24. package/dist/img/output.1dfa94eb.png +0 -0
  25. package/dist/img/speaking.fa87fedb.png +0 -0
  26. package/dist/img/thinking.21ad5ca5.png +0 -0
  27. package/dist/img/waiting.460478ef.png +0 -0
  28. package/dist/index.common.js +29750 -2967
  29. package/dist/index.common.js.map +1 -1
  30. package/dist/index.css +11 -1
  31. package/dist/index.umd.js +29742 -2959
  32. package/dist/index.umd.js.map +1 -1
  33. package/dist/index.umd.min.js +1 -1
  34. package/dist/index.umd.min.js.map +1 -1
  35. package/package.json +5 -3
  36. package/components/assets/byt.mp3 +0 -0
  37. package/dist/img/entering.42f05909.png +0 -0
  38. package/dist/img/normal.13f08ecb.png +0 -0
  39. package/dist/img/output.85c6bd8b.png +0 -0
  40. package/dist/img/speaking.3ce8b666.png +0 -0
  41. package/dist/img/thinking.05f29a84.png +0 -0
  42. package/dist/img/waiting.ac21d76e.png +0 -0
Binary file
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ export const API_URL = 'http://192.168.8.87:3100/lingxiao-byt/api/v1/mcp/ask';
2
+ export const WS_URL = 'ws://192.168.8.9:9999/ai_model/ws/voice-stream';
3
+ export const AUDIO_URL = '/minio/lingxiaoai/byt.mp3';
4
+ export const TIME_JUMP_POINTS_URL = '/minio/lingxiaoai/timeJumpPoints.json';
@@ -1,4 +1,5 @@
1
1
  import { StreamParser } from '../utils/StreamParser'
2
+ import { API_URL } from '../config/index.js'
2
3
 
3
4
  export default {
4
5
  data() {
@@ -25,6 +26,8 @@ export default {
25
26
  thinking: '',
26
27
  charts: [],
27
28
  content: '',
29
+ loading: true,
30
+ thinkingExpanded: true
28
31
  };
29
32
  this.messages.push(message);
30
33
  this.currentMessage = message;
@@ -59,12 +62,9 @@ export default {
59
62
  try {
60
63
  const startTime = Date.now();
61
64
  const controller = new AbortController();
62
- const token = `Bearer ac627d0a-8346-4ae9-b93a-f37ff6210adc`;
63
-
64
- const baseUrl = window.location.protocol + '//' + window.location.hostname + ':3100';
65
- const apiUrl = baseUrl + '/lingxiao-byt/api/chat/completions';
65
+ const token = `Bearer e298f087-85bc-48c2-afb9-7c69ffc911aa`;
66
66
 
67
- const response = await fetch(apiUrl, {
67
+ const response = await fetch(API_URL, {
68
68
  method: 'POST',
69
69
  signal: controller.signal,
70
70
  headers: {
@@ -82,7 +82,10 @@ export default {
82
82
  await this.consumeStream(response.body);
83
83
 
84
84
  // 完成解析
85
- this.streamParser.finish(this.handleStreamUpdate);
85
+ const self = this;
86
+ this.streamParser.finish(function(result) {
87
+ self.handleStreamUpdate(result);
88
+ });
86
89
 
87
90
  // 记录耗时
88
91
  const duration = Date.now() - startTime;
@@ -91,6 +94,7 @@ export default {
91
94
  }
92
95
 
93
96
  console.log(`流处理完成,总耗时: ${duration}ms`);
97
+ this.avaterStatus = 'normal';
94
98
 
95
99
  } catch (error) {
96
100
  console.error('发送消息失败:', error);
@@ -98,6 +102,11 @@ export default {
98
102
  this.currentMessage.content = '抱歉,发生了错误,请重试。';
99
103
  this.$forceUpdate();
100
104
  }
105
+ } finally {
106
+ // 确保加载状态关闭
107
+ if (this.currentMessage) {
108
+ this.currentMessage.loading = false;
109
+ }
101
110
  }
102
111
  },
103
112
 
@@ -107,6 +116,7 @@ export default {
107
116
  async consumeStream(readableStream) {
108
117
  const reader = readableStream.getReader();
109
118
  const decoder = new TextDecoder('utf-8');
119
+ const self = this;
110
120
 
111
121
  try {
112
122
  // eslint-disable-next-line no-constant-condition
@@ -116,8 +126,10 @@ export default {
116
126
 
117
127
  const chunk = decoder.decode(value, { stream: true });
118
128
 
119
- // 使用解析器处理数据块
120
- this.streamParser.processChunk(chunk, this.handleStreamUpdate);
129
+ // 使用解析器处理数据块,确保this指向正确
130
+ this.streamParser.processChunk(chunk, function(result) {
131
+ self.handleStreamUpdate(result);
132
+ });
121
133
  }
122
134
  } finally {
123
135
  reader.releaseLock();
@@ -130,6 +142,11 @@ export default {
130
142
  handleStreamUpdate(result) {
131
143
  if (!this.currentMessage) return;
132
144
 
145
+ console.log('收到更新:', result);
146
+
147
+ if (this.currentMessage.loading) {
148
+ this.currentMessage.loading = false;
149
+ }
133
150
  // 更新思考内容
134
151
  if (result.thinking) {
135
152
  this.currentMessage.thinking += result.thinking;
@@ -1,8 +1,10 @@
1
+ import { WS_URL } from '../config/index.js'
2
+
1
3
  export default {
2
4
  data() {
3
5
  return {
4
6
  ws: null,
5
- wsUrl: 'ws://192.168.8.9:9999/ai_model/ws/voice-stream',
7
+ wsUrl: WS_URL,
6
8
  isConnected: false,
7
9
  reconnectCount: 0, // 重连尝试次数
8
10
  maxReconnectAttempts: 3 // 最大重连尝试次数
@@ -1,11 +1,3 @@
1
- /**
2
- * SSE (Server-Sent Events) 流解析器
3
- * 优化点:
4
- * 1. 使用状态机模式处理标签解析
5
- * 2. 批量更新减少DOM操作
6
- * 3. 内存优化:避免频繁字符串拼接
7
- * 4. 可配置的更新频率
8
- */
9
1
  export class StreamParser {
10
2
  constructor(options = {}) {
11
3
  this.options = {
@@ -46,6 +38,23 @@ export class StreamParser {
46
38
  this.metrics.chunks++;
47
39
  this.buffer += chunk;
48
40
 
41
+ if (this.options.debug) {
42
+ console.log('[StreamParser] 收到chunk:', chunk.substring(0, 100));
43
+ }
44
+
45
+ // 尝试解析为 SSE 格式
46
+ if (this.buffer.includes('data:')) {
47
+ this.processSSEFormat(callback);
48
+ } else {
49
+ // 直接处理纯文本流
50
+ this.processPlainText(callback);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 处理标准 SSE 格式
56
+ */
57
+ processSSEFormat(callback) {
49
58
  // SSE 格式:事件由双换行符分隔
50
59
  const events = this.buffer.split('\n\n');
51
60
 
@@ -56,15 +65,33 @@ export class StreamParser {
56
65
  for (const event of events) {
57
66
  if (event.trim()) {
58
67
  this.metrics.events++;
59
- this.processEvent(event, callback);
68
+ this.processSSEEvent(event, callback);
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 处理纯文本流格式
75
+ */
76
+ processPlainText(callback) {
77
+ const content = this.buffer;
78
+ this.buffer = ''; // 清空缓冲区
79
+
80
+ if (content) {
81
+ this.metrics.chars += content.length;
82
+
83
+ if (this.options.debug) {
84
+ console.log('[StreamParser] 处理纯文本:', content);
60
85
  }
86
+
87
+ this.parseContent(content, callback);
61
88
  }
62
89
  }
63
90
 
64
91
  /**
65
92
  * 处理单个SSE事件
66
93
  */
67
- processEvent(eventStr, callback) {
94
+ processSSEEvent(eventStr, callback) {
68
95
  const lines = eventStr.split('\n');
69
96
 
70
97
  for (const line of lines) {
@@ -83,11 +110,16 @@ export class StreamParser {
83
110
 
84
111
  if (content) {
85
112
  this.metrics.chars += content.length;
113
+
114
+ if (this.options.debug) {
115
+ console.log('[StreamParser] 解析SSE内容:', content);
116
+ }
117
+
86
118
  this.parseContent(content, callback);
87
119
  }
88
120
  } catch (error) {
89
121
  if (this.options.debug) {
90
- console.warn('[StreamParser] JSON解析失败:', data);
122
+ console.warn('[StreamParser] JSON解析失败:', data, error);
91
123
  }
92
124
  }
93
125
  }
@@ -158,15 +190,16 @@ export class StreamParser {
158
190
  handleTag(tag) {
159
191
  const tagName = tag.toLowerCase();
160
192
 
193
+ if (this.options.debug) {
194
+ console.log('[StreamParser] 处理标签:', tag);
195
+ }
196
+
161
197
  if (tagName === '<think>') {
162
198
  this.status = 'thinking';
163
199
  } else if (tagName === '</think>') {
164
200
  this.status = 'output';
165
201
  }
166
202
  // 可扩展:支持更多标签类型
167
- // else if (tagName.startsWith('<code')) {
168
- // this.status = 'code';
169
- // }
170
203
  }
171
204
 
172
205
  /**
@@ -198,7 +231,10 @@ export class StreamParser {
198
231
  * 立即刷新缓冲区
199
232
  */
200
233
  flush(callback) {
201
- if (!callback) return;
234
+ if (!callback) {
235
+ console.warn('[StreamParser] flush: callback 为空');
236
+ return;
237
+ }
202
238
 
203
239
  const hasThinking = this.thinkingBuffer.length > 0;
204
240
  const hasContent = this.contentBuffer.length > 0;
@@ -212,12 +248,26 @@ export class StreamParser {
212
248
  status: this.status
213
249
  };
214
250
 
251
+ if (this.options.debug) {
252
+ console.log('[StreamParser] 刷新缓冲区:', {
253
+ thinking: result.thinking?.length || 0,
254
+ content: result.content?.length || 0,
255
+ status: result.status,
256
+ thinkingPreview: result.thinking?.substring(0, 50),
257
+ contentPreview: result.content?.substring(0, 50)
258
+ });
259
+ }
260
+
215
261
  // 清空缓冲区
216
262
  this.thinkingBuffer = [];
217
263
  this.contentBuffer = [];
218
264
 
219
265
  // 回调更新
220
- callback(result);
266
+ try {
267
+ callback(result);
268
+ } catch (error) {
269
+ console.error('[StreamParser] 回调执行错误:', error);
270
+ }
221
271
  }
222
272
 
223
273
  /**
@@ -252,26 +302,4 @@ export class StreamParser {
252
302
  }
253
303
  this.reset();
254
304
  }
255
- }
256
-
257
- /**
258
- * 使用示例:
259
- *
260
- * const parser = new StreamParser({ debug: true });
261
- *
262
- * // 处理流
263
- * for await (const chunk of stream) {
264
- * parser.processChunk(chunk, (result) => {
265
- * if (result.thinking) {
266
- * message.thinking += result.thinking;
267
- * }
268
- * if (result.content) {
269
- * message.content += result.content;
270
- * }
271
- * this.$forceUpdate();
272
- * });
273
- * }
274
- *
275
- * // 完成
276
- * parser.finish();
277
- */
305
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file