byt-lingxiao-ai 0.3.23 → 0.3.25

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,107 +1,93 @@
1
1
  <template>
2
2
  <div class="chat-window-message-ai">
3
3
  <div class="ai-render">
4
- <div class="ai-loading" v-if="isLoading">
4
+ <div v-if="message.loading" class="ai-loading">
5
5
  <div class="dot"></div>
6
6
  <div class="dot"></div>
7
7
  <div class="dot"></div>
8
8
  </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>
9
+ <div
10
+ v-for="item in message.timeline"
11
+ :key="item.id"
12
+ class="ai-message-item"
13
+ >
14
+ <div
15
+ v-if="item.type === 'thinking'"
16
+ class="ai-thinking"
17
+ @click="$emit('thinking-toggle')"
18
+ >
19
+ <div class="ai-thinking-time">
20
+ {{
21
+ item.duration
22
+ ? `思考用时 ${item.duration} 秒`
23
+ : '思考中...'
24
+ }}
25
+ </div>
26
+ <div
27
+ class="ai-thinking-content"
28
+ v-if="thinkingExpanded"
29
+ >
30
+ {{ item.content }}
31
+ </div>
32
+ </div>
33
+ <div
34
+ v-else-if="item.type === 'tool_call'"
35
+ class="ai-tool-call"
36
+ >
37
+ <div class="tool-title">🔧 {{ item.content }}{{ item.name }}</div>
38
+ </div>
39
+ <div
40
+ v-else-if="item.type === 'tool_result'"
41
+ class="ai-tool-result"
42
+ >
43
+ <div class="tool-title">📦 {{ item.content }}</div>
44
+ </div>
45
+ <div
46
+ v-else-if="item.type === 'content'"
47
+ class="ai-content markdown-body"
48
+ v-html="renderMarkdown(item.content)"
49
+ ></div>
12
50
  </div>
13
- <div class="ai-content markdown-body" @click="handleLinkClick" v-html="renderedContent"></div>
14
51
  </div>
15
52
  </div>
16
53
  </template>
17
54
 
18
55
  <script>
19
- import { marked } from 'marked';
20
- import hljs from 'highlight.js';
21
- import 'highlight.js/styles/github.css';
22
-
23
- // 创建自定义渲染器
24
- const renderer = new marked.Renderer()
25
-
26
- // 自定义链接渲染
27
- renderer.link = function ({ href, text }) {
28
- let dataPath = ''
29
- const url = new URL(href, window.location.origin)
30
- const pathname = url.pathname
31
-
32
- if (pathname.startsWith('/portal/')) {
33
- dataPath = pathname.substring('/portal'.length)
34
- return `<a href="javascript:void(0)" data-path="${dataPath}" data-text="${text}">${text}</a>`
35
- } else {
36
- dataPath = href
37
- return `<a href="${dataPath}" target="_blank">${text}</a>`
38
- }
39
- }
56
+ import { marked } from 'marked'
57
+ import hljs from 'highlight.js'
58
+ import 'highlight.js/styles/github.css'
59
+
40
60
  marked.setOptions({
41
- renderer: renderer,
42
- highlight: function(code, lang) {
61
+ highlight(code, lang) {
43
62
  if (lang && hljs.getLanguage(lang)) {
44
- return hljs.highlight(code, { language: lang }).value;
63
+ return hljs.highlight(code, { language: lang }).value
45
64
  }
46
- return hljs.highlightAuto(code).value;
65
+ return hljs.highlightAuto(code).value
47
66
  },
48
67
  breaks: true,
49
68
  gfm: true
50
- });
69
+ })
51
70
 
