byt-lingxiao-ai 0.3.3 → 0.3.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.
@@ -58,16 +58,16 @@ function parseMarkdown(text) {
58
58
 
59
59
  html = html.replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>');
60
60
  // 解析Markdown链接
61
- html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
61
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" aria-current="page">$1</a>');
62
62
 
63
63
  html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />');
64
64
 
65
65
  html = html.replace(/^-{3,}$/gm, '<hr>');
66
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
- });
67
+ // const simpleUrlRegex = /(https?:\/\/[^\s<>"']+)/g;
68
+ // html = html.replace(simpleUrlRegex, (match) => {
69
+ // return `<a href="${match}" aria-current="page">${match}</a>`;
70
+ // });
71
71
 
72
72
  // html = html.replace(/\n/g, '<br>');
73
73
 
@@ -33,6 +33,7 @@
33
33
  :messages="messages"
34
34
  :input-message="inputMessage"
35
35
  :think-status="thinkStatus"
36
+ :chat-id="chatId"
36
37
  @update:inputMessage="inputMessage = $event"
37
38
  @send="handleSend"
38
39
  @thinking-click="handleThinkingClick"
@@ -49,9 +50,11 @@ import audioMixin from './mixins/audioMixin'
49
50
  import webSocketMixin from './mixins/webSocketMixin'
50
51
  import messageMixin from './mixins/messageMixin'
51
52
  import { AUDIO_URL, TIME_JUMP_POINTS_URL } from './config/index.js'
53
+ import generateUuid from './utils/Uuid.js'
52
54
 
53
55
  const SAMPLE_RATE = 16000;
54
56
  const FRAME_SIZE = 512;
57
+ const startTime = null
55
58
 
