byt-lingxiao-ai 0.3.5 → 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.
- package/components/AiMessage.vue +26 -144
- package/components/ChatWindow.vue +1 -0
- package/components/config/index.js +5 -2
- package/components/mixins/audioMixin.js +17 -24
- package/components/mixins/messageMixin.js +5 -3
- package/components/mixins/webSocketMixin.js +6 -0
- package/components/utils/StreamParser.js +50 -229
- package/dist/index.common.js +92 -364
- package/dist/index.common.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.umd.js +92 -364
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/package.json +3 -2
package/components/AiMessage.vue
CHANGED
|
@@ -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" aria-current="page">$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
|
-
'&': '&',
|
|
149
|
-
'<': '<',
|
|
150
|
-
'>': '>',
|
|
151
|
-
'"': '"',
|
|
152
|
-
"'": '''
|
|
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
|
|
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
|
|
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
|
-
|
|
379
|
-
|
|
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
|
|
270
|
+
.markdown-body ::v-deep table::-webkit-scrollbar {
|
|
385
271
|
height: 6px;
|
|
386
272
|
}
|
|
387
|
-
.markdown-body ::v-deep
|
|
273
|
+
.markdown-body ::v-deep table::-webkit-scrollbar-track {
|
|
388
274
|
background-color: #f3f4f6;
|
|
389
275
|
}
|
|
390
|
-
.markdown-body ::v-deep
|
|
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
|
|
280
|
+
.markdown-body ::v-deep table::-webkit-scrollbar-thumb:hover {
|
|
395
281
|
background-color: #9ca3af;
|
|
396
282
|
}
|
|
397
283
|
|
|
398
|
-
.markdown-body ::v-deep
|
|
399
|
-
|
|
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
|
|
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
|
|
297
|
+
.markdown-body ::v-deep table tr:nth-child(even) {
|
|
416
298
|
background-color: #f9fafb;
|
|
417
299
|
}
|
|
418
300
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
const
|
|
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
8
|
export const API_URL = `${baseUrl}:${chatPort}/lingxiao-byt/api/v1/mcp/ask`;
|
|
6
|
-
export const WS_URL =
|
|
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';
|
|
@@ -6,9 +6,7 @@ export default {
|
|
|
6
6
|
audioContext: null,
|
|
7
7
|
microphone: null,
|
|
8
8
|
processor: null,
|
|
9
|
-
audioBuffer: new Float32Array(0)
|
|
10
|
-
// 优化
|
|
11
|
-
int16Buffer: null
|
|
9
|
+
audioBuffer: new Float32Array(0)
|
|
12
10
|
}
|
|
13
11
|
},
|
|
14
12
|
methods: {
|
|
@@ -20,21 +18,15 @@ export default {
|
|
|
20
18
|
audio: {
|
|
21
19
|
sampleRate: this.SAMPLE_RATE,
|
|
22
20
|
channelCount: 1,
|
|
23
|
-
noiseSuppression:
|
|
24
|
-
echoCancellation:
|
|
21
|
+
noiseSuppression: false,
|
|
22
|
+
echoCancellation: false,
|
|
23
|
+
autoGainControl: false,
|
|
25
24
|
}
|
|
26
25
|
});
|
|
27
26
|
|
|
28
27
|
this.audioContext = new AudioContext({ sampleRate: this.SAMPLE_RATE });
|
|
29
|
-
|
|
30
|
-
// 优化 处理 AudioContext 挂起
|
|
31
|
-
if (this.audioContext.state === 'suspended') {
|
|
32
|
-
await this.audioContext.resume();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
28
|
this.microphone = this.audioContext.createMediaStreamSource(stream);
|
|
36
29
|
this.processor = this.audioContext.createScriptProcessor(this.FRAME_SIZE, 1, 1);
|
|
37
|
-
this.int16Buffer = new Int16Array(this.FRAME_SIZE);
|
|
38
30
|
this.processor.onaudioprocess = this.processAudio;
|
|
39
31
|
|
|
40
32
|
this.microphone.connect(this.processor);
|
|
@@ -53,20 +45,19 @@ export default {
|
|
|
53
45
|
if (!this.isRecording) return;
|
|
54
46
|
const inputData = event.inputBuffer.getChannelData(0);
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
const tempBuffer = new Float32Array(this.audioBuffer.length + inputData.length);
|
|
49
|
+
tempBuffer.set(this.audioBuffer, 0);
|
|
50
|
+
tempBuffer.set(inputData, this.audioBuffer.length);
|
|
51
|
+
this.audioBuffer = tempBuffer;
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.int16Buffer[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
|
66
|
-
}
|
|
53
|
+
while (this.audioBuffer.length >= this.FRAME_SIZE) {
|
|
54
|
+
const frame = this.audioBuffer.slice(0, this.FRAME_SIZE);
|
|
55
|
+
this.audioBuffer = this.audioBuffer.slice(this.FRAME_SIZE);
|
|
56
|
+
const pcmData = this.floatTo16BitPCM(frame);
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
if (this.ws && this.ws.readyState === this.ws.OPEN) {
|
|
59
|
+
this.ws.send(pcmData);
|
|
60
|
+
}
|
|
70
61
|
}
|
|
71
62
|
},
|
|
72
63
|
|
|
@@ -98,6 +89,7 @@ export default {
|
|
|
98
89
|
},
|
|
99
90
|
|
|
100
91
|
pause() {
|
|
92
|
+
console.log('暂停播放');
|
|
101
93
|
this.robotStatus = 'waiting';
|
|
102
94
|
this.$refs.audioPlayer.pause();
|
|
103
95
|
},
|
|
@@ -125,6 +117,7 @@ export default {
|
|
|
125
117
|
this.pause();
|
|
126
118
|
} else if (command === 'C6') {
|
|
127
119
|
this.robotStatus = 'leaving';
|
|
120
|
+
this.avaterStatus = 'normal';
|
|
128
121
|
this.stop();
|
|
129
122
|
}
|
|
130
123
|
}
|
|
@@ -64,7 +64,7 @@ export default {
|
|
|
64
64
|
|
|
65
65
|
try {
|
|
66
66
|
const startTime = Date.now();
|
|
67
|
-
const token = getCookie('bonyear-access_token') || `
|
|
67
|
+
const token = getCookie('bonyear-access_token') || `44e7f112-63f3-429d-908d-2c97ec380de2`;
|
|
68
68
|
|
|
69
69
|
const response = await fetch(API_URL, {
|
|
70
70
|
method: 'POST',
|
|
@@ -72,7 +72,7 @@ export default {
|
|
|
72
72
|
'Content-Type': 'application/json',
|
|
73
73
|
'Authorization': `Bearer ${token}`,
|
|
74
74
|
},
|
|
75
|
-
body: JSON.stringify({ content: message,
|
|
75
|
+
body: JSON.stringify({ content: message, chat_id: this.chatId })
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
if (!response.ok) {
|
|
@@ -126,9 +126,10 @@ export default {
|
|
|
126
126
|
if (done) break;
|
|
127
127
|
|
|
128
128
|
const chunk = decoder.decode(value, { stream: true });
|
|
129
|
-
|
|
129
|
+
console.log('收到数据块:', chunk);
|
|
130
130
|
// 使用解析器处理数据块,确保this指向正确
|
|
131
131
|
this.streamParser.processChunk(chunk, function(result) {
|
|
132
|
+
console.log('处理数据块:', result);
|
|
132
133
|
self.handleStreamUpdate(result);
|
|
133
134
|
});
|
|
134
135
|
}
|
|
@@ -154,6 +155,7 @@ export default {
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
// 更新回复内容
|
|
158
|
+
console.log('更新回复内容:', result.content);
|
|
157
159
|
if (result.content) {
|
|
158
160
|
this.currentMessage.content += result.content;
|
|
159
161
|
}
|
|
@@ -75,6 +75,10 @@ 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';
|
|
79
83
|
|
|
80
84
|
// 性能检测起点
|
|
@@ -97,6 +101,8 @@ export default {
|
|
|
97
101
|
this.analyzeAudioCommand(data.category);
|
|
98
102
|
} else {
|
|
99
103
|
console.log('状态: 其他');
|
|
104
|
+
this.avaterStatus = 'normal';
|
|
105
|
+
this.robotStatus = 'leaving';
|
|
100
106
|
}
|
|
101
107
|
},
|
|
102
108
|
|