52
71
  export default {
53
- name: 'AiMessage',
54
72
  props: {
55
- message: {
56
- type: Object,
57
- required: true
58
- }
73
+ message: Object,
74
+ thinkStatus: Boolean
59
75
  },
60
76
  computed: {
61
77
  thinkingExpanded() {
62
- return this.message.thinkingExpanded !== false;
63
- },
64
- renderedContent() {
65
- return marked.parse(this.message.content || '');
66
- },
67
- isLoading() {
68
- return this.message.loading === true;
69
- }
70
- },
71
- watch: {
72
- thinkStatus(newVal, oldVal) {
73
- console.log('thinkStatus 变化:', newVal, oldVal);
78
+ return this.message.thinkingExpanded !== false
74
79
  }
75
80
  },
76
81
  methods: {
77
- handleLinkClick(event) {
78
- const link = event.target.closest('a')
79
- if (!link) return
80
-
81
- const routePath = link.getAttribute('data-path')
82
- const linkText = link.getAttribute('data-text')
83
-
84
- if (routePath && linkText) {
85
- event.preventDefault()
86
-
87
- if (this.$appOptions?.store?.dispatch) {
88
- this.$appOptions.store.dispatch('tags/addTagview', {
89
- path: routePath,
90
- fullPath: routePath,
91
- label: linkText,
92
- name: linkText,
93
- meta: { title: linkText }
94
- })
95
- this.$appOptions.router.push({ path: routePath })
96
- } else {
97
- this.$router.push(routePath).catch(() => {})
98
- }
99
- }
82
+ renderMarkdown(text) {
83
+ return marked.parse(text || '')
100
84
  }
101
85
  }
102
86
  }
103
87
  </script>
104
88
 
89
+
90
+
105
91
  <style scoped>
106
92
  /* Loading 容器 */
107
93
  .ai-loading {
@@ -6,11 +6,11 @@
6
6
  v-for="message in messages"
7
7
  :key="message.id"
8
8
  >
9
- <UserMessage
9
+ <UserMessage
10
10
  v-if="message.type === 'user'"
11
11
  :content="message.content"
12
12
  />
13
- <AiMessage
13
+ <AiMessage
14
14
  v-else
15
15
  :message="message"
16
16
  :think-status="thinkStatus"
@@ -25,53 +25,34 @@ import UserMessage from './UserMessage.vue'
25
25
  import AiMessage from './AiMessage.vue'
26
26
 
27
27
  export default {
28
- name: 'ChatMessageList',
29
- components: {
30
- UserMessage,
31
- AiMessage
32
- },
28
+ components: { UserMessage, AiMessage },
33
29
  props: {
34
- messages: {
35
- type: Array,
36
- required: true
37
- },
38
- thinkStatus: {
39
- type: Boolean,
40
- default: true
41
- }
30
+ messages: Array,
31
+ thinkStatus: Boolean
42
32
  },
43
33
  computed: {
44
34
  lastMessageObject() {
45
- const len = this.messages.length;
46
- if (len === 0) {
47
- return null;
48
- }
49
- return this.messages[len - 1];
35
+ return this.messages[this.messages.length - 1] || null
50
36
  }
51
37
  },
52
38
  methods: {
53
39
  handleThinkingToggle(message) {
54
- console.log('handleThinkingToggle', message)
55
- this.$set(message, 'thinkingExpanded', !message.thinkingExpanded);
40
+ this.$set(message, 'thinkingExpanded', !message.thinkingExpanded)
56
41
  },
57
42
  scrollToBottom() {
58
43
  this.$nextTick(() => {
59
- const chatArea = this.$refs.chatArea
60
- if (chatArea) {
61
- chatArea.scrollTop = chatArea.scrollHeight
62
- }
44
+ const el = this.$refs.chatArea
45
+ if (el) el.scrollTop = el.scrollHeight
63
46
  })
64
47
  }
65
48
  },
66
49
  watch: {
67
50
  lastMessageObject: {
68
- handler(newMsg) {
69
- if (newMsg) {
70
- this.scrollToBottom();
71
- }
51
+ handler() {
52
+ this.scrollToBottom()
72
53
  },
73
54
  deep: true,
74
- immediate: true
55
+ immediate: true
75
56
  }
76
57
  }
77
58
  }
@@ -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'
@@ -7,7 +7,14 @@ import { getCookie } from '../utils/Cookie.js'
7
7
  export default {
8
8
  data() {
9
9
  return {
10
- streamParser: null
10
+ streamParser: null,
11
+ aiMessage: {
12
+ id: Date.now(),
13
+ type: 'ai',
14
+ timeline: [],
15
+ loading: true,
16
+ thinkingExpanded: true
17
+ }
11
18
  }
12
19
  },
13
20
 
@@ -21,20 +28,9 @@ export default {
21
28
 
22
29
  methods: {
23
30
  createAiMessage() {
24
- const message = {
25
- id: this.messages.length + 1,
26
- type: 'ai',
27
- sender: 'AI',
28
- time: '',
29
- thinking: '',
30
- charts: [],
31
- content: '',
32
- loading: true,
33
- thinkingExpanded: true
34
- };
35
- this.messages.push(message);
36
- this.currentMessage = message;
37
- return message;
31
+ this.messages.push(this.aiMessage);
32
+ this.currentMessage = this.aiMessage;
33
+ return this.aiMessage;
38
34
  },
39
35
 
40
36
  createUserMessage(content) {
@@ -86,10 +82,8 @@ export default {
86
82
  await this.consumeStream(response.body);
87
83
 
88
84
  // 完成解析
89
- const self = this;
90
- this.streamParser.finish(function(result) {
91
- self.handleStreamUpdate(result);
92
- });
85
+ this.streamParser.finish(() => {});
86
+ this.aiMessage.loading = false;
93
87
 
94
88
  // 记录耗时
95
89
  const duration = Date.now() - startTime;
@@ -120,7 +114,6 @@ export default {
120
114
  async consumeStream(readableStream) {
121
115
  const reader = readableStream.getReader();
122
116
  const decoder = new TextDecoder('utf-8');
123
- const self = this;
124
117
 
125
118
  try {
126
119
  // eslint-disable-next-line no-constant-condition
@@ -131,9 +124,11 @@ export default {
131
124
  const chunk = decoder.decode(value, { stream: true });
132
125
  console.log('收到数据块:', chunk);
133
126
  // 使用解析器处理数据块,确保this指向正确
134
- this.streamParser.processChunk(chunk, function(result) {
135
- console.log('处理数据块:', result);
136
- self.handleStreamUpdate(result);
127
+ this.streamParser.processChunk(chunk, (e) => {
128
+ console.log('处理数据块:', e);
129
+ if (e.type === 'create') {
130
+ this.aiMessage.timeline.push(e.event)
131
+ }
137
132
  });
138
133
  }
139
134
  } finally {
@@ -174,11 +169,5 @@ export default {
174
169
  this.$forceUpdate();
175
170
  }
176
171
  },
177
-
178
- beforeDestroy() {
179
- // 清理解析器
180
- if (this.streamParser) {
181
- this.streamParser.destroy();
182
- }
183
- }
172
+ beforeDestroy() {}
184
173
  }
@@ -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 () => {