byt-lingxiao-ai 0.3.22 → 0.3.24

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.
@@ -1,74 +1,26 @@
1
1
  <template>
2
2
  <div class="chat-window-message-ai">
3
3
  <div class="ai-render">
4
-
5
- <!-- loading -->
6
4
  <div class="ai-loading" v-if="isLoading">
7
5
  <div class="dot"></div>
8
6
  <div class="dot"></div>
9
7
  <div class="dot"></div>
10
8
  </div>
11
- <!-- 时间线渲染 -->
12
- <div
13
- v-for="item in mergedTimeline"
14
- :key="item.id"
15
- class="ai-timeline-item"
16
- >
17
- <!-- 思考 -->
18
- <div
19
- v-if="item.type === 'thinking'"
20
- class="ai-thinking"
21
- @click="$emit('thinking-toggle')"
22
- >
23
- <div class="ai-thinking-time">
24
- {{ message.time ? `思考用时 ${message.time} 秒` : '思考中...' }}
25
- </div>
26
- <div
27
- class="ai-thinking-content"
28
- v-if="thinkingExpanded"
29
- >
30
- {{ item.text }}
31
- </div>
32
- </div>
33
-
34
- <!-- 工具调用 -->
35
- <div
36
- v-else-if="item.type === 'tool'"
37
- class="ai-tool"
38
- >
39
- <div class="ai-tool-header">
40
- 调用工具:{{ item.tool.name }}
41
- </div>
42
- <details>
43
- <summary>参数</summary>
44
- <pre>{{ item.tool.args }}</pre>
45
- </details>
46
- <details v-if="item.tool.result">
47
- <summary>结果</summary>
48
- <pre>{{ item.tool.result }}</pre>
49
- </details>
50
- </div>
51
- <div
52
- v-else-if="item.type === 'content'"
53
- class="ai-content markdown-body"
54
- v-html="renderMarkdown(item.text)"
55
- @click="handleLinkClick"
56
- />
57
- <div
58
- v-if="item.type === 'protocol_error'"
59
- class="ai-protocol-error"
60
- >
61
- {{ item.raw }}
62
- </div>
9
+ <div class="ai-thinking" @click="$emit('thinking-toggle')" v-if="message.thinking">
10
+ <div class="ai-thinking-time">{{ message.time ? `思考用时${message.time}秒` : '思考中...' }}</div>
11
+ <div class="ai-thinking-content" v-if="thinkingExpanded">{{ message.thinking }}</div>
63
12
  </div>
13
+ <div class="ai-content markdown-body" @click="handleLinkClick" v-html="renderedContent"></div>
64
14
  </div>
65
15
  </div>
66
16
  </template>
17
+
67
18
  <script>
68
- import { marked } from 'marked'
69
- import hljs from 'highlight.js'
70
- import 'highlight.js/styles/github.css'
19
+ import { marked } from 'marked';
20
+ import hljs from 'highlight.js';
21
+ import 'highlight.js/styles/github.css';
71
22
 
23
+ // 创建自定义渲染器
72
24
  const renderer = new marked.Renderer()
73
25
 
74
26
  // 自定义链接渲染
@@ -85,18 +37,17 @@ renderer.link = function ({ href, text }) {
85
37
  return `<a href="${dataPath}" target="_blank">${text}</a>`
86
38
  }
87
39
  }
88
-
89
40
  marked.setOptions({
90
- renderer,
91
- highlight(code, lang) {
41
+ renderer: renderer,
42
+ highlight: function(code, lang) {
92
43
  if (lang && hljs.getLanguage(lang)) {
93
- return hljs.highlight(code, { language: lang }).value
44
+ return hljs.highlight(code, { language: lang }).value;
94
45
  }
95
- return hljs.highlightAuto(code).value
46
+ return hljs.highlightAuto(code).value;
96
47
  },
97
48
  breaks: true,
98
49
  gfm: true
99
- })
50
+ });
100
51
 