56
59
  export default {
57
60
  name: 'ChatWindow',
@@ -69,6 +72,7 @@ export default {
69
72
  },
70
73
  data() {
71
74
  return {
75
+ chatId: generateUuid(),
72
76
  audioSrc: AUDIO_URL,
73
77
  inputMessage: '',
74
78
  visible: false,
@@ -80,6 +84,7 @@ export default {
80
84
  jumpedTimePoints: new Set(),
81
85
  SAMPLE_RATE,
82
86
  FRAME_SIZE,
87
+ startTime, // 检查性能使用
83
88
  dragThreshold: 5, // 拖拽阈值
84
89
  isDragging: false,
85
90
  dragStartX: 0,
@@ -2,7 +2,7 @@
2
2
  <div class="chat-overlay" v-show="value" @click="$emit('overlay-click')">
3
3
  <div class="chat-window" @click.stop>
4
4
  <!-- 头部 -->
5
- <ChatWindowHeader @close="$emit('input', false)" />
5
+ <ChatWindowHeader :chat-id="chatId" @close="$emit('input', false)" />
6
6
 
7
7
  <!-- 消息列表 -->
8
8
  <ChatMessageList
@@ -55,6 +55,10 @@ export default {
55
55
  loading: {
56
56
  type: Boolean,
57
57
  default: false
58
+ },
59
+ chatId: {
60
+ type: String,
61
+ default: ''
58
62
  }
59
63
  }
60
64
  }
@@ -39,11 +39,15 @@
39
39
  <script>
40
40
  export default {
41
41
  name: 'ChatWindowHeader',
42
+ props: {
43
+ chatId: {
44
+ type: String,
45
+ default: ''
46
+ }
47
+ },
42
48
  methods: {
43
49
  handleOpen() {
44
- // const chatId = ''
45
- // const baseUrl = window.location.protocol + '//' + window.location.hostname + ':3100/c/' + chatId;
46
- const baseUrl = window.location.protocol + '//' + window.location.hostname + ':3100/';
50
+ const baseUrl = window.location.protocol + '//' + window.location.hostname + ':3100/c/' + this.chatId;
47
51
  window.open(baseUrl, '_blank')
48
52
  }
49
53
  }
@@ -2,7 +2,7 @@ const baseUrl = window.location.protocol + '//' + window.location.hostname;
2
2
  const chatPort = '3100';
3
3
 
4
4
  console.log(baseUrl, chatPort);
5
- export const API_URL = 'http://192.168.8.87:3100/lingxiao-byt/api/v1/mcp/ask';
5
+ export const API_URL = `${baseUrl}:${chatPort}/lingxiao-byt/api/v1/mcp/ask`;
6
6
  export const WS_URL = 'ws://192.168.8.9:9999/ai_model/ws/voice-stream';
7
7
  export const AUDIO_URL = '/minio/lingxiaoai/byt.mp3';
8
8
  export const TIME_JUMP_POINTS_URL = '/minio/lingxiaoai/timeJumpPoints.json';
@@ -6,7 +6,9 @@ export default {
6
6
  audioContext: null,
7
7
  microphone: null,
8
8
  processor: null,
9
- audioBuffer: new Float32Array(0)
9
+ audioBuffer: new Float32Array(0),
10
+ // 优化
11
+ int16Buffer: null
10
12
  }
11
13
  },
12
14
  methods: {
@@ -24,8 +26,15 @@ export default {
24
26
  });
25
27
 
26
28
  this.audioContext = new AudioContext({ sampleRate: this.SAMPLE_RATE });
29
+
30
+ // 优化 处理 AudioContext 挂起
31
+ if (this.audioContext.state === 'suspended') {
32
+ await this.audioContext.resume();
33
+ }
34
+
27
35
  this.microphone = this.audioContext.createMediaStreamSource(stream);
28
36
  this.processor = this.audioContext.createScriptProcessor(this.FRAME_SIZE, 1, 1);
37
+ this.int16Buffer = new Int16Array(this.FRAME_SIZE);
29
38
  this.processor.onaudioprocess = this.processAudio;
30
39
 
31
40
  this.microphone.connect(this.processor);
@@ -44,19 +53,20 @@ export default {
44
53
  if (!this.isRecording) return;
45
54
  const inputData = event.inputBuffer.getChannelData(0);
46
55
 
47
- const tempBuffer = new Float32Array(this.audioBuffer.length + inputData.length);
48
- tempBuffer.set(this.audioBuffer, 0);
49
- tempBuffer.set(inputData, this.audioBuffer.length);
50
- this.audioBuffer = tempBuffer;
51
-
52
- while (this.audioBuffer.length >= this.FRAME_SIZE) {
53
- const frame = this.audioBuffer.slice(0, this.FRAME_SIZE);
54
- this.audioBuffer = this.audioBuffer.slice(this.FRAME_SIZE);
55
- const pcmData = this.floatTo16BitPCM(frame);
56
+ if (this.ws && this.ws.readyState === this.ws.OPEN) {
57
+ if (inputData.length !== this.int16Buffer.length) {
58
+ this.int16Buffer = new Int16Array(inputData.length);
59
+ }
56
60
 
57
- if (this.ws && this.ws.readyState === this.ws.OPEN) {
58
- this.ws.send(pcmData);
61
+ for (let i = 0; i < inputData.length; i++) {
62
+ let s = inputData[i];
63
+ // 简单的 clamp
64
+ s = s < -1 ? -1 : s > 1 ? 1 : s;
65
+ this.int16Buffer[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
59
66
  }
67
+
68
+ // 4. 发送数据
69
+ this.ws.send(this.int16Buffer.buffer);
60
70
  }
61
71
  },
62
72
 
@@ -1,5 +1,8 @@
1
1
  import { StreamParser } from '../utils/StreamParser'
2
2
  import { API_URL } from '../config/index.js'
3
+ import { getCookie } from '../utils/Cookie.js'
4
+
5
+
3
6
 
4
7
  export default {
5
8
  data() {
@@ -61,17 +64,15 @@ export default {
61
64
 
62
65
  try {
63
66
  const startTime = Date.now();
64
- const controller = new AbortController();
65
- const token = `Bearer e298f087-85bc-48c2-afb9-7c69ffc911aa`;
67
+ const token = getCookie('bonyear-access_token') || `e298f087-85bc-48c2-afb9-7c69ffc911aa`;
66
68
 
67
69
  const response = await fetch(API_URL, {
68
70
  method: 'POST',
69
- signal: controller.signal,
70
71
  headers: {
71
72
  'Content-Type': 'application/json',
72
- 'Authorization': token,
73
+ 'Authorization': `Bearer ${token}`,
73
74
  },
74
- body: JSON.stringify({ content: message })
75
+ body: JSON.stringify({ content: message, chatId: this.chatId })
75
76
  });
76
77
 
77
78
  if (!response.ok) {
@@ -76,11 +76,24 @@ export default {
76
76
  if (data.type === 'detection') {
77
77
  console.log('检测到唤醒词');
78
78
  this.avaterStatus = 'normal';
79
+
80
+ // 性能检测起点
81
+ this.startTime = Date.now(); // <-- 新增计时器
82
+ console.log(`[Timer] 指令发送开始计时: ${this.startTime}ms`);
83
+
79
84
  } else if (data.type === 'Collecting') {
80
85
  console.log('状态: 采集中');
81
86
  this.avaterStatus = 'thinking';
82
87
  } else if (data.type === 'command') {
83
88
  console.log('状态: 处理中');
89
+
90
+ // 性能检测终点
91
+ if (this.startTime) {
92
+ const latency = Date.now() - this.startTime;
93
+ console.log(`[Latency] 完整命令处理耗时: ${latency}ms`); // 记录端到端延迟
94
+ this.startTime = null;
95
+ }
96
+
84
97
  this.analyzeAudioCommand(data.category);
85
98
  } else {
86
99
  console.log('状态: 其他');
@@ -0,0 +1,19 @@
1
+ const setCookie = (name, value, days) => {
2
+ const d = new Date();
3
+ d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
4
+ const expires = `expires=${d.toUTCString()}`;
5
+ document.cookie = `${name}=${value}; ${expires}`;
6
+ };
7
+
8
+ const getCookie = cname => {
9
+ const name = `${cname}=`;
10
+ const ca = document.cookie.split(';');
11
+ for (let i = 0; i < ca.length; i++) {
12
+ let c = ca[i];
13
+ while (c.charAt(0) === ' ') c = c.substring(1);
14
+ if (c.indexOf(name) !== -1) return c.substring(name.length, c.length);
15
+ }
16
+ return '';
17
+ };
18
+
19
+ export { setCookie, getCookie };
@@ -41,6 +41,15 @@ export class StreamParser {
41
41
  if (this.options.debug) {
42
42
  console.log('[StreamParser] 收到chunk:', chunk.substring(0, 100));
43
43
  }
44
+
45
+ if (!this.buffer.includes('data:') && !this.buffer.includes('\n\n')) {
46
+ // 纯文本流,直接处理
47
+ if (this.options.debug) {
48
+ console.log('[StreamParser] 检测到纯文本流');
49
+ }
50
+ this.processPlainText(callback);
51
+ return;
52
+ }
44
53
 
45
54
  // 尝试解析为 SSE 格式
46
55
  if (this.buffer.includes('data:')) {
@@ -0,0 +1,6 @@
1
+ export default function generateUuid() {
2
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
3
+ var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
4
+ return v.toString(16);
5
+ });
6
+ }
@@ -0,0 +1,27 @@
1
+ // 在您的组件库的某个 util 文件中定义 (例如: utils/workletSource.js)
2
+
3
+ export const RECORDER_PROCESSOR_CODE = `
4
+ class RecorderProcessor extends AudioWorkletProcessor {
5
+ constructor() {
6
+ super();
7
+ console.log('RecorderProcessor created in Worklet Thread');
8
+ }
9
+
10
+ process(inputs, outputs, parameters) {
11
+ const input = inputs[0];
12
+ const inputChannel = input[0];
13
+
14
+ if (inputChannel && inputChannel.length > 0) {
15
+ // 通过 postMessage 将 Float32Array 缓冲区发送回主线程。
16
+ this.port.postMessage(inputChannel.slice());
17
+ }
18
+
19
+ return true;
20
+ }
21
+ }
22
+
23
+ registerProcessor('recorder-processor', RecorderProcessor);
24
+ `;
25
+
26
+ // Worklet 节点在主线程中注册时使用的名称
27
+ export const PROCESSOR_NAME = 'recorder-processor';