byt-lingxiao-ai 0.3.4 → 0.3.6

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.
@@ -30,129 +30,6 @@ marked.setOptions({
30
30
  breaks: true,
31
31
  gfm: true
32
32
  });
33
- function parseMarkdown(text) {
34
- if (!text) return '';
35
- let html = text;
36
-
37
- html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
38
- return `<pre><code class="language-${lang || 'text'}">${escapeHtml(code.trim())}</code></pre>`;
39
- });
40
- html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
41
- html = parseTable(html);
42
-
43
- html = html.replace(/^#### (.*$)/gm, '<h4>$1</h4>');
44
- html = html.replace(/^### (.*$)/gm, '<h3>$1</h3>');
45
- html = html.replace(/^## (.*$)/gm, '<h2>$1</h2>');
46
- html = html.replace(/^# (.*$)/gm, '<h1>$1</h1>');
47
-
48
- html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
49
-
50
- html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
51
-
52
- html = html.replace(/~~(.*?)~~/g, '<del>$1</del>');
53
-
54
- html = html.replace(/^\s*[-*+]\s+(.+)$/gm, '<li>$1</li>');
55
- html = html.replace(/(<li>.*?<\/li>)/gs, '<ul>$1</ul>');
56
-
57
- html = html.replace(/^\s*\d+\.\s+(.+)$/gm, '<li>$1</li>');
58
-
59
- html = html.replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>');
60
- // 解析Markdown链接
61
- html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
62
-
63
- html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />');
64
-
65
- html = html.replace(/^-{3,}$/gm, '<hr>');
66
- // 解析简单URL
67
- // const simpleUrlRegex = /(https?:\/\/[^\s<>"']+)/g;
68
- // html = html.replace(simpleUrlRegex, (match) => {
69
- // return `<a href="${match}" aria-current="page">${match}</a>`;
70
- // });
71
-
72
- // html = html.replace(/\n/g, '<br>');
73
-
74
- return html;
75
- }
76
- // 解析表格
77
- function parseTable(text) {
78
- const lines = text.split('\n');
79
- let result = [];
80
- let inTable = false;
81
- let tableRows = [];
82
-
83
- for (let i = 0; i < lines.length; i++) {
84
- const line = lines[i].trim();
85
-
86
- // 检测表格行
87
- if (line.includes('|') && line.split('|').length >= 3) {
88
- if (!inTable) {
89
- inTable = true;
90
- tableRows = [];
91
- }
92
- tableRows.push(line);
93
-
94
- // 检查下一行是否还是表格
95
- if (i === lines.length - 1 || !lines[i + 1].includes('|')) {
96
- // 表格结束
97
- result.push(renderTable(tableRows));
98
- inTable = false;
99
- tableRows = [];
100
- }
101
- } else {
102
- if (inTable) {
103
- // 表格意外结束
104
- result.push(renderTable(tableRows));
105
- inTable = false;
106
- tableRows = [];
107
- }
108
- result.push(line);
109
- }
110
- }
111
-
112
- return result.join('\n');
113
- }
114
- // 渲染表格
115
- function renderTable(rows) {
116
- if (rows.length < 2) return rows.join('\n');
117
-
118
- let html = '<div class="table-wrapper"><table class="markdown-table">';
119
-
120
- // 表头
121
- const headerCells = rows[0].split('|').filter(cell => cell.trim());
122
- html += '<thead><tr>';
123
- headerCells.forEach(cell => {
124
- html += `<th>${cell.trim()}</th>`;
125
- });
126
- html += '</tr></thead>';
127
-
128
- // 表体(跳过分隔行)
129
- html += '<tbody>';
130
- for (let i = 2; i < rows.length; i++) {
131
- const cells = rows[i].split('|').filter(cell => cell.trim());
132
- if (cells.length > 0) {
133
- html += '<tr>';
134
- cells.forEach(cell => {
135
- html += `<td><div class="table-cell">${cell.trim()}</div></td>`;
136
- });
137
- html += '</tr>';
138
- }
139
- }
140
- html += '</tbody>';
141
-
142
- html += '</table></div>';
143
- return html;
144
- }
145
- // HTML 转义
146
- function escapeHtml(text) {
147
- const map = {
148
- '&': '&amp;',
149
- '<': '&lt;',
150
- '>': '&gt;',
151
- '"': '&quot;',
152
- "'": '&#039;'
153
- };
154
- return text.replace(/[&<>"']/g, m => map[m]);
155
- }
156
33
 
157
34
  export default {
158
35
  name: 'AiMessage',
@@ -167,7 +44,7 @@ export default {
167
44
  return this.message.thinkingExpanded !== false;
168
45
  },
169
46
  renderedContent() {
170
- return parseMarkdown(this.message.content);
47
+ return marked.parse(this.message.content || '');
171
48
  },
172
49
  isLoading() {
173
50
  return this.message.loading === true;
@@ -335,6 +212,19 @@ export default {
335
212
  .markdown-body ::v-deep h4 {
336
213
  font-size: 16px;
337
214
  }
215
+ .markdown-body ::v-deep ol {
216
+ list-style-type: decimal; /* 确保显示数字 */
217
+ padding-left: 24px;
218
+ margin: 8px 0;
219
+ }
220
+ .markdown-body ::v-deep ul {
221
+ list-style-type: disc; /* 确保显示圆点 */
222
+ padding-left: 24px;
223
+ margin: 8px 0;
224
+ }
225
+ .markdown-body ::v-deep li {
226
+ display: list-item; /* 确保是列表项显示 */
227
+ }
338
228
 
339
229
  .markdown-body ::v-deep code {
340
230
  background-color: rgba(175, 184, 193, 0.2);
@@ -367,52 +257,44 @@ export default {
367
257
  color: #24292e;
368
258
  font-size: 14px;
369
259
  }
370
- .markdown-body ::v-deep .table-wrapper{
371
- overflow-x: auto;
260
+ .markdown-body ::v-deep table{
372
261
  border: 1px solid #dfe2e5;
262
+ max-height: 500px;
373
263
  margin: 12px 0;
374
- }
375
- .markdown-body ::v-deep .markdown-table {
376
264
  border-collapse: collapse;
377
265
  width: 100%;
378
- font-size: 14px;
379
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
380
- overflow: hidden;
381
- margin: -1px 0 0 -1px;
266
+ display: block;
267
+ overflow: auto;
382
268
  }
383
269
  /* 定义table的滚动条 */
384
- .markdown-body ::v-deep .table-wrapper::-webkit-scrollbar {
270
+ .markdown-body ::v-deep table::-webkit-scrollbar {
385
271
  height: 6px;
386
272
  }
387
- .markdown-body ::v-deep .table-wrapper::-webkit-scrollbar-track {
273
+ .markdown-body ::v-deep table::-webkit-scrollbar-track {
388
274
  background-color: #f3f4f6;
389
275
  }
390
- .markdown-body ::v-deep .table-wrapper::-webkit-scrollbar-thumb {
276
+ .markdown-body ::v-deep table::-webkit-scrollbar-thumb {
391
277
  background-color: #d1d5db;
392
278
  border-radius: 3px;
393
279
  }
394
- .markdown-body ::v-deep .table-wrapper::-webkit-scrollbar-thumb:hover {
280
+ .markdown-body ::v-deep table::-webkit-scrollbar-thumb:hover {
395
281
  background-color: #9ca3af;
396
282
  }
397
283
 
398
- .markdown-body ::v-deep .table-cell {
399
- min-width: 100px;
400
- text-align: left;
401
- }
402
- .markdown-body ::v-deep .markdown-table th,
403
- .markdown-body ::v-deep .markdown-table td {
284
+ .markdown-body ::v-deep table th,
285
+ .markdown-body ::v-deep table td {
404
286
  border: 1px solid #dfe2e5;
405
287
  padding: 10px 14px;
406
288
  text-align: left;
407
289
  }
408
290
 
409
- .markdown-body ::v-deep .markdown-table th {
291
+ .markdown-body ::v-deep table th {
410
292
  background-color: #f3f4f6;
411
293
  font-weight: 600;
412
294
  color: #374151;
413
295
  }
414
296
 
415
- .markdown-body ::v-deep .markdown-table tr:nth-child(even) {
297
+ .markdown-body ::v-deep table tr:nth-child(even) {
416
298
  background-color: #f9fafb;
417
299
  }
418
300
 
@@ -33,6 +33,7 @@
33
33
  :messages="messages"
34
34
  :input-message="inputMessage"
35
35
  :think-status="thinkStatus"
36
+ :chat-id="chatId"
36
37
  @update:inputMessage="inputMessage = $event"
37
38
  @send="handleSend"
38
39
  @thinking-click="handleThinkingClick"
@@ -49,9 +50,11 @@ import audioMixin from './mixins/audioMixin'
49
50
  import webSocketMixin from './mixins/webSocketMixin'
50
51
  import messageMixin from './mixins/messageMixin'
51
52
  import { AUDIO_URL, TIME_JUMP_POINTS_URL } from './config/index.js'
53
+ import generateUuid from './utils/Uuid.js'
52
54
 
53
55
  const SAMPLE_RATE = 16000;
54
56
  const FRAME_SIZE = 512;
57
+ const startTime = null
55
58
 
56
59
  export default {
57
60
  name: 'ChatWindow',
@@ -69,6 +72,7 @@ export default {
69
72
  },
70
73
  data() {
71
74
  return {
75
+ chatId: generateUuid(),
72
76
  audioSrc: AUDIO_URL,
73
77
  inputMessage: '',
74
78
  visible: false,
@@ -80,6 +84,7 @@ export default {
80
84
  jumpedTimePoints: new Set(),
81
85
  SAMPLE_RATE,
82
86
  FRAME_SIZE,
87
+ startTime, // 检查性能使用
83
88
  dragThreshold: 5, // 拖拽阈值
84
89
  isDragging: false,
85
90
  dragStartX: 0,
@@ -132,6 +137,7 @@ export default {
132
137
  },
133
138
  methods: {
134
139
  toggleWindow() {
140
+ if (this.avaterStatus === 'thinking') return;
135
141
  this.visible = !this.visible
136
142
 
137
143
  if (this.visible) {
@@ -2,7 +2,7 @@
2
2
  <div class="chat-overlay" v-show="value" @click="$emit('overlay-click')">
3
3
  <div class="chat-window" @click.stop>
4
4
  <!-- 头部 -->
5
- <ChatWindowHeader @close="$emit('input', false)" />
5
+ <ChatWindowHeader :chat-id="chatId" @close="$emit('input', false)" />
6
6
 
7
7
  <!-- 消息列表 -->
8
8
  <ChatMessageList
@@ -55,6 +55,10 @@ export default {
55
55
  loading: {
56
56
  type: Boolean,
57
57
  default: false
58
+ },
59
+ chatId: {
60
+ type: String,
61
+ default: ''
58
62
  }
59
63
  }
60
64
  }
@@ -39,11 +39,15 @@
39
39
  <script>
40
40
  export default {
41
41
  name: 'ChatWindowHeader',
42
+ props: {
43
+ chatId: {
44
+ type: String,
45
+ default: ''
46
+ }
47
+ },
42
48
  methods: {
43
49
  handleOpen() {
44
- // const chatId = ''
45
- // const baseUrl = window.location.protocol + '//' + window.location.hostname + ':3100/c/' + chatId;
46
- const baseUrl = window.location.protocol + '//' + window.location.hostname + ':3100/';
50
+ const baseUrl = window.location.protocol + '//' + window.location.hostname + ':3100/c/' + this.chatId;
47
51
  window.open(baseUrl, '_blank')
48
52
  }
49
53
  }
@@ -1,8 +1,11 @@
1
- const baseUrl = window.location.protocol + '//' + window.location.hostname;
1
+ const protocol = window.location.protocol;
2
+ const host = window.location.hostname;
3
+ const baseUrl = `${protocol}//${host}`;
2
4
  const chatPort = '3100';
5
+ const voicePort = '3101';
3
6
 
4
7
  console.log(baseUrl, chatPort);
5
- export const API_URL = 'http://192.168.8.87:3100/lingxiao-byt/api/v1/mcp/ask';
6
- export const WS_URL = 'ws://192.168.8.9:9999/ai_model/ws/voice-stream';
8
+ export const API_URL = `${baseUrl}:${chatPort}/lingxiao-byt/api/v1/mcp/ask`;
9
+ export const WS_URL = `ws://${host}:${voicePort}/ai_model/ws/voice-stream`;
7
10
  export const AUDIO_URL = '/minio/lingxiaoai/byt.mp3';
8
11
  export const TIME_JUMP_POINTS_URL = '/minio/lingxiaoai/timeJumpPoints.json';
@@ -18,8 +18,9 @@ export default {
18
18
  audio: {
19
19
  sampleRate: this.SAMPLE_RATE,
20
20
  channelCount: 1,
21
- noiseSuppression: true,
22
- echoCancellation: true
21
+ noiseSuppression: false,
22
+ echoCancellation: false,
23
+ autoGainControl: false,
23
24
  }
24
25
  });
25
26
 
@@ -88,6 +89,7 @@ export default {
88
89
  },
89
90
 
90
91
  pause() {
92
+ console.log('暂停播放');
91
93
  this.robotStatus = 'waiting';
92
94
  this.$refs.audioPlayer.pause();
93
95
  },
@@ -115,6 +117,7 @@ export default {
115
117
  this.pause();
116
118
  } else if (command === 'C6') {
117
119
  this.robotStatus = 'leaving';
120
+ this.avaterStatus = 'normal';
118
121
  this.stop();
119
122
  }
120
123
  }
@@ -1,5 +1,8 @@
1
1
  import { StreamParser } from '../utils/StreamParser'
2
2
  import { API_URL } from '../config/index.js'
3
+ import { getCookie } from '../utils/Cookie.js'
4
+
5
+
3
6
 
4
7
  export default {
5
8
  data() {
@@ -61,17 +64,15 @@ export default {
61
64
 
62
65
  try {
63
66
  const startTime = Date.now();
64
- const controller = new AbortController();
65
- const token = `Bearer e298f087-85bc-48c2-afb9-7c69ffc911aa`;
67
+ const token = getCookie('bonyear-access_token') || `44e7f112-63f3-429d-908d-2c97ec380de2`;
66
68
 
67
69
  const response = await fetch(API_URL, {
68
70
  method: 'POST',
69
- signal: controller.signal,
70
71
  headers: {
71
72
  'Content-Type': 'application/json',
72
- 'Authorization': token,
73
+ 'Authorization': `Bearer ${token}`,
73
74
  },
74
- body: JSON.stringify({ content: message })
75
+ body: JSON.stringify({ content: message, chat_id: this.chatId })
75
76
  });
76
77
 
77
78
  if (!response.ok) {
@@ -125,9 +126,10 @@ export default {
125
126
  if (done) break;
126
127
 
127
128
  const chunk = decoder.decode(value, { stream: true });
128
-
129
+ console.log('收到数据块:', chunk);
129
130
  // 使用解析器处理数据块,确保this指向正确
130
131
  this.streamParser.processChunk(chunk, function(result) {
132
+ console.log('处理数据块:', result);
131
133
  self.handleStreamUpdate(result);
132
134
  });
133
135
  }
@@ -153,6 +155,7 @@ export default {
153
155
  }
154
156
 
155
157
  // 更新回复内容
158
+ console.log('更新回复内容:', result.content);
156
159
  if (result.content) {
157
160
  this.currentMessage.content += result.content;
158
161
  }
@@ -75,15 +75,34 @@ export default {
75
75
  handleWebSocketMessage(data) {
76
76
  if (data.type === 'detection') {
77
77
  console.log('检测到唤醒词');
78
+ console.log('当前状态:', this.avaterStatus);
79
+ if (this.robotStatus === 'speaking') {
80
+ this.robotStatus = 'waiting';
81
+ }
78
82
  this.avaterStatus = 'normal';
83
+
84
+ // 性能检测起点
85
+ this.startTime = Date.now(); // <-- 新增计时器
86
+ console.log(`[Timer] 指令发送开始计时: ${this.startTime}ms`);
87
+
79
88
  } else if (data.type === 'Collecting') {
80
89
  console.log('状态: 采集中');
81
90
  this.avaterStatus = 'thinking';
82
91
  } else if (data.type === 'command') {
83
92
  console.log('状态: 处理中');
93
+
94
+ // 性能检测终点
95
+ if (this.startTime) {
96
+ const latency = Date.now() - this.startTime;
97
+ console.log(`[Latency] 完整命令处理耗时: ${latency}ms`); // 记录端到端延迟
98
+ this.startTime = null;
99
+ }
100
+
84
101
  this.analyzeAudioCommand(data.category);
85
102
  } else {
86
103
  console.log('状态: 其他');
104
+ this.avaterStatus = 'normal';
105
+ this.robotStatus = 'leaving';
87
106
  }
88
107
  },
89
108
 
@@ -0,0 +1,19 @@
1
+ const setCookie = (name, value, days) => {
2
+ const d = new Date();
3
+ d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
4
+ const expires = `expires=${d.toUTCString()}`;
5
+ document.cookie = `${name}=${value}; ${expires}`;
6
+ };
7
+
8
+ const getCookie = cname => {
9
+ const name = `${cname}=`;
10
+ const ca = document.cookie.split(';');
11
+ for (let i = 0; i < ca.length; i++) {
12
+ let c = ca[i];
13
+ while (c.charAt(0) === ' ') c = c.substring(1);
14
+ if (c.indexOf(name) !== -1) return c.substring(name.length, c.length);
15
+ }
16
+ return '';
17
+ };
18
+
19
+ export { setCookie, getCookie };