101
52
  export default {
102
53
  name: 'AiMessage',
@@ -108,36 +59,21 @@ export default {
108
59
  },
109
60
  computed: {
110
61
  thinkingExpanded() {
111
- return this.message.thinkingExpanded !== false
62
+ return this.message.thinkingExpanded !== false;
112
63
  },
113
- isLoading() {
114
- return this.message.loading === true
64
+ renderedContent() {
65
+ return marked.parse(this.message.content || '');
115
66
  },
116
- mergedTimeline() {
117
- const result = []
118
- let last = null
119
-
120
- for (const item of this.message.timeline || []) {
121
- if (item.type === 'content') {
122
- if (last && last.type === 'content') {
123
- last.text += item.text
124
- } else {
125
- last = { ...item }
126
- result.push(last)
127
- }
128
- } else {
129
- last = null
130
- result.push(item)
131
- }
132
- }
133
- return result
67
+ isLoading() {
68
+ return this.message.loading === true;
69
+ }
70
+ },
71
+ watch: {
72
+ thinkStatus(newVal, oldVal) {
73
+ console.log('thinkStatus 变化:', newVal, oldVal);
134
74
  }
135
75
  },
136
-
137
76
  methods: {
138
- renderMarkdown(text) {
139
- return marked.parse(text || '')
140
- },
141
77
  handleLinkClick(event) {
142
78
  const link = event.target.closest('a')
143
79
  if (!link) return
@@ -165,6 +101,7 @@ export default {
165
101
  }
166
102
  }
167
103
  </script>
104
+
168
105
  <style scoped>
169
106
  /* Loading 容器 */
170
107
  .ai-loading {
@@ -94,6 +94,7 @@ export default {
94
94
  initialX: 10,
95
95
  initialY: 20,
96
96
  hasMoved: false,
97
+ timeJumpPoints: []
97
98
  }
98
99
  },
99
100
  computed: {
@@ -117,6 +118,8 @@ export default {
117
118
  this.appendToBodyHandler()
118
119
  }
119
120
 
121
+ this.fetchTimeJumpPoints()
122
+
120
123
  this.$nextTick(() => {
121
124
  const chatEl = this.$el;
122
125
  const style = window.getComputedStyle(chatEl);
@@ -219,42 +222,47 @@ export default {
219
222
  }
220
223
  })
221
224
  },
225
+ async fetchTimeJumpPoints() {
226
+ try {
227
+ const res = await fetch(TIME_JUMP_POINTS_URL)
228
+ const data = await res.json()
229
+ this.timeJumpPoints = Array.isArray(data) ? data : []
230
+ console.log('时间跳转点加载完成:', this.timeJumpPoints)
231
+ } catch (err) {
232
+ console.error('获取时间跳转点失败:', err)
233
+ this.timeJumpPoints = []
234
+ }
235
+ },
222
236
  // 音频时间更新处理
223
237
  onTimeUpdate() {
224
238
  const audio = this.$refs.audioPlayer
225
239
  const currentTime = audio.currentTime
226
240
 
227
- if (!this.jumpedTimePoints) {
228
- this.jumpedTimePoints = new Set()
229
- }
241
+ if (!this.timeJumpPoints.length) return
242
+
243
+ this.timeJumpPoints.forEach(point => {
244
+ if (
245
+ currentTime >= point.time &&
246
+ currentTime < point.time + 1 &&
247
+ !this.jumpedTimePoints.has(point.time)
248
+ ) {
249
+ console.log('触发跳转:', point.url)
230
250
 
231
- fetch(TIME_JUMP_POINTS_URL)
232
- .then(response => response.json())
233
- .then(data => {
234
- console.log('时间跳转点:', data)
235
- console.log('当前时间:', currentTime)
236
- data.forEach(point => {
237
- console.log('跳转点:', point)
238
- console.log('跳转时间:', point.time)
239
- console.log('当前时间:', currentTime)
240
- console.log('是否已跳转:', this.jumpedTimePoints.has(point.time))
241
- console.log('跳转时间范围:', currentTime >= point.time && currentTime < point.time + 1)
242
- if (currentTime >= point.time && currentTime < point.time + 1 && !this.jumpedTimePoints.has(point.time)) {
243
- this.jumpedTimePoints.add(point.time)
244
- this.$appOptions.store.dispatch('tags/addTagview', {
245
- path: point.url,
246
- fullPath: point.url,
247
- label: point.title,
248
- name: point.title,
249
- meta: { title: point.title },
250
- query: {},
251
- params: {}
252
- })
253
- this.$appOptions.router.push({ path: point.url })
254
- }
251
+ this.jumpedTimePoints.add(point.time)
252
+
253
+ this.$appOptions.store.dispatch('tags/addTagview', {
254
+ path: point.url,
255
+ fullPath: point.url,
256
+ label: point.title,
257
+ name: point.title,
258
+ meta: { title: point.title },
259
+ query: {},
260
+ params: {}
255
261
  })
256
- })
257
- .catch(error => console.error('获取时间跳转点失败:', error))
262
+
263
+ this.$appOptions.router.push({ path: point.url })
264
+ }
265
+ })
258
266
  },
259
267
  onAudioEnded() {
260
268
  this.robotStatus = 'leaving'
@@ -26,7 +26,9 @@ export default {
26
26
  type: 'ai',
27
27
  sender: 'AI',
28
28
  time: '',
29
- timeline: [],
29
+ thinking: '',
30
+ charts: [],
31
+ content: '',
30
32
  loading: true,
31
33
  thinkingExpanded: true
32
34
  };
@@ -64,7 +66,7 @@ export default {
64
66
 
65
67
  try {
66
68
  const startTime = Date.now();
67
- const token = getCookie('bonyear-access_token') || `b6dd936e-4b8b-4e10-ba70-573eb35673f1`;
69
+ const token = getCookie('bonyear-access_token') || `44e7f112-63f3-429d-908d-2c97ec380de2`;
68
70
 
69
71
  const response = await fetch(API_URL, {
70
72
  method: 'POST',
@@ -145,39 +147,28 @@ export default {
145
147
  handleStreamUpdate(result) {
146
148
  if (!this.currentMessage) return;
147
149
 
148
- this.currentMessage.loading = false
150
+ console.log('收到更新:', result);
149
151
 
150
- if (result.protocolError) {
151
- this.currentMessage.timeline.push({
152
- type: 'protocol_error',
153
- id: Date.now() + Math.random(),
154
- raw: result.protocolError
155
- })
152
+ if (this.currentMessage.loading) {
153
+ this.currentMessage.loading = false;
156
154
  }
157
- if (result.thinkingSegment) {
158
- this.currentMessage.timeline.push({
159
- type: 'thinking',
160
- id: Date.now() + Math.random(),
161
- text: result.thinkingSegment
162
- })
163
- }
164
- if (result.toolCall) {
165
- this.currentMessage.timeline.push({
166
- type: 'tool',
167
- id: result.toolCall.id,
168
- tool: result.toolCall
169
- })
155
+ // 更新思考内容
156
+ if (result.thinking) {
157
+ this.currentMessage.thinking += result.thinking;
170
158
  }
159
+
160
+ // 更新回复内容
161
+ console.log('更新回复内容:', result.content);
171
162
  if (result.content) {
172
- this.currentMessage.timeline.push({
173
- type: 'content',
174
- id: Date.now() + Math.random(),
175
- text: result.content
176
- })
163
+ this.currentMessage.content += result.content;
177
164
  }
178
165
 
179
166
  // 更新状态
180
- this.avaterStatus = result.status === 'thinking' ? 'thinking' : 'output'
167
+ if (result.status === 'thinking') {
168
+ this.avaterStatus = 'thinking';
169
+ } else {
170
+ this.avaterStatus = 'output';
171
+ }
181
172
 
182
173
  // 触发视图更新
183
174
  this.$forceUpdate();
@@ -14,7 +14,9 @@ export default {
14
14
  try {
15
15
  // this.ws = new WebSocket('ws://10.2.233.41:9999');
16
16
  // 测试
17
+ // console.log('WS_URL:', WS_URL)
17
18
  this.ws = new WebSocket(WS_URL);
19
+ // this.ws = new WebSocket('ws://192.168.8.87/audio/ws/');
18
20
  this.ws.binaryType = 'arraybuffer';
19
21
 
20
22
  this.ws.onopen = async () => {
@@ -1,213 +1,135 @@
1
- // utils/StreamParser.js
2
1
  export class StreamParser {
3
2
  constructor(options = {}) {
4
3
  this.options = {
5
- updateInterval: 16,
4
+ updateInterval: 16, // 每16ms刷新一次(约60fps)
6
5
  debug: false,
7
6
  ...options
8
- }
9
- this.reset()
7
+ };
8
+ this.reset();
10
9
  }
11
10
 
11
+ // 重置解析器状态
12
12
  reset() {
13
- this.contentBuffer = []
14
- this.thinkingBuffer = []
15
-
16
- this.completedThinkingSegments = []
17
- this.completedToolCalls = []
18
-
19
- this.currentToolCall = null
20
-
21
- this.protocolErrorBuffer = [] // ✅ 新增
22
-
23
- this.inTag = false
24
- this.tagBuffer = ''
25
- this.updateTimer = null
26
-
27
- this.status = 'output' // thinking | output | tool_call | tool_result
13
+ this.buffer = '';
14
+ this.contentBuffer = [];
15
+ this.thinkingBuffer = [];
16
+ this.inTag = false;
17
+ this.tagBuffer = '';
18
+ this.updateTimer = null;
19
+ this.status = 'output'; // thinking | output
28
20
  }
29
21
 
22
+ // 处理接收到的流数据块
30
23
  processChunk(chunk, callback) {
31
- if (!chunk) return
32
- this.parseContent(chunk, callback)
24
+ if (!chunk) return;
25
+ this.parseContent(chunk, callback);
33
26
  }
34
27
 
28
+ // 核心内容解析,支持 <think> 标签
35
29
  parseContent(content, callback) {
36
- let i = 0
30
+ let i = 0;
37
31
 
38
32
  while (i < content.length) {
39
33
  if (this.inTag) {
40
- const end = content.indexOf('>', i)
41
- if (end !== -1) {
42
- this.tagBuffer += content.slice(i, end + 1)
43
- this.handleTag(this.tagBuffer)
44
- this.tagBuffer = ''
45
- this.inTag = false
46
- i = end + 1
34
+ const endIndex = content.indexOf('>', i);
35
+ if (endIndex !== -1) {
36
+ this.tagBuffer += content.substring(i, endIndex + 1);
37
+ this.handleTag(this.tagBuffer);
38
+ this.tagBuffer = '';
39
+ this.inTag = false;
40
+ i = endIndex + 1;
47
41
  } else {
48
- this.tagBuffer += content.slice(i)
49
- break
42
+ // 标签未闭合,超过50字符强制输出,防止阻塞
43
+ this.tagBuffer += content.substring(i);
44
+ if (this.tagBuffer.length > 50) {
45
+ this.appendText(this.tagBuffer);
46
+ this.tagBuffer = '';
47
+ this.inTag = false;
48
+ }
49
+ break;
50
50
  }
51
51
  } else {
52
- const start = content.indexOf('<', i)
53
- if (start === -1) {
54
- this.appendText(content.slice(i))
55
- break
56
- }
57
-
58
- if (start > i) {
59
- this.appendText(content.slice(i, start))
60
- }
61
-
62
- const end = content.indexOf('>', start)
63
- if (end === -1) {
64
- this.inTag = true
65
- this.tagBuffer = content.slice(start)
66
- break
52
+ const startIndex = content.indexOf('<', i);
53
+ if (startIndex !== -1) {
54
+ if (startIndex > i) this.appendText(content.substring(i, startIndex));
55
+
56
+ const endIndex = content.indexOf('>', startIndex);
57
+ if (endIndex !== -1) {
58
+ const tag = content.substring(startIndex, endIndex + 1);
59
+ this.handleTag(tag);
60
+ i = endIndex + 1;
61
+ } else {
62
+ const nextChar = content[startIndex + 1];
63
+ if (!/[a-zA-Z/]/.test(nextChar)) {
64
+ // 很可能不是标签,直接当文本输出
65
+ this.appendText('<');
66
+ i = startIndex + 1;
67
+ } else {
68
+ this.inTag = true;
69
+ this.tagBuffer = content.substring(startIndex);
70
+ break;
71
+ }
72
+ }
73
+ } else {
74
+ this.appendText(content.substring(i));
75
+ break;
67
76
  }
68
-
69
- const tag = content.slice(start, end + 1)
70
- this.handleTag(tag)
71
- i = end + 1
72
77
  }
73
78
  }
74
79
 
75
- this.scheduleUpdate(callback)
80
+ this.scheduleUpdate(callback);
76
81
  }
77
82
 
83
+ // 处理标签
78
84
  handleTag(tag) {
79
- const t = tag.toLowerCase()
80
-
81
- // ---------- think ----------
82
- if (t === '<think>') {
83
- this.status = 'thinking'
84
- return
85
- }
86
-
87
- if (t === '</think>') {
88
- if (this.thinkingBuffer.length) {
89
- this.completedThinkingSegments.push(
90
- this.thinkingBuffer.join('')
91
- )
92
- this.thinkingBuffer = []
93
- }
94
- this.status = 'output'
95
- return
96
- }
97
-
98
- // ---------- tool_call ----------
99
- if (t.startsWith('<tool_call')) {
100
- const nameMatch = tag.match(/name\s*=\s*["'](.+?)["']/)
101
- this.currentToolCall = {
102
- id: `${Date.now()}-${Math.random()}`,
103
- name: nameMatch ? nameMatch[1] : 'unknown',
104
- args: '',
105
- result: '',
106
- status: 'calling'
107
- }
108
- this.status = 'tool_call'
109
- return
110
- }
111
-
112
- if (t === '</tool_call>') {
113
- this.status = 'output'
114
- return
115
- }
116
-
117
- // ---------- tool_result ----------
118
- if (t === '<tool_result>') {
119
- this.status = 'tool_result'
120
- return
121
- }
122
-
123
- if (t === '</tool_result>') {
124
- if (this.currentToolCall) {
125
- this.currentToolCall.status = 'done'
126
- this.completedToolCalls.push(this.currentToolCall)
127
- this.currentToolCall = null
128
- }
129
- this.status = 'output'
130
- return
131
- }
132
-
133
- // ---------- ❌ 协议错误 ----------
134
- if (t.startsWith('<') && t.endsWith('>')) {
135
- this.protocolErrorBuffer.push(tag)
136
- this.status = 'output'
137
- }
85
+ const t = tag.toLowerCase();
86
+ if (t === '<think>') this.status = 'thinking';
87
+ else if (t === '</think>') this.status = 'output';
138
88
  }
139
89
 
90
+ // 添加文本到缓冲区
140
91
  appendText(text) {
141
- if (!text) return
142
-
143
- if (this.status === 'thinking') {
144
- this.thinkingBuffer.push(text)
145
- }
146
- else if (this.status === 'tool_call') {
147
- this.currentToolCall && (this.currentToolCall.args += text)
148
- }
149
- else if (this.status === 'tool_result') {
150
- this.currentToolCall && (this.currentToolCall.result += text)
151
- }
152
- else {
153
- this.contentBuffer.push(text)
154
- }
92
+ if (!text) return;
93
+ if (this.status === 'thinking') this.thinkingBuffer.push(text);
94
+ else this.contentBuffer.push(text);
155
95
  }
156
96
 
97
+ // 防抖刷新
157
98
  scheduleUpdate(callback) {
158
- if (this.updateTimer) return
99
+ if (this.updateTimer) return;
159
100
  this.updateTimer = setTimeout(() => {
160
- this.flush(callback)
161
- this.updateTimer = null
162
- }, this.options.updateInterval)
101
+ this.flush(callback);
102
+ this.updateTimer = null;
103
+ }, this.options.updateInterval);
163
104
  }
164
105
 
106
+ // 刷新缓冲区
165
107
  flush(callback) {
166
- if (!callback) return
167
-
108
+ if (!callback) return;
168
109
  const result = {
169
- thinkingSegment: null,
170
- toolCall: null,
171
- content: null,
172
- protocolError: null,
110
+ thinking: this.thinkingBuffer.length ? this.thinkingBuffer.join('') : null,
111
+ content: this.contentBuffer.length ? this.contentBuffer.join('') : null,
173
112
  status: this.status
174
- }
175
-
176
- if (this.protocolErrorBuffer.length) {
177
- result.protocolError = this.protocolErrorBuffer.join('\n')
178
- this.protocolErrorBuffer = []
179
- }
180
-
181
- if (this.completedThinkingSegments.length) {
182
- result.thinkingSegment =
183
- this.completedThinkingSegments.shift()
184
- }
185
-
186
- if (this.completedToolCalls.length) {
187
- result.toolCall = this.completedToolCalls.shift()
188
- }
189
-
190
- if (this.contentBuffer.length) {
191
- result.content = this.contentBuffer.join('')
192
- this.contentBuffer = []
193
- }
194
-
195
- callback(result)
113
+ };
114
+ this.thinkingBuffer = [];
115
+ this.contentBuffer = [];
116
+ callback(result);
196
117
  }
197
118
 
119
+ // 完成解析
198
120
  finish(callback) {
199
- if (this.thinkingBuffer.length) {
200
- this.completedThinkingSegments.push(
201
- this.thinkingBuffer.join('')
202
- )
203
- this.thinkingBuffer = []
121
+ if (this.inTag && this.tagBuffer) {
122
+ this.appendText(this.tagBuffer);
123
+ this.tagBuffer = '';
124
+ this.inTag = false;
204
125
  }
205
- this.flush(callback)
206
- if (this.updateTimer) clearTimeout(this.updateTimer)
126
+ this.flush(callback);
127
+ if (this.updateTimer) clearTimeout(this.updateTimer);
207
128
  }
208
129
 
130
+ // 销毁解析器
209
131
  destroy() {
210
- if (this.updateTimer) clearTimeout(this.updateTimer)
211
- this.reset()
132
+ if (this.updateTimer) clearTimeout(this.updateTimer);
133
+ this.reset();
212
134
  }
213
- }
135
+ }