byt-lingxiao-ai 0.2.2 → 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">凌霄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
 
@@ -195,6 +209,7 @@ export default {
195
209
  // 处理音频播放结束事件
196
210
  onAudioEnded() {
197
211
  this.robotStatus = 'leaving'
212
+ this.jumpedTimePoints.clear()
198
213
  },
199
214
  // 处理音频播放时间更新事件
200
215
  onTimeUpdate() {
@@ -206,10 +221,9 @@ export default {
206
221
  }
207
222
 
208
223
  const timeJumpPoints = [
209
- { time: 30, url: '/permission/user', name: 'permission_user', title: '用户管理' },
210
- { time: 50, url: '/permission/menu', name: 'permission_menu', title: '菜单管理' },
211
- { time: 60, url: '/permission/role', name: 'permission_role', title: '角色管理' },
212
- { time: 70, url: '/permission/dept', name: 'permission_dept', title: '部门管理' }
224
+ { time: 40, url: '/permission/user', name: 'permission_user', title: '用户管理' },
225
+ { time: 65, url: '/permission/menu', name: 'permission_menu', title: '菜单管理' },
226
+ { time: 75, url: '/permission/role', name: 'permission_role', title: '角色管理' }
213
227
  ]
214
228
  // 检查当前时间是否达到跳转点
215
229
  timeJumpPoints.forEach(point => {
@@ -248,6 +262,7 @@ export default {
248
262
  this.robotStatus = 'leaving'
249
263
  this.$refs.audioPlayer.pause()
250
264
  this.$refs.audioPlayer.currentTime = 0
265
+ this.jumpedTimePoints.clear()
251
266
  },
252
267
  initWebSocket() {
253
268
  try {
@@ -276,8 +291,10 @@ export default {
276
291
  } else if (data.code === 0) {
277
292
  if (data.data.type === 'detection') {
278
293
  console.log('检测到唤醒词...');
294
+ this.avaterStatus = 'normal'
279
295
  } else if (data.data.type === 'Collecting') {
280
296
  console.log('状态: 采集中...');
297
+ this.avaterStatus = 'thinking'
281
298
  } else if (data.data.type === 'command') {
282
299
  // 根据指令改变机器人的状态
283
300
  console.log('状态: 处理中...')
@@ -315,6 +332,32 @@ export default {
315
332
  this.ws.close()
316
333
  }
317
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
+ },
318
361
  async initAudio() {
319
362
  if (this.isRecording) return;
320
363
  try {
@@ -355,21 +398,20 @@ export default {
355
398
  return
356
399
  }
357
400
  const message = this.inputMessage.trim();
401
+ // 初始化信息
402
+ this.initState()
358
403
  // 发送消息
359
- this.messages.push({
360
- id: this.messages.length + 1,
361
- type: 'user',
362
- sender: '用户',
363
- time: new Date().toLocaleTimeString(),
364
- content: this.inputMessage,
365
- })
366
- this.inputMessage = ''
367
-
404
+ this.createUserMessage(message)
405
+ // 创建AI消息
406
+ this.createAiMessage()
407
+
368
408
  try {
369
- const token = `Bearer 24ab99b4-4b59-42a0-84df-1d73a96e70cd`
409
+ const controller = new AbortController();
410
+ const token = `Bearer e298f087-85bc-48c2-afb9-7c69ffc911aa`
370
411
  const response = await fetch('/bytserver/api-model/chat/stream', {
371
412
  timeout: 30000,
372
413
  method: 'POST',
414
+ signal: controller.signal,
373
415
  headers: {
374
416
  'Content-Type': 'application/json' ,
375
417
  'Authorization': token,
@@ -380,13 +422,21 @@ export default {
380
422
  throw new Error(`${response.status}`);
381
423
  }
382
424
 
383
- console.log('响应状态:', response.status);
384
- // 解析响应流
385
- 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
+ }
386
435
  } catch (error) {
387
436
  console.error('发送消息失败:', error)
388
437
  }
389
438
  },
439
+ initState() {},
390
440
  // 分析音频命令
391
441
  analyzeAudioCommand(command) {
392
442
  console.log('分析音频命令:', command)
@@ -414,9 +464,97 @@ export default {
414
464
  this.stop()
415
465
  }
416
466
  },
417
- // 解析响应流
418
- parseResponseStream(body) {
419
- 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();
420
558
  },
421
559
  processAudio(event) {
422
560
  if (!this.isRecording) return;
@@ -599,8 +737,16 @@ export default {
599
737
  align-items: center;
600
738
  background-size: cover;
601
739
  background-position: center;
740
+ }
741
+ .chat-ai-avater.normal {
602
742
  background-image: url('./assets/normal.png');
603
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
+ }
604
750
  .chat-ai-text {
605
751
  color: #fff;
606
752
  font-family: 'PingFang SC';
@@ -638,9 +784,7 @@ export default {
638
784
  display: flex;
639
785
  gap: 12px;
640
786
  align-items: flex-start;
641
- }
642
- .ai-message {
643
- max-width: 80%;
787
+ max-width: 100%;
644
788
  border-radius: 12px;
645
789
  background: #f6f8fc;
646
790
  padding: 8px 12px;
@@ -679,6 +823,19 @@ export default {
679
823
  color: #29414e;
680
824
  font-size: 18px;
681
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;
682
839
  }
683
840
  .chat-window-header-close {
684
841
  width: 24px;
@@ -714,4 +871,52 @@ export default {
714
871
  justify-content: center;
715
872
  cursor: pointer;
716
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
+ }
717
922
  </style>
Binary file
Binary file
Binary file
package/dist/demo.html ADDED
@@ -0,0 +1 @@
1
+ <!doctype html><meta charset="utf-8"><title>index demo</title><script src="./index.umd.js"></script><link rel="stylesheet" href="./index.css"><script>console.log(index)</script>
@@ -3746,7 +3746,7 @@ if (typeof window !== 'undefined') {
3746
3746
  var es_iterator_constructor = __webpack_require__(8111);
3747
3747
  // EXTERNAL MODULE: ./node_modules/core-js/modules/es.iterator.for-each.js
3748
3748
  var es_iterator_for_each = __webpack_require__(7588);
3749
- ;// ./node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[3]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./components/ChatWindow.vue?vue&type=template&id=6cc1c97f&scoped=true
3749
+ ;// ./node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[3]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./components/ChatWindow.vue?vue&type=template&id=43521054&scoped=true
3750
3750
  var render = function render() {
3751
3751
  var _vm = this,
3752
3752
  _c = _vm._self._c;
@@ -4043,6 +4043,7 @@ const FRAME_SIZE = 512;
4043
4043
  // 处理音频播放结束事件
4044
4044
  onAudioEnded() {
4045
4045
  this.robotStatus = 'leaving';
4046
+ this.jumpedTimePoints.clear();
4046
4047
  },
4047
4048
  // 处理音频播放时间更新事件
4048
4049
  onTimeUpdate() {
@@ -4052,25 +4053,20 @@ const FRAME_SIZE = 512;
4052
4053
  this.jumpedTimePoints = new Set();
4053
4054
  }
4054
4055
  const timeJumpPoints = [{
4055
- time: 30,
4056
+ time: 40,
4056
4057
  url: '/permission/user',
4057
4058
  name: 'permission_user',
4058
4059
  title: '用户管理'
4059
4060
  }, {
4060
- time: 50,
4061
+ time: 65,
4061
4062
  url: '/permission/menu',
4062
4063
  name: 'permission_menu',
4063
4064
  title: '菜单管理'
4064
4065
  }, {
4065
- time: 60,
4066
+ time: 75,
4066
4067
  url: '/permission/role',
4067
4068
  name: 'permission_role',
4068
4069
  title: '角色管理'
4069
- }, {
4070
- time: 70,
4071
- url: '/permission/dept',
4072
- name: 'permission_dept',
4073
- title: '部门管理'
4074
4070
  }];
4075
4071
  // 检查当前时间是否达到跳转点
4076
4072
  timeJumpPoints.forEach(point => {
@@ -4108,6 +4104,7 @@ const FRAME_SIZE = 512;
4108
4104
  this.robotStatus = 'leaving';
4109
4105
  this.$refs.audioPlayer.pause();
4110
4106
  this.$refs.audioPlayer.currentTime = 0;
4107
+ this.jumpedTimePoints.clear();
4111
4108
  },
4112
4109
  initWebSocket() {
4113
4110
  try {
@@ -4373,10 +4370,10 @@ const FRAME_SIZE = 512;
4373
4370
  });
4374
4371
  ;// ./components/ChatWindow.vue?vue&type=script&lang=js
4375
4372
  /* harmony default export */ var components_ChatWindowvue_type_script_lang_js = (ChatWindowvue_type_script_lang_js);
4376
- ;// ./node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/@vue/vue-loader-v15/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./components/ChatWindow.vue?vue&type=style&index=0&id=6cc1c97f&prod&scoped=true&lang=css
4373
+ ;// ./node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/@vue/vue-loader-v15/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./components/ChatWindow.vue?vue&type=style&index=0&id=43521054&prod&scoped=true&lang=css
4377
4374
  // extracted by mini-css-extract-plugin
4378
4375
 
4379
- ;// ./components/ChatWindow.vue?vue&type=style&index=0&id=6cc1c97f&prod&scoped=true&lang=css
4376
+ ;// ./components/ChatWindow.vue?vue&type=style&index=0&id=43521054&prod&scoped=true&lang=css
4380
4377
 
4381
4378
  ;// ./node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js
4382
4379
  /* globals __VUE_SSR_CONTEXT__ */
@@ -4491,7 +4488,7 @@ var component = normalizeComponent(
4491
4488
  staticRenderFns,
4492
4489
  false,
4493
4490
  null,
4494
- "6cc1c97f",
4491
+ "43521054",
4495
4492
  null
4496
4493
 
4497
4494
  )