byt-lingxiao-ai 0.2.3 → 0.2.4
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.
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
</audio>
|
|
13
13
|
<div v-if="robotStatus !== 'leaving'" :class="['chat-robot', robotStatus]" ></div>
|
|
14
14
|
<div v-else class="chat-ai" @click="toggleWindow">
|
|
15
|
-
<div class="chat-ai-avater"></div>
|
|
16
|
-
<div class="chat-ai-text"
|
|
15
|
+
<div :class="['chat-ai-avater', avaterStatus]"></div>
|
|
16
|
+
<div class="chat-ai-text">{{ avaterText }}</div>
|
|
17
17
|
</div>
|
|
18
18
|
<!-- 添加一个遮罩层,用于捕获点击外部事件 -->
|
|
19
19
|
<div class="chat-overlay" v-show="visible" @click="handleOverlayClick">
|
|
@@ -58,7 +58,13 @@
|
|
|
58
58
|
<div class="user-message">{{ message.content }}</div>
|
|
59
59
|
</div>
|
|
60
60
|
<div class="chat-window-message-ai" v-else>
|
|
61
|
-
<div class="ai-
|
|
61
|
+
<div class="ai-render">
|
|
62
|
+
<div class="ai-thinking">
|
|
63
|
+
<div class="ai-thinking-time">思考用时{{ message.time }}秒</div>
|
|
64
|
+
<div class="ai-thinking-content">{{ message.thinking }}</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="ai-content">{{ message.content }}</div>
|
|
67
|
+
</div>
|
|
62
68
|
</div>
|
|
63
69
|
</div>
|
|
64
70
|
</div>
|
|
@@ -140,22 +146,15 @@ export default {
|
|
|
140
146
|
type: 'ai',
|
|
141
147
|
sender: 'AI',
|
|
142
148
|
time: '',
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
content: '
|
|
151
|
-
}
|
|
152
|
-
{
|
|
153
|
-
id: 4,
|
|
154
|
-
type: 'user',
|
|
155
|
-
sender: '用户',
|
|
156
|
-
time: '',
|
|
157
|
-
content: '你好,欢迎来到凌霄大模型AI对话。',
|
|
158
|
-
},
|
|
149
|
+
thinking: '嗯,用户问的是回转窑的工业应用。首先,我需要回忆一下之前对话的内容。用户之前让我解释了水泥的制作流程,特别是提到了回转窑在高温煅烧熟料中的作用。',
|
|
150
|
+
charts: [
|
|
151
|
+
{
|
|
152
|
+
title: '',
|
|
153
|
+
options: {}
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
content: '回转窑(Rotary Kiln)是一种长筒形旋转煅烧设备(类似倾斜安装的大管子),因其独特的旋转运动和高温耐火衬里设计,在多个工业领域都有广泛应用',
|
|
157
|
+
}
|
|
159
158
|
], // 消息列表
|
|
160
159
|
isRecording: false, // 正在录制
|
|
161
160
|
isMicAvailable: false, // 麦克风是否可用
|
|
@@ -168,9 +167,24 @@ export default {
|
|
|
168
167
|
microphone: null, // 麦克风输入节点
|
|
169
168
|
processor: null, // 音频处理节点
|
|
170
169
|
robotStatus: 'leaving', // 机器人状态 waiting, speaking, leaving, entering
|
|
170
|
+
avaterStatus: 'normal', // 头像状态 normal output thinking
|
|
171
|
+
buffer: '', // 音频缓冲区
|
|
172
|
+
currentMessage: null, // 当前消息
|
|
173
|
+
inTag: false, // 是否在标签页
|
|
174
|
+
tagBuilder: '', // 标签构建器
|
|
171
175
|
audioBuffer: new Float32Array(0) // 音频缓冲区
|
|
172
176
|
}
|
|
173
177
|
},
|
|
178
|
+
computed: {
|
|
179
|
+
avaterText() {
|
|
180
|
+
const textMap = {
|
|
181
|
+
'normal': '凌霄AI',
|
|
182
|
+
'thinking': '思考中',
|
|
183
|
+
'output': '语音中'
|
|
184
|
+
}
|
|
185
|
+
return textMap[this.avaterStatus]
|
|
186
|
+
}
|
|
187
|
+
},
|
|
174
188
|
mounted() {
|
|
175
189
|
this.initWebSocket()
|
|
176
190
|
|
|
@@ -277,8 +291,10 @@ export default {
|
|
|
277
291
|
} else if (data.code === 0) {
|
|
278
292
|
if (data.data.type === 'detection') {
|
|
279
293
|
console.log('检测到唤醒词...');
|
|
294
|
+
this.avaterStatus = 'normal'
|
|
280
295
|
} else if (data.data.type === 'Collecting') {
|
|
281
296
|
console.log('状态: 采集中...');
|
|
297
|
+
this.avaterStatus = 'thinking'
|
|
282
298
|
} else if (data.data.type === 'command') {
|
|
283
299
|
// 根据指令改变机器人的状态
|
|
284
300
|
console.log('状态: 处理中...')
|
|
@@ -316,6 +332,32 @@ export default {
|
|
|
316
332
|
this.ws.close()
|
|
317
333
|
}
|
|
318
334
|
},
|
|
335
|
+
createAiMessage() {
|
|
336
|
+
const message = {
|
|
337
|
+
id: this.messages.length + 1,
|
|
338
|
+
type: 'ai',
|
|
339
|
+
sender: 'AI',
|
|
340
|
+
time: '',
|
|
341
|
+
thinking: '',
|
|
342
|
+
charts: [],
|
|
343
|
+
content: '',
|
|
344
|
+
}
|
|
345
|
+
this.messages.push(message)
|
|
346
|
+
this.currentMessage = message
|
|
347
|
+
return message
|
|
348
|
+
},
|
|
349
|
+
createUserMessage(content) {
|
|
350
|
+
const message = {
|
|
351
|
+
id: this.messages.length + 1,
|
|
352
|
+
type: 'user',
|
|
353
|
+
sender: '用户',
|
|
354
|
+
time: '',
|
|
355
|
+
content,
|
|
356
|
+
}
|
|
357
|
+
this.messages.push(message)
|
|
358
|
+
this.inputMessage = ''
|
|
359
|
+
return message
|
|
360
|
+
},
|
|
319
361
|
async initAudio() {
|
|
320
362
|
if (this.isRecording) return;
|
|
321
363
|
try {
|
|
@@ -356,21 +398,20 @@ export default {
|
|
|
356
398
|
return
|
|
357
399
|
}
|
|
358
400
|
const message = this.inputMessage.trim();
|
|
401
|
+
// 初始化信息
|
|
402
|
+
this.initState()
|
|
359
403
|
// 发送消息
|
|
360
|
-
this.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
time: new Date().toLocaleTimeString(),
|
|
365
|
-
content: this.inputMessage,
|
|
366
|
-
})
|
|
367
|
-
this.inputMessage = ''
|
|
368
|
-
|
|
404
|
+
this.createUserMessage(message)
|
|
405
|
+
// 创建AI消息
|
|
406
|
+
this.createAiMessage()
|
|
407
|
+
|
|
369
408
|
try {
|
|
370
|
-
const
|
|
409
|
+
const controller = new AbortController();
|
|
410
|
+
const token = `Bearer e298f087-85bc-48c2-afb9-7c69ffc911aa`
|
|
371
411
|
const response = await fetch('/bytserver/api-model/chat/stream', {
|
|
372
412
|
timeout: 30000,
|
|
373
413
|
method: 'POST',
|
|
414
|
+
signal: controller.signal,
|
|
374
415
|
headers: {
|
|
375
416
|
'Content-Type': 'application/json' ,
|
|
376
417
|
'Authorization': token,
|
|
@@ -381,13 +422,21 @@ export default {
|
|
|
381
422
|
throw new Error(`${response.status}`);
|
|
382
423
|
}
|
|
383
424
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
425
|
+
const render = response.body.getReader()
|
|
426
|
+
const decoder = new TextDecoder()
|
|
427
|
+
|
|
428
|
+
// eslint-disable-next-line no-constant-condition
|
|
429
|
+
while (true) {
|
|
430
|
+
const { done, value } = await render.read()
|
|
431
|
+
if (done) break
|
|
432
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
433
|
+
this.processStreamChunk(chunk)
|
|
434
|
+
}
|
|
387
435
|
} catch (error) {
|
|
388
436
|
console.error('发送消息失败:', error)
|
|
389
437
|
}
|
|
390
438
|
},
|
|
439
|
+
initState() {},
|
|
391
440
|
// 分析音频命令
|
|
392
441
|
analyzeAudioCommand(command) {
|
|
393
442
|
console.log('分析音频命令:', command)
|
|
@@ -415,9 +464,97 @@ export default {
|
|
|
415
464
|
this.stop()
|
|
416
465
|
}
|
|
417
466
|
},
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
467
|
+
processStreamChunk(chunk) {
|
|
468
|
+
console.log('原始数据:', chunk)
|
|
469
|
+
try {
|
|
470
|
+
this.buffer += chunk;
|
|
471
|
+
|
|
472
|
+
// eslint-disable-next-line no-constant-condition
|
|
473
|
+
while (true) {
|
|
474
|
+
const eventEnd = this.buffer.indexOf('\n\n');
|
|
475
|
+
if (eventEnd === -1) break;
|
|
476
|
+
|
|
477
|
+
const eventData = this.buffer.slice(0, eventEnd);
|
|
478
|
+
this.buffer = this.buffer.slice(eventEnd + 2);
|
|
479
|
+
console.log('解析数据:', eventData)
|
|
480
|
+
this.processEventData(eventData);
|
|
481
|
+
}
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error('流数据处理异常:', error);
|
|
484
|
+
this.streaming = false;
|
|
485
|
+
this.response = this.$t('3d.chat.dataFormatError');
|
|
486
|
+
this.$forceUpdate();
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
processEventData(data) {
|
|
490
|
+
data.split('\n').forEach(line => {
|
|
491
|
+
console.log('原始数据:', line)
|
|
492
|
+
if (!line.startsWith('data:')) return;
|
|
493
|
+
|
|
494
|
+
const jsonStr = line.replace(/^data:\s*/, '').trim();
|
|
495
|
+
if (jsonStr === '[DONE]') return;
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
const data = this.safeJsonParse(jsonStr);
|
|
499
|
+
this.processContentDelta(data.choices[0].delta);
|
|
500
|
+
} catch (e) {
|
|
501
|
+
console.warn('JSON解析跳过:', e.message);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
},
|
|
505
|
+
safeJsonParse(jsonStr) {
|
|
506
|
+
try {
|
|
507
|
+
return JSON.parse(jsonStr);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.warn('JSON parse failed:', jsonStr);
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
processContentDelta(delta) {
|
|
514
|
+
const content = delta.content || '';
|
|
515
|
+
if (!content || !this.currentMessage) return;
|
|
516
|
+
|
|
517
|
+
for (let i = 0; i < content.length; i++) {
|
|
518
|
+
const char = content[i];
|
|
519
|
+
|
|
520
|
+
// 处理正在拼接的标签
|
|
521
|
+
if (this.inTag) {
|
|
522
|
+
this.tagBuilder += char;
|
|
523
|
+
|
|
524
|
+
if (char === '>') {
|
|
525
|
+
const tag = this.tagBuilder;
|
|
526
|
+
|
|
527
|
+
if (tag === '<think>') {
|
|
528
|
+
this.avaterStatus = 'thinking'
|
|
529
|
+
} else if (tag === '</think>') {
|
|
530
|
+
this.avaterStatus = 'output'
|
|
531
|
+
} else {
|
|
532
|
+
console.log('无效标签:', tag)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// 重置
|
|
536
|
+
this.inTag = false;
|
|
537
|
+
this.tagBuilder = '';
|
|
538
|
+
}
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 检测是否是标签开始
|
|
543
|
+
if (char === '<') {
|
|
544
|
+
this.inTag = true;
|
|
545
|
+
this.tagBuilder = '<';
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// 正常字符处理
|
|
550
|
+
if (this.avaterStatus === 'thinking') {
|
|
551
|
+
this.currentMessage.thinking += char;
|
|
552
|
+
} else {
|
|
553
|
+
this.currentMessage.content += char;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
this.$forceUpdate();
|
|
421
558
|
},
|
|
422
559
|
processAudio(event) {
|
|
423
560
|
if (!this.isRecording) return;
|
|
@@ -600,8 +737,16 @@ export default {
|
|
|
600
737
|
align-items: center;
|
|
601
738
|
background-size: cover;
|
|
602
739
|
background-position: center;
|
|
740
|
+
}
|
|
741
|
+
.chat-ai-avater.normal {
|
|
603
742
|
background-image: url('./assets/normal.png');
|
|
604
743
|
}
|
|
744
|
+
.chat-ai-avater.thinking {
|
|
745
|
+
background-image: url('./assets/thinking.png');
|
|
746
|
+
}
|
|
747
|
+
.chat-ai-avater.output {
|
|
748
|
+
background-image: url('./assets/output.png');
|
|
749
|
+
}
|
|
605
750
|
.chat-ai-text {
|
|
606
751
|
color: #fff;
|
|
607
752
|
font-family: 'PingFang SC';
|
|
@@ -639,9 +784,7 @@ export default {
|
|
|
639
784
|
display: flex;
|
|
640
785
|
gap: 12px;
|
|
641
786
|
align-items: flex-start;
|
|
642
|
-
|
|
643
|
-
.ai-message {
|
|
644
|
-
max-width: 80%;
|
|
787
|
+
max-width: 100%;
|
|
645
788
|
border-radius: 12px;
|
|
646
789
|
background: #f6f8fc;
|
|
647
790
|
padding: 8px 12px;
|
|
@@ -680,6 +823,19 @@ export default {
|
|
|
680
823
|
color: #29414e;
|
|
681
824
|
font-size: 18px;
|
|
682
825
|
font-weight: 900;
|
|
826
|
+
position: relative;
|
|
827
|
+
padding-left:36px;
|
|
828
|
+
}
|
|
829
|
+
.chat-window-header-title::before {
|
|
830
|
+
content: '';
|
|
831
|
+
position: absolute;
|
|
832
|
+
left: 0;
|
|
833
|
+
top: 50%;
|
|
834
|
+
transform: translateY(-50%);
|
|
835
|
+
width: 32px;
|
|
836
|
+
height: 32px;
|
|
837
|
+
background-image: url('./assets/logo.png');
|
|
838
|
+
background-size: cover;
|
|
683
839
|
}
|
|
684
840
|
.chat-window-header-close {
|
|
685
841
|
width: 24px;
|
|
@@ -715,4 +871,52 @@ export default {
|
|
|
715
871
|
justify-content: center;
|
|
716
872
|
cursor: pointer;
|
|
717
873
|
}
|
|
874
|
+
.ai-thinking-time {
|
|
875
|
+
border-radius: 9px;
|
|
876
|
+
background: #ECEDF4;
|
|
877
|
+
color: #86909C;
|
|
878
|
+
font-family: "Alibaba PuHuiTi 2.0";
|
|
879
|
+
font-size: 16px;
|
|
880
|
+
font-style: normal;
|
|
881
|
+
font-weight: 500;
|
|
882
|
+
padding: 0 26px 0 26px;
|
|
883
|
+
height: 28px;
|
|
884
|
+
box-sizing: border-box;
|
|
885
|
+
display: inline-flex;
|
|
886
|
+
align-items: center;
|
|
887
|
+
position: relative;
|
|
888
|
+
user-select: none;
|
|
889
|
+
cursor: pointer;
|
|
890
|
+
}
|
|
891
|
+
.ai-thinking-time::before {
|
|
892
|
+
content: '';
|
|
893
|
+
position: absolute;
|
|
894
|
+
left: 6px;
|
|
895
|
+
top: 50%;
|
|
896
|
+
transform: translateY(-50%);
|
|
897
|
+
width: 16px;
|
|
898
|
+
height: 16px;
|
|
899
|
+
background: url('./assets/think.png') no-repeat;
|
|
900
|
+
background-size: cover;
|
|
901
|
+
}
|
|
902
|
+
.ai-thinking-time::after {
|
|
903
|
+
content: '';
|
|
904
|
+
position: absolute;
|
|
905
|
+
right: 6px;
|
|
906
|
+
top: 50%;
|
|
907
|
+
transform: translateY(-50%);
|
|
908
|
+
width: 16px;
|
|
909
|
+
height: 16px;
|
|
910
|
+
background: url('./assets/arrow.png') no-repeat;
|
|
911
|
+
background-size: cover;
|
|
912
|
+
}
|
|
913
|
+
.ai-thinking-content {
|
|
914
|
+
color: #86909C;
|
|
915
|
+
font-family: "Alibaba PuHuiTi 2.0";
|
|
916
|
+
font-size: 14px;
|
|
917
|
+
font-style: normal;
|
|
918
|
+
font-weight: 400;
|
|
919
|
+
line-height: 24px;
|
|
920
|
+
padding: 8px 0 8px 12px;
|
|
921
|
+
}
|
|
718
922
|
</style>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|