byt-lingxiao-ai 0.2.3 → 0.2.5

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">凌霄AI</div>
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-message">{{ message.content }}</div>
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
- content: '欢迎来到凌霄大模型AI对话。',
144
- },
145
- {
146
- id: 3,
147
- type: 'ai',
148
- sender: 'AI',
149
- time: '',
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.messages.push({
361
- id: this.messages.length + 1,
362
- type: 'user',
363
- sender: '用户',
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 token = `Bearer 24ab99b4-4b59-42a0-84df-1d73a96e70cd`
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
- console.log('响应状态:', response.status);
385
- // 解析响应流
386
- this.parseResponseStream(response.body)
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
- parseResponseStream(body) {
420
- console.log(body)
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
Binary file
Binary file