byt-lingxiao-ai 0.2.8 → 0.3.0
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 +358 -3
- package/components/ChatAvatar.vue +1 -1
- package/components/ChatMessageList.vue +5 -0
- package/components/ChatWindow.vue +4 -2
- package/components/ChatWindowDialog.vue +5 -0
- package/components/config/index.js +4 -0
- package/components/mixins/messageMixin.js +20 -9
- package/components/mixins/webSocketMixin.js +3 -1
- package/components/utils/StreamParser.js +67 -39
- package/dist/index.common.js +29626 -2953
- package/dist/index.common.js.map +1 -1
- package/dist/index.css +11 -1
- package/dist/index.umd.js +29616 -2943
- 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 +5 -3
package/components/AiMessage.vue
CHANGED
|
@@ -1,16 +1,158 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="chat-window-message-ai">
|
|
3
3
|
<div class="ai-render">
|
|
4
|
-
<div class="ai-
|
|
4
|
+
<div class="ai-loading" v-if="loading">
|
|
5
|
+
<div class="dot"></div>
|
|
6
|
+
<div class="dot"></div>
|
|
7
|
+
<div class="dot"></div>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="ai-thinking" @click="$emit('thinking-click')" v-if="message.thinking">
|
|
5
10
|
<div class="ai-thinking-time">思考用时{{ message.time }}秒</div>
|
|
6
11
|
<div class="ai-thinking-content" v-if="thinkStatus">{{ message.thinking }}</div>
|
|
7
12
|
</div>
|
|
8
|
-
<div class="ai-content"
|
|
13
|
+
<div class="ai-content markdown-body" v-html="renderedContent"></div>
|
|
9
14
|
</div>
|
|
10
15
|
</div>
|
|
11
16
|
</template>
|
|
12
17
|
|
|
13
18
|
<script>
|
|
19
|
+
import { marked } from 'marked';
|
|
20
|
+
import hljs from 'highlight.js';
|
|
21
|
+
import 'highlight.js/styles/github.css';
|
|
22
|
+
|
|
23
|
+
marked.setOptions({
|
|
24
|
+
highlight: function(code, lang) {
|
|
25
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
26
|
+
return hljs.highlight(code, { language: lang }).value;
|
|
27
|
+
}
|
|
28
|
+
return hljs.highlightAuto(code).value;
|
|
29
|
+
},
|
|
30
|
+
breaks: true,
|
|
31
|
+
gfm: true
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function parseMarkdown(text) {
|
|
35
|
+
if (!text) return '';
|
|
36
|
+
let html = text;
|
|
37
|
+
|
|
38
|
+
html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
|
|
39
|
+
return `<pre><code class="language-${lang || 'text'}">${escapeHtml(code.trim())}</code></pre>`;
|
|
40
|
+
});
|
|
41
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
42
|
+
html = parseTable(html);
|
|
43
|
+
|
|
44
|
+
html = html.replace(/^#### (.*$)/gm, '<h4>$1</h4>');
|
|
45
|
+
html = html.replace(/^### (.*$)/gm, '<h3>$1</h3>');
|
|
46
|
+
html = html.replace(/^## (.*$)/gm, '<h2>$1</h2>');
|
|
47
|
+
html = html.replace(/^# (.*$)/gm, '<h1>$1</h1>');
|
|
48
|
+
|
|
49
|
+
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
50
|
+
|
|
51
|
+
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
|
52
|
+
|
|
53
|
+
html = html.replace(/~~(.*?)~~/g, '<del>$1</del>');
|
|
54
|
+
|
|
55
|
+
html = html.replace(/^\s*[-*+]\s+(.+)$/gm, '<li>$1</li>');
|
|
56
|
+
html = html.replace(/(<li>.*?<\/li>)/gs, '<ul>$1</ul>');
|
|
57
|
+
|
|
58
|
+
html = html.replace(/^\s*\d+\.\s+(.+)$/gm, '<li>$1</li>');
|
|
59
|
+
|
|
60
|
+
html = html.replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>');
|
|
61
|
+
|
|
62
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
|
63
|
+
|
|
64
|
+
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />');
|
|
65
|
+
|
|
66
|
+
html = html.replace(/^-{3,}$/gm, '<hr>');
|
|
67
|
+
|
|
68
|
+
html = html.replace(/\n/g, '<br>');
|
|
69
|
+
|
|
70
|
+
return html;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 解析表格
|
|
74
|
+
function parseTable(text) {
|
|
75
|
+
const lines = text.split('\n');
|
|
76
|
+
let result = [];
|
|
77
|
+
let inTable = false;
|
|
78
|
+
let tableRows = [];
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < lines.length; i++) {
|
|
81
|
+
const line = lines[i].trim();
|
|
82
|
+
|
|
83
|
+
// 检测表格行
|
|
84
|
+
if (line.includes('|') && line.split('|').length >= 3) {
|
|
85
|
+
if (!inTable) {
|
|
86
|
+
inTable = true;
|
|
87
|
+
tableRows = [];
|
|
88
|
+
}
|
|
89
|
+
tableRows.push(line);
|
|
90
|
+
|
|
91
|
+
// 检查下一行是否还是表格
|
|
92
|
+
if (i === lines.length - 1 || !lines[i + 1].includes('|')) {
|
|
93
|
+
// 表格结束
|
|
94
|
+
result.push(renderTable(tableRows));
|
|
95
|
+
inTable = false;
|
|
96
|
+
tableRows = [];
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
if (inTable) {
|
|
100
|
+
// 表格意外结束
|
|
101
|
+
result.push(renderTable(tableRows));
|
|
102
|
+
inTable = false;
|
|
103
|
+
tableRows = [];
|
|
104
|
+
}
|
|
105
|
+
result.push(line);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result.join('\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 渲染表格
|
|
113
|
+
function renderTable(rows) {
|
|
114
|
+
if (rows.length < 2) return rows.join('\n');
|
|
115
|
+
|
|
116
|
+
let html = '<div class="table-wrapper"><table class="markdown-table">';
|
|
117
|
+
|
|
118
|
+
// 表头
|
|
119
|
+
const headerCells = rows[0].split('|').filter(cell => cell.trim());
|
|
120
|
+
html += '<thead><tr>';
|
|
121
|
+
headerCells.forEach(cell => {
|
|
122
|
+
html += `<th>${cell.trim()}</th>`;
|
|
123
|
+
});
|
|
124
|
+
html += '</tr></thead>';
|
|
125
|
+
|
|
126
|
+
// 表体(跳过分隔行)
|
|
127
|
+
html += '<tbody>';
|
|
128
|
+
for (let i = 2; i < rows.length; i++) {
|
|
129
|
+
const cells = rows[i].split('|').filter(cell => cell.trim());
|
|
130
|
+
if (cells.length > 0) {
|
|
131
|
+
html += '<tr>';
|
|
132
|
+
cells.forEach(cell => {
|
|
133
|
+
html += `<td>${cell.trim()}</td>`;
|
|
134
|
+
});
|
|
135
|
+
html += '</tr>';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
html += '</tbody>';
|
|
139
|
+
|
|
140
|
+
html += '</table></div>';
|
|
141
|
+
return html;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// HTML 转义
|
|
145
|
+
function escapeHtml(text) {
|
|
146
|
+
const map = {
|
|
147
|
+
'&': '&',
|
|
148
|
+
'<': '<',
|
|
149
|
+
'>': '>',
|
|
150
|
+
'"': '"',
|
|
151
|
+
"'": '''
|
|
152
|
+
};
|
|
153
|
+
return text.replace(/[&<>"']/g, m => map[m]);
|
|
154
|
+
}
|
|
155
|
+
|
|
14
156
|
export default {
|
|
15
157
|
name: 'AiMessage',
|
|
16
158
|
props: {
|
|
@@ -21,12 +163,66 @@ export default {
|
|
|
21
163
|
thinkStatus: {
|
|
22
164
|
type: Boolean,
|
|
23
165
|
default: true
|
|
166
|
+
},
|
|
167
|
+
loading: {
|
|
168
|
+
type: Boolean,
|
|
169
|
+
default: false
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
computed: {
|
|
173
|
+
renderedContent() {
|
|
174
|
+
return parseMarkdown(this.message.content);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
watch: {
|
|
178
|
+
thinkStatus(newVal, oldVal) {
|
|
179
|
+
console.log('thinkStatus 变化:', newVal, oldVal);
|
|
24
180
|
}
|
|
25
181
|
}
|
|
26
182
|
}
|
|
27
183
|
</script>
|
|
28
184
|
|
|
29
185
|
<style scoped>
|
|
186
|
+
/* Loading 容器 */
|
|
187
|
+
.ai-loading {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
padding: 12px 0;
|
|
191
|
+
height: 24px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* 单个圆点样式 */
|
|
195
|
+
.dot {
|
|
196
|
+
width: 8px;
|
|
197
|
+
height: 8px;
|
|
198
|
+
margin-right: 6px;
|
|
199
|
+
background-color: #86909C; /* 与你的思考字体颜色一致 */
|
|
200
|
+
border-radius: 50%;
|
|
201
|
+
animation: dot-bounce 1.4s infinite ease-in-out both;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.dot:nth-child(1) {
|
|
205
|
+
animation-delay: -0.32s;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.dot:nth-child(2) {
|
|
209
|
+
animation-delay: -0.16s;
|
|
210
|
+
}
|
|
211
|
+
.dot:nth-child(3) {
|
|
212
|
+
animation-delay: 0s;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* 定义跳动动画 */
|
|
216
|
+
@keyframes dot-bounce {
|
|
217
|
+
0%, 80%, 100% {
|
|
218
|
+
transform: scale(0);
|
|
219
|
+
opacity: 0.5;
|
|
220
|
+
}
|
|
221
|
+
40% {
|
|
222
|
+
transform: scale(1);
|
|
223
|
+
opacity: 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
30
226
|
.chat-window-message-ai {
|
|
31
227
|
display: flex;
|
|
32
228
|
gap: 12px;
|
|
@@ -99,11 +295,170 @@ export default {
|
|
|
99
295
|
|
|
100
296
|
.ai-content {
|
|
101
297
|
color: #4E5969;
|
|
102
|
-
text-align: justify;
|
|
103
298
|
font-family: "Alibaba PuHuiTi 2.0";
|
|
104
299
|
font-size: 16px;
|
|
105
300
|
font-style: normal;
|
|
106
301
|
font-weight: 400;
|
|
107
302
|
line-height: 24px;
|
|
108
303
|
}
|
|
304
|
+
|
|
305
|
+
/* Markdown 样式 */
|
|
306
|
+
.markdown-body {
|
|
307
|
+
word-wrap: break-word;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.markdown-body ::v-deep h1,
|
|
311
|
+
.markdown-body ::v-deep h2,
|
|
312
|
+
.markdown-body ::v-deep h3,
|
|
313
|
+
.markdown-body ::v-deep h4 {
|
|
314
|
+
margin: 16px 0 8px 0;
|
|
315
|
+
font-weight: 600;
|
|
316
|
+
line-height: 1.4;
|
|
317
|
+
color: #1f2937;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.markdown-body ::v-deep h1 {
|
|
321
|
+
font-size: 24px;
|
|
322
|
+
border-bottom: 2px solid #e5e7eb;
|
|
323
|
+
padding-bottom: 8px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.markdown-body ::v-deep h2 {
|
|
327
|
+
font-size: 20px;
|
|
328
|
+
border-bottom: 1px solid #e5e7eb;
|
|
329
|
+
padding-bottom: 6px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.markdown-body ::v-deep h3 {
|
|
333
|
+
font-size: 18px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.markdown-body ::v-deep h4 {
|
|
337
|
+
font-size: 16px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.markdown-body ::v-deep code {
|
|
341
|
+
background-color: rgba(175, 184, 193, 0.2);
|
|
342
|
+
border-radius: 3px;
|
|
343
|
+
font-size: 85%;
|
|
344
|
+
margin: 0;
|
|
345
|
+
padding: 0.2em 0.4em;
|
|
346
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
|
|
347
|
+
color: #e83e8c;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.markdown-body ::v-deep pre {
|
|
351
|
+
background-color: #f6f8fa;
|
|
352
|
+
border-radius: 6px;
|
|
353
|
+
padding: 16px;
|
|
354
|
+
overflow: auto;
|
|
355
|
+
margin: 12px 0;
|
|
356
|
+
border: 1px solid #e1e4e8;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.markdown-body ::v-deep pre code {
|
|
360
|
+
background-color: transparent;
|
|
361
|
+
border: 0;
|
|
362
|
+
display: inline;
|
|
363
|
+
line-height: inherit;
|
|
364
|
+
margin: 0;
|
|
365
|
+
overflow: visible;
|
|
366
|
+
padding: 0;
|
|
367
|
+
word-wrap: normal;
|
|
368
|
+
color: #24292e;
|
|
369
|
+
font-size: 14px;
|
|
370
|
+
}
|
|
371
|
+
.markdown-body ::v-deep .table-wrapper{
|
|
372
|
+
overflow-x: auto;
|
|
373
|
+
border: 1px solid #dfe2e5;
|
|
374
|
+
}
|
|
375
|
+
.markdown-body ::v-deep .markdown-table {
|
|
376
|
+
border-collapse: collapse;
|
|
377
|
+
width: 100%;
|
|
378
|
+
margin: 12px 0;
|
|
379
|
+
font-size: 14px;
|
|
380
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
381
|
+
overflow: hidden;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.markdown-body ::v-deep .markdown-table th,
|
|
385
|
+
.markdown-body ::v-deep .markdown-table td {
|
|
386
|
+
border: 1px solid #dfe2e5;
|
|
387
|
+
padding: 10px 14px;
|
|
388
|
+
text-align: left;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.markdown-body ::v-deep .markdown-table th {
|
|
392
|
+
background-color: #f3f4f6;
|
|
393
|
+
font-weight: 600;
|
|
394
|
+
color: #374151;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.markdown-body ::v-deep .markdown-table tr:nth-child(even) {
|
|
398
|
+
background-color: #f9fafb;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.markdown-body ::v-deep .markdown-table tr:hover {
|
|
402
|
+
background-color: #f3f4f6;
|
|
403
|
+
transition: background-color 0.2s;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.markdown-body ::v-deep ul,
|
|
407
|
+
.markdown-body ::v-deep ol {
|
|
408
|
+
padding-left: 24px;
|
|
409
|
+
margin: 8px 0;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.markdown-body ::v-deep li {
|
|
413
|
+
margin: 4px 0;
|
|
414
|
+
line-height: 1.6;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.markdown-body ::v-deep strong {
|
|
418
|
+
font-weight: 600;
|
|
419
|
+
color: #1f2937;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.markdown-body ::v-deep em {
|
|
423
|
+
font-style: italic;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.markdown-body ::v-deep del {
|
|
427
|
+
text-decoration: line-through;
|
|
428
|
+
opacity: 0.7;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.markdown-body ::v-deep blockquote {
|
|
432
|
+
border-left: 4px solid #dfe2e5;
|
|
433
|
+
padding-left: 16px;
|
|
434
|
+
margin: 12px 0;
|
|
435
|
+
color: #6b7280;
|
|
436
|
+
font-style: italic;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.markdown-body ::v-deep a {
|
|
440
|
+
color: #3b82f6;
|
|
441
|
+
text-decoration: none;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.markdown-body ::v-deep a:hover {
|
|
445
|
+
text-decoration: underline;
|
|
446
|
+
color: #2563eb;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.markdown-body ::v-deep img {
|
|
450
|
+
max-width: 100%;
|
|
451
|
+
border-radius: 6px;
|
|
452
|
+
margin: 12px 0;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.markdown-body ::v-deep hr {
|
|
456
|
+
border: none;
|
|
457
|
+
border-top: 1px solid #e5e7eb;
|
|
458
|
+
margin: 16px 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.markdown-body ::v-deep br {
|
|
462
|
+
line-height: 1.6;
|
|
463
|
+
}
|
|
109
464
|
</style>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
v-else
|
|
14
14
|
:message="message"
|
|
15
15
|
:think-status="thinkStatus"
|
|
16
|
+
:loading="isLoading"
|
|
16
17
|
@thinking-click="$emit('thinking-click')"
|
|
17
18
|
/>
|
|
18
19
|
</div>
|
|
@@ -37,6 +38,10 @@ export default {
|
|
|
37
38
|
thinkStatus: {
|
|
38
39
|
type: Boolean,
|
|
39
40
|
default: true
|
|
41
|
+
},
|
|
42
|
+
isLoading: {
|
|
43
|
+
type: Boolean,
|
|
44
|
+
default: false
|
|
40
45
|
}
|
|
41
46
|
},
|
|
42
47
|
methods: {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
:messages="messages"
|
|
31
31
|
:input-message="inputMessage"
|
|
32
32
|
:think-status="thinkStatus"
|
|
33
|
+
:loading="isLoading"
|
|
33
34
|
@update:inputMessage="inputMessage = $event"
|
|
34
35
|
@send="handleSend"
|
|
35
36
|
@thinking-click="handleThinkingClick"
|
|
@@ -45,6 +46,7 @@ import ChatWindowDialog from './ChatWindowDialog.vue'
|
|
|
45
46
|
import audioMixin from './mixins/audioMixin'
|
|
46
47
|
import webSocketMixin from './mixins/webSocketMixin'
|
|
47
48
|
import messageMixin from './mixins/messageMixin'
|
|
49
|
+
import { AUDIO_URL, TIME_JUMP_POINTS_URL } from './config/index.js'
|
|
48
50
|
|
|
49
51
|
const SAMPLE_RATE = 16000;
|
|
50
52
|
const FRAME_SIZE = 512;
|
|
@@ -65,7 +67,7 @@ export default {
|
|
|
65
67
|
},
|
|
66
68
|
data() {
|
|
67
69
|
return {
|
|
68
|
-
audioSrc:
|
|
70
|
+
audioSrc: AUDIO_URL,
|
|
69
71
|
inputMessage: '',
|
|
70
72
|
visible: false,
|
|
71
73
|
messages: [],
|
|
@@ -117,7 +119,7 @@ export default {
|
|
|
117
119
|
this.jumpedTimePoints = new Set()
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
fetch(
|
|
122
|
+
fetch(TIME_JUMP_POINTS_URL)
|
|
121
123
|
.then(response => response.json())
|
|
122
124
|
.then(data => {
|
|
123
125
|
console.log('时间跳转点:', data)
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
ref="messageList"
|
|
10
10
|
:messages="messages"
|
|
11
11
|
:think-status="thinkStatus"
|
|
12
|
+
:loading="loading"
|
|
12
13
|
@thinking-click="$emit('thinking-click')"
|
|
13
14
|
/>
|
|
14
15
|
|
|
@@ -50,6 +51,10 @@ export default {
|
|
|
50
51
|
thinkStatus: {
|
|
51
52
|
type: Boolean,
|
|
52
53
|
default: true
|
|
54
|
+
},
|
|
55
|
+
loading: {
|
|
56
|
+
type: Boolean,
|
|
57
|
+
default: false
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
60
|
}
|
|
@@ -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,9 +1,11 @@
|
|
|
1
1
|
import { StreamParser } from '../utils/StreamParser'
|
|
2
|
+
import { API_URL } from '../config/index.js'
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
data() {
|
|
5
6
|
return {
|
|
6
|
-
streamParser: null
|
|
7
|
+
streamParser: null,
|
|
8
|
+
isLoading: false
|
|
7
9
|
}
|
|
8
10
|
},
|
|
9
11
|
|
|
@@ -13,6 +15,7 @@ export default {
|
|
|
13
15
|
updateInterval: 16, // 约60fps
|
|
14
16
|
debug: process.env.NODE_ENV === 'development'
|
|
15
17
|
});
|
|
18
|
+
this.isLoading = true;
|
|
16
19
|
},
|
|
17
20
|
|
|
18
21
|
methods: {
|
|
@@ -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
|
|
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(
|
|
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
|
|
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,8 @@ export default {
|
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
console.log(`流处理完成,总耗时: ${duration}ms`);
|
|
97
|
+
this.avaterStatus = 'normal';
|
|
98
|
+
this.isLoading = false;
|
|
94
99
|
|
|
95
100
|
} catch (error) {
|
|
96
101
|
console.error('发送消息失败:', error);
|
|
@@ -98,6 +103,7 @@ export default {
|
|
|
98
103
|
this.currentMessage.content = '抱歉,发生了错误,请重试。';
|
|
99
104
|
this.$forceUpdate();
|
|
100
105
|
}
|
|
106
|
+
this.isLoading = false;
|
|
101
107
|
}
|
|
102
108
|
},
|
|
103
109
|
|
|
@@ -107,6 +113,7 @@ export default {
|
|
|
107
113
|
async consumeStream(readableStream) {
|
|
108
114
|
const reader = readableStream.getReader();
|
|
109
115
|
const decoder = new TextDecoder('utf-8');
|
|
116
|
+
const self = this;
|
|
110
117
|
|
|
111
118
|
try {
|
|
112
119
|
// eslint-disable-next-line no-constant-condition
|
|
@@ -116,8 +123,10 @@ export default {
|
|
|
116
123
|
|
|
117
124
|
const chunk = decoder.decode(value, { stream: true });
|
|
118
125
|
|
|
119
|
-
//
|
|
120
|
-
this.streamParser.processChunk(chunk,
|
|
126
|
+
// 使用解析器处理数据块,确保this指向正确
|
|
127
|
+
this.streamParser.processChunk(chunk, function(result) {
|
|
128
|
+
self.handleStreamUpdate(result);
|
|
129
|
+
});
|
|
121
130
|
}
|
|
122
131
|
} finally {
|
|
123
132
|
reader.releaseLock();
|
|
@@ -130,6 +139,8 @@ export default {
|
|
|
130
139
|
handleStreamUpdate(result) {
|
|
131
140
|
if (!this.currentMessage) return;
|
|
132
141
|
|
|
142
|
+
console.log('收到更新:', result);
|
|
143
|
+
|
|
133
144
|
// 更新思考内容
|
|
134
145
|
if (result.thinking) {
|
|
135
146
|
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:
|
|
7
|
+
wsUrl: WS_URL,
|
|
6
8
|
isConnected: false,
|
|
7
9
|
reconnectCount: 0, // 重连尝试次数
|
|
8
10
|
maxReconnectAttempts: 3 // 最大重连尝试次数
|