byt-lingxiao-ai 0.3.27 → 0.3.29
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.
- package/components/AudioSubtitle.vue +29 -0
- package/components/ChatWindow.vue +362 -70
- package/components/config/blacklist.js +3 -0
- package/components/config/index.js +12 -3
- package/components/mixins/audioMixin.js +137 -80
- package/components/mixins/messageMixin.js +2 -2
- package/components/mixins/webSocketMixin.js +7 -10
- package/dist/index.common.js +544 -135
- package/dist/index.common.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.umd.js +544 -135
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="subtitle-container">
|
|
3
|
+
<div class="subtitle-text">{{ currentSubtitle }}</div>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
<script>
|
|
7
|
+
export default {
|
|
8
|
+
props: {
|
|
9
|
+
currentSubtitle: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: ''
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
<style scoped>
|
|
17
|
+
.subtitle-container {
|
|
18
|
+
position: fixed;
|
|
19
|
+
left: 0;
|
|
20
|
+
bottom: 10px;
|
|
21
|
+
width: 100%;
|
|
22
|
+
text-align: center;
|
|
23
|
+
font-size: 24px;
|
|
24
|
+
}
|
|
25
|
+
.subtitle-text {
|
|
26
|
+
background-color: rgba(0, 0, 0, 0.4);
|
|
27
|
+
color: #fff;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
<!-- 隐藏的音频播放器 -->
|
|
7
7
|
<audio
|
|
8
8
|
ref="audioPlayer"
|
|
9
|
+
:src="audioSrc"
|
|
9
10
|
class="hidden-audio"
|
|
11
|
+
controls
|
|
12
|
+
preload="auto"
|
|
10
13
|
@timeupdate="onTimeUpdate"
|
|
11
14
|
@ended="onAudioEnded"
|
|
12
|
-
|
|
13
|
-
<source :src="audioSrc" type="audio/mpeg">
|
|
14
|
-
您的浏览器不支持音频元素。
|
|
15
|
-
</audio>
|
|
15
|
+
/>
|
|
16
16
|
|
|
17
17
|
<!-- 机器人动画 -->
|
|
18
18
|
<ChatRobot
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<ChatAvatar
|
|
25
25
|
v-else
|
|
26
26
|
:status="avaterStatus"
|
|
27
|
-
@mousedown="startDrag"
|
|
27
|
+
@mousedown="startDrag($event)"
|
|
28
28
|
/>
|
|
29
29
|
|
|
30
30
|
<!-- 聊天窗口 -->
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
@thinking-click="handleThinkingClick"
|
|
41
41
|
@overlay-click="handleOverlayClick"
|
|
42
42
|
/>
|
|
43
|
+
<!-- 字幕显示区域 -->
|
|
44
|
+
<AudioSubtitle
|
|
45
|
+
ref="audioSubtitle"
|
|
46
|
+
:current-subtitle="currentSubtitle"
|
|
47
|
+
/>
|
|
43
48
|
</div>
|
|
44
49
|
</template>
|
|
45
50
|
|
|
@@ -47,22 +52,35 @@
|
|
|
47
52
|
import ChatRobot from './ChatRobot.vue'
|
|
48
53
|
import ChatAvatar from './ChatAvatar.vue'
|
|
49
54
|
import ChatWindowDialog from './ChatWindowDialog.vue'
|
|
55
|
+
import AudioSubtitle from './AudioSubtitle.vue'
|
|
50
56
|
import audioMixin from './mixins/audioMixin'
|
|
51
57
|
import webSocketMixin from './mixins/webSocketMixin'
|
|
52
58
|
import messageMixin from './mixins/messageMixin'
|
|
53
|
-
import { AUDIO_URL, TIME_JUMP_POINTS_URL } from './config/index.js'
|
|
59
|
+
import { AUDIO_URL, COLLECTION, AREA_BOARD, ENTERPRISE_APPLICATION, REGIONAL_APPLICATION, TIME_JUMP_POINTS_URL, SUBTITLE_POINTS_URL } from './config/index.js'
|
|
54
60
|
import generateUuid from './utils/Uuid.js'
|
|
61
|
+
import { getCookie } from './utils/Cookie'
|
|
62
|
+
import { BLACKLIST } from './config/blacklist.js'
|
|
55
63
|
|
|
56
64
|
const SAMPLE_RATE = 16000;
|
|
57
65
|
const FRAME_SIZE = 512;
|
|
58
|
-
const
|
|
66
|
+
const ROBOT_STATUS = {
|
|
67
|
+
ENTERING: 'entering', // 进入中
|
|
68
|
+
SPEAKING: 'speaking', // 说话中
|
|
69
|
+
WAITING: 'waiting', // 等待中
|
|
70
|
+
LEAVING: 'leaving' // 离开中
|
|
71
|
+
}
|
|
72
|
+
const AVATAR_STATUS = {
|
|
73
|
+
NORMAL: 'normal', // 正常
|
|
74
|
+
THINKING: 'thinking' // 思考中
|
|
75
|
+
}
|
|
59
76
|
|
|
60
77
|
export default {
|
|
61
78
|
name: 'ChatWindow',
|
|
62
79
|
components: {
|
|
63
80
|
ChatRobot,
|
|
64
81
|
ChatAvatar,
|
|
65
|
-
ChatWindowDialog
|
|
82
|
+
ChatWindowDialog,
|
|
83
|
+
AudioSubtitle
|
|
66
84
|
},
|
|
67
85
|
mixins: [audioMixin, webSocketMixin, messageMixin],
|
|
68
86
|
props: {
|
|
@@ -73,30 +91,37 @@ export default {
|
|
|
73
91
|
},
|
|
74
92
|
data() {
|
|
75
93
|
return {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
lastTimeUpdate: null, // 上次更新时间
|
|
95
|
+
chatId: generateUuid(), // 唯一标识当前聊天会话
|
|
96
|
+
audioSrc: AUDIO_URL, // 音频文件URL
|
|
97
|
+
inputMessage: '', // 当前输入的消息
|
|
98
|
+
visible: false, // 是否显示聊天窗口
|
|
99
|
+
messages: [], // 聊天消息数组
|
|
100
|
+
messageLoading: true, // 是否正在加载消息
|
|
101
|
+
robotStatus: 'leaving', // 机器人状态 entering: 进入中, waiting: 等待中, speaking: 说话中, leaving: 离开中
|
|
102
|
+
avaterStatus: 'normal', // 头像状态 normal: 正常, thinking: 思考中
|
|
103
|
+
currentMessage: null, // 当前正在处理的消息
|
|
104
|
+
thinkStatus: true, // 是否思考中
|
|
105
|
+
jumpedTimePoints: new Set(), // 已跳转的时间点集合
|
|
106
|
+
SAMPLE_RATE, // 采样率 16000Hz
|
|
107
|
+
FRAME_SIZE, // 帧大小 512
|
|
90
108
|
dragThreshold: 5, // 拖拽阈值
|
|
91
|
-
isDragging: false,
|
|
92
|
-
dragStartX: 0,
|
|
93
|
-
dragStartY: 0,
|
|
94
|
-
currentX: 10,
|
|
95
|
-
currentY: 20,
|
|
96
|
-
initialX: 10,
|
|
97
|
-
initialY: 20,
|
|
98
|
-
hasMoved: false,
|
|
99
|
-
timeJumpPoints:
|
|
109
|
+
isDragging: false, // 是否正在拖拽
|
|
110
|
+
dragStartX: 0, // 拖拽开始时的X坐标
|
|
111
|
+
dragStartY: 0, // 拖拽开始时的Y坐标
|
|
112
|
+
currentX: 10, // 当前X坐标
|
|
113
|
+
currentY: 20, // 当前Y坐标
|
|
114
|
+
initialX: 10, // 初始X坐标
|
|
115
|
+
initialY: 20, // 初始Y坐标
|
|
116
|
+
hasMoved: false, // 是否已移动
|
|
117
|
+
timeJumpPoints: null, // 跳转
|
|
118
|
+
subTitlePoints: null, // 字幕
|
|
119
|
+
currentJumpPoints: [], // 当前跳转点数组
|
|
120
|
+
currentSubtitles: [], // 当前字幕数组
|
|
121
|
+
currentSubtitle: '', // 当前字幕,
|
|
122
|
+
jumpIndex: 0, // 当前跳转索引
|
|
123
|
+
subtitleIndex: 0, // 当前字幕索引
|
|
124
|
+
isTourRunning: false, // 是否正在导览
|
|
100
125
|
}
|
|
101
126
|
},
|
|
102
127
|
computed: {
|
|
@@ -114,13 +139,16 @@ export default {
|
|
|
114
139
|
};
|
|
115
140
|
}
|
|
116
141
|
},
|
|
117
|
-
mounted() {
|
|
142
|
+
async mounted() {
|
|
118
143
|
this.initWebSocket()
|
|
119
144
|
if (this.appendToBody) {
|
|
120
145
|
this.appendToBodyHandler()
|
|
121
146
|
}
|
|
122
147
|
|
|
123
|
-
|
|
148
|
+
await Promise.all([
|
|
149
|
+
this.fetchTimeJumpPoints(),
|
|
150
|
+
this.fetchSubTitlePoints()
|
|
151
|
+
])
|
|
124
152
|
|
|
125
153
|
this.$nextTick(() => {
|
|
126
154
|
const chatEl = this.$el;
|
|
@@ -141,17 +169,30 @@ export default {
|
|
|
141
169
|
document.removeEventListener('mouseup', this.stopDrag)
|
|
142
170
|
},
|
|
143
171
|
methods: {
|
|
172
|
+
initGuide() {
|
|
173
|
+
this.jumpIndex = 0
|
|
174
|
+
this.subtitleIndex = 0
|
|
175
|
+
|
|
176
|
+
this.jumpedTimePoints.clear()
|
|
177
|
+
|
|
178
|
+
this.setRobotStatus(ROBOT_STATUS.LEAVING)
|
|
179
|
+
this.setAvatarStatus(AVATAR_STATUS.NORMAL)
|
|
180
|
+
},
|
|
181
|
+
setRobotStatus(status) {
|
|
182
|
+
this.robotStatus = status || ROBOT_STATUS.LEAVING
|
|
183
|
+
},
|
|
184
|
+
setAvatarStatus(status) {
|
|
185
|
+
this.avaterStatus = status || AVATAR_STATUS.NORMAL
|
|
186
|
+
},
|
|
144
187
|
toggleWindow() {
|
|
145
|
-
if (this.avaterStatus === 'thinking') return;
|
|
146
188
|
this.visible = !this.visible
|
|
147
189
|
|
|
148
190
|
if (this.visible) {
|
|
149
191
|
this.currentX = this.initialX
|
|
150
192
|
this.currentY = this.initialY
|
|
151
193
|
}
|
|
152
|
-
|
|
153
|
-
startDrag() {
|
|
154
|
-
console.log('startDrag')
|
|
194
|
+
},
|
|
195
|
+
startDrag(event) {
|
|
155
196
|
if (this.robotStatus !== 'leaving' && this.visible) return;
|
|
156
197
|
|
|
157
198
|
this.isDragging = true;
|
|
@@ -208,9 +249,18 @@ export default {
|
|
|
208
249
|
document.removeEventListener('mousemove', this.onDrag);
|
|
209
250
|
document.removeEventListener('mouseup', this.stopDrag);
|
|
210
251
|
if (!this.hasMoved) {
|
|
211
|
-
this.
|
|
252
|
+
this.handleClick()
|
|
212
253
|
}
|
|
213
254
|
},
|
|
255
|
+
handleClick() {
|
|
256
|
+
if (this.avaterStatus === 'thinking') return
|
|
257
|
+
if (this.isInBlacklist()) return
|
|
258
|
+
this.toggleWindow()
|
|
259
|
+
},
|
|
260
|
+
isInBlacklist() {
|
|
261
|
+
const tenantId = getCookie('bonyear-tenantId')
|
|
262
|
+
return tenantId && BLACKLIST.includes(tenantId)
|
|
263
|
+
},
|
|
214
264
|
handleThinkingClick() {
|
|
215
265
|
this.thinkStatus = !this.thinkStatus
|
|
216
266
|
},
|
|
@@ -226,49 +276,292 @@ export default {
|
|
|
226
276
|
},
|
|
227
277
|
async fetchTimeJumpPoints() {
|
|
228
278
|
try {
|
|
229
|
-
const res = await fetch(TIME_JUMP_POINTS_URL)
|
|
279
|
+
const res = await fetch(TIME_JUMP_POINTS_URL + '?timestamp=' + Date.now())
|
|
230
280
|
const data = await res.json()
|
|
231
|
-
this.timeJumpPoints =
|
|
232
|
-
console.log('时间跳转点加载完成:', this.timeJumpPoints)
|
|
281
|
+
this.timeJumpPoints = data
|
|
233
282
|
} catch (err) {
|
|
234
283
|
console.error('获取时间跳转点失败:', err)
|
|
235
|
-
this.timeJumpPoints =
|
|
284
|
+
this.timeJumpPoints = null
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
async fetchSubTitlePoints() {
|
|
288
|
+
try {
|
|
289
|
+
const res = await fetch(SUBTITLE_POINTS_URL + '?timestamp=' + Date.now())
|
|
290
|
+
const data = await res.json()
|
|
291
|
+
this.subTitlePoints = data
|
|
292
|
+
} catch (err) {
|
|
293
|
+
console.error('获取字幕跳转点失败')
|
|
294
|
+
this.subTitlePoints = null
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
normalizeCommand(cmd = '') {
|
|
298
|
+
return cmd
|
|
299
|
+
.trim() // 去掉 \n \r 空格
|
|
300
|
+
.replace(/[\u200B-\u200D]/g,'') // 去零宽字符
|
|
301
|
+
.replace(/[。!?,、,.!?]/g, '') // 去标点
|
|
302
|
+
},
|
|
303
|
+
// 分析音频语音指令
|
|
304
|
+
analyzeVoiceCommand(name) {
|
|
305
|
+
console.log('===== analyzeVoiceCommand =====')
|
|
306
|
+
|
|
307
|
+
console.log('typeof name:', typeof name)
|
|
308
|
+
console.log('name === "结束导览":', name === '结束导览')
|
|
309
|
+
console.log('Object.prototype.toString:', Object.prototype.toString.call(name))
|
|
310
|
+
|
|
311
|
+
const normalized = this.normalizeCommand(name)
|
|
312
|
+
const commandMap = {
|
|
313
|
+
'开始导览': this.startTheTour,
|
|
314
|
+
'开始区域看板导览': this.startAreaBoardTour,
|
|
315
|
+
'开始企业应用导览': this.startEnterpriseApplicationTour,
|
|
316
|
+
'开始区域应用导览': this.startRegionalApplicationTour,
|
|
317
|
+
'暂停导览': this.pauseTheTour,
|
|
318
|
+
'继续导览': this.resumeTheTour,
|
|
319
|
+
'结束导览': this.offTheTour,
|
|
320
|
+
'返回首页': this.returnToHome,
|
|
321
|
+
'未知指令': this.offTheTour,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const handler = commandMap[normalized]
|
|
325
|
+
|
|
326
|
+
if (!handler) {
|
|
327
|
+
console.warn('未定义的指令:', name)
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
handler()
|
|
332
|
+
},
|
|
333
|
+
// 启动导览
|
|
334
|
+
async onTheTour() {
|
|
335
|
+
if (this.isTourRunning) return
|
|
336
|
+
|
|
337
|
+
this.isTourRunning = true
|
|
338
|
+
|
|
339
|
+
const audio = this.$refs.audioPlayer
|
|
340
|
+
if (!audio) return
|
|
341
|
+
|
|
342
|
+
this.setRobotStatus(ROBOT_STATUS.ENTERING)
|
|
343
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
344
|
+
|
|
345
|
+
this.setRobotStatus(ROBOT_STATUS.SPEAKING)
|
|
346
|
+
try {
|
|
347
|
+
await audio.play()
|
|
348
|
+
} catch (e) {
|
|
349
|
+
console.error('播放音频失败:', e)
|
|
350
|
+
} finally {
|
|
351
|
+
this.isTourRunning = false
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
// 暂停导览
|
|
355
|
+
pauseTheTour() {
|
|
356
|
+
this.pause()
|
|
357
|
+
this.setRobotStatus(ROBOT_STATUS.WAITING)
|
|
358
|
+
},
|
|
359
|
+
// 继续导览
|
|
360
|
+
resumeTheTour() {
|
|
361
|
+
this.play()
|
|
362
|
+
this.setRobotStatus(ROBOT_STATUS.SPEAKING)
|
|
363
|
+
},
|
|
364
|
+
// 结束导览
|
|
365
|
+
offTheTour() {
|
|
366
|
+
this.stop()
|
|
367
|
+
this.currentSubtitle = ''
|
|
368
|
+
this.setRobotStatus(ROBOT_STATUS.LEAVING)
|
|
369
|
+
this.setAvatarStatus(AVATAR_STATUS.NORMAL)
|
|
370
|
+
},
|
|
371
|
+
// 返回首页
|
|
372
|
+
returnToHome() {
|
|
373
|
+
this.setRobotStatus(ROBOT_STATUS.LEAVING)
|
|
374
|
+
this.setAvatarStatus(AVATAR_STATUS.NORMAL)
|
|
375
|
+
this.$appOptions.router.push({ path: '/' })
|
|
376
|
+
},
|
|
377
|
+
initJumpPoints(name) {
|
|
378
|
+
// 初始化跳转点
|
|
379
|
+
if (name) {
|
|
380
|
+
this.currentJumpPoints = this.timeJumpPoints[name] || []
|
|
381
|
+
console.log('currentJumpPoints:', this.currentJumpPoints)
|
|
382
|
+
} else {
|
|
383
|
+
this.currentJumpPoints = this.timeJumpPoints
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
initSubtitles(name) {
|
|
387
|
+
console.log('name:', name)
|
|
388
|
+
if (name) {
|
|
389
|
+
this.currentSubtitles = this.subTitlePoints[name] || []
|
|
390
|
+
} else {
|
|
391
|
+
this.currentSubtitles = this.subTitlePoints
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
// 开始导览
|
|
395
|
+
async startTheTour() {
|
|
396
|
+
await this.setAudio(COLLECTION) // 重置音频源
|
|
397
|
+
|
|
398
|
+
// 重置指针
|
|
399
|
+
this.initGuide()
|
|
400
|
+
// 重置跳转点指针
|
|
401
|
+
this.initJumpPoints('开始导览')
|
|
402
|
+
// 重置字幕指针
|
|
403
|
+
this.initSubtitles('开始导览')
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
await this.onTheTour()
|
|
407
|
+
},
|
|
408
|
+
// 开始区域看板导览
|
|
409
|
+
async startAreaBoardTour() {
|
|
410
|
+
// 重置audio
|
|
411
|
+
await this.setAudio(AREA_BOARD)
|
|
412
|
+
|
|
413
|
+
// 重置指针
|
|
414
|
+
this.initGuide()
|
|
415
|
+
// 重置跳转点指针
|
|
416
|
+
this.initJumpPoints('开始区域看板导览')
|
|
417
|
+
// 重置字幕指针
|
|
418
|
+
this.initSubtitles('开始区域看板导览')
|
|
419
|
+
|
|
420
|
+
await this.onTheTour()
|
|
421
|
+
},
|
|
422
|
+
// 开始企业应用导览
|
|
423
|
+
async startEnterpriseApplicationTour() {
|
|
424
|
+
await this.setAudio(ENTERPRISE_APPLICATION)
|
|
425
|
+
|
|
426
|
+
// 重置指针
|
|
427
|
+
this.initGuide()
|
|
428
|
+
// 重置跳转点指针
|
|
429
|
+
this.initJumpPoints('开始企业应用导览')
|
|
430
|
+
// 重置字幕指针
|
|
431
|
+
this.initSubtitles('开始企业应用导览')
|
|
432
|
+
|
|
433
|
+
await this.onTheTour()
|
|
434
|
+
},
|
|
435
|
+
// 开始区域应用导览
|
|
436
|
+
async startRegionalApplicationTour() {
|
|
437
|
+
await this.setAudio(REGIONAL_APPLICATION)
|
|
438
|
+
|
|
439
|
+
// 重置指针
|
|
440
|
+
this.initGuide()
|
|
441
|
+
// 重置跳转点指针
|
|
442
|
+
this.initJumpPoints('开始区域应用导览')
|
|
443
|
+
// 重置字幕指针
|
|
444
|
+
this.initSubtitles('开始区域应用导览')
|
|
445
|
+
|
|
446
|
+
await this.onTheTour()
|
|
447
|
+
},
|
|
448
|
+
// 分析音频命令
|
|
449
|
+
analyzeAudioCommand(command) {
|
|
450
|
+
console.log('分析音频命令:', command);
|
|
451
|
+
|
|
452
|
+
if (command === 'C5') {
|
|
453
|
+
this.setRobotStatus(ROBOT_STATUS.ENTERING);
|
|
454
|
+
setTimeout(() => {
|
|
455
|
+
this.setRobotStatus(ROBOT_STATUS.SPEAKING);
|
|
456
|
+
this.play();
|
|
457
|
+
}, 3000);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (command === 'C8') {
|
|
461
|
+
this.setRobotStatus(ROBOT_STATUS.SPEAKING);
|
|
462
|
+
this.play();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (command === 'C7') {
|
|
466
|
+
this.setRobotStatus(ROBOT_STATUS.WAITING);
|
|
467
|
+
this.pause();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (command === 'C6') {
|
|
471
|
+
this.setRobotStatus(ROBOT_STATUS.LEAVING);
|
|
472
|
+
this.stop();
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
// 处理点击跳转时间
|
|
476
|
+
handleJumpPoint(currentTime) {
|
|
477
|
+
console.log('jumpIndex:', this.jumpIndex)
|
|
478
|
+
const point = this.currentJumpPoints[this.jumpIndex]
|
|
479
|
+
|
|
480
|
+
if (!point) return
|
|
481
|
+
console.log('currentTime:', currentTime)
|
|
482
|
+
console.log('point.time:', point.time)
|
|
483
|
+
|
|
484
|
+
if (currentTime >= point.time && !this.jumpedTimePoints.has(point.time)) {
|
|
485
|
+
|
|
486
|
+
this.jumpedTimePoints.add(point.time)
|
|
487
|
+
this.jumpIndex++
|
|
488
|
+
|
|
489
|
+
this.$appOptions.store.dispatch('tags/addTagview', {
|
|
490
|
+
path: point.url,
|
|
491
|
+
fullPath: point.url,
|
|
492
|
+
label: point.title,
|
|
493
|
+
name: point.title,
|
|
494
|
+
meta: { title: point.title },
|
|
495
|
+
query: {},
|
|
496
|
+
params: {}
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
this.$appOptions.router.push({ path: point.url })
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
// 处理播放字幕
|
|
503
|
+
handlePlaySubtitles(currentTime){
|
|
504
|
+
const subtitle = this.currentSubtitles[this.subtitleIndex]
|
|
505
|
+
|
|
506
|
+
if (!subtitle) return
|
|
507
|
+
|
|
508
|
+
if (currentTime < subtitle.start) return
|
|
509
|
+
|
|
510
|
+
// 正在播放当前字幕
|
|
511
|
+
if (currentTime >= subtitle.start && currentTime <= subtitle.end) {
|
|
512
|
+
if (this.currentSubtitle !== subtitle.text) {
|
|
513
|
+
this.currentSubtitle = subtitle.text
|
|
514
|
+
}
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// 超过当前字幕,推进游标
|
|
519
|
+
if (currentTime > subtitle.end) {
|
|
520
|
+
this.subtitleIndex++
|
|
521
|
+
this.currentSubtitle = ''
|
|
236
522
|
}
|
|
237
523
|
},
|
|
524
|
+
async setAudio(src) {
|
|
525
|
+
return new Promise((resolve, reject) => {
|
|
526
|
+
const audio = this.$refs.audioPlayer
|
|
527
|
+
|
|
528
|
+
if (!audio) return reject('未找到音频元素')
|
|
529
|
+
|
|
530
|
+
audio.pause()
|
|
531
|
+
audio.currentTime = 0
|
|
532
|
+
|
|
533
|
+
// ✅ 用响应式数据
|
|
534
|
+
this.audioSrc = src
|
|
535
|
+
|
|
536
|
+
this.$nextTick(() => {
|
|
537
|
+
audio.load()
|
|
538
|
+
|
|
539
|
+
const onCanPlay = () => {
|
|
540
|
+
audio.removeEventListener('canplay', onCanPlay)
|
|
541
|
+
resolve()
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
audio.addEventListener('canplay', onCanPlay)
|
|
545
|
+
})
|
|
546
|
+
})
|
|
547
|
+
},
|
|
238
548
|
// 音频时间更新处理
|
|
239
549
|
onTimeUpdate() {
|
|
550
|
+
const now = performance.now()
|
|
551
|
+
|
|
552
|
+
if (this.lastTimeUpdate && now - this.lastTimeUpdate < 200) return
|
|
553
|
+
this.lastTimeUpdate = now
|
|
554
|
+
|
|
240
555
|
const audio = this.$refs.audioPlayer
|
|
241
556
|
const currentTime = audio.currentTime
|
|
242
557
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.timeJumpPoints.forEach(point => {
|
|
246
|
-
if (
|
|
247
|
-
currentTime >= point.time &&
|
|
248
|
-
currentTime < point.time + 1 &&
|
|
249
|
-
!this.jumpedTimePoints.has(point.time)
|
|
250
|
-
) {
|
|
251
|
-
console.log('触发跳转:', point.url)
|
|
252
|
-
|
|
253
|
-
this.jumpedTimePoints.add(point.time)
|
|
254
|
-
|
|
255
|
-
this.$appOptions.store.dispatch('tags/addTagview', {
|
|
256
|
-
path: point.url,
|
|
257
|
-
fullPath: point.url,
|
|
258
|
-
label: point.title,
|
|
259
|
-
name: point.title,
|
|
260
|
-
meta: { title: point.title },
|
|
261
|
-
query: {},
|
|
262
|
-
params: {}
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
this.$appOptions.router.push({ path: point.url })
|
|
266
|
-
}
|
|
267
|
-
})
|
|
558
|
+
this.handleJumpPoint(currentTime)
|
|
559
|
+
this.handlePlaySubtitles(currentTime)
|
|
268
560
|
},
|
|
269
561
|
onAudioEnded() {
|
|
270
|
-
this.
|
|
271
|
-
this.
|
|
562
|
+
this.currentSubtitle = ''
|
|
563
|
+
this.setRobotStatus(ROBOT_STATUS.LEAVING)
|
|
564
|
+
this.setAvatarStatus(AVATAR_STATUS.NORMAL)
|
|
272
565
|
this.jumpedTimePoints.clear()
|
|
273
566
|
}
|
|
274
567
|
}
|
|
@@ -282,7 +575,6 @@ export default {
|
|
|
282
575
|
opacity: 0;
|
|
283
576
|
pointer-events: none;
|
|
284
577
|
}
|
|
285
|
-
|
|
286
578
|
.chat {
|
|
287
579
|
position: fixed;
|
|
288
580
|
bottom: 20px;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
const baseUrl = window.location.host;
|
|
1
|
+
// const baseUrl = window.location.host;
|
|
2
2
|
|
|
3
3
|
export const API_URL = `/lingxiao-byt/api/v1/mcp/ask`; // 对话
|
|
4
|
-
export const WS_URL = `ws://${baseUrl}/audio/ws/`; // 语音
|
|
5
|
-
export const
|
|
4
|
+
// export const WS_URL = `ws://${baseUrl}/audio/ws/`; // 语音
|
|
5
|
+
export const WS_URL = `ws://220.189.237.146:8312/audio/ws/`; // 测试语音
|
|
6
|
+
|
|
7
|
+
export const AUDIO_URL = '/minio/lingxiaoai/byt.mp3'; // 导览 铜梁
|
|
8
|
+
|
|
9
|
+
export const COLLECTION = '/minio/lingxiaoai/collection.mp3'
|
|
10
|
+
export const AREA_BOARD = '/minio/lingxiaoai/area_board.mp3'; // 区域看板
|
|
11
|
+
export const ENTERPRISE_APPLICATION = '/minio/lingxiaoai/enterprise_application.mp3'; // 企业应用
|
|
12
|
+
export const REGIONAL_APPLICATION = '/minio/lingxiaoai/regional_application.mp3'; // 区域应用
|
|
13
|
+
|
|
6
14
|
export const TIME_JUMP_POINTS_URL = '/minio/lingxiaoai/timeJumpPoints.json'; // 语音url跳转节点
|
|
15
|
+
export const SUBTITLE_POINTS_URL = '/minio/lingxiaoai/subTitlePoints.json'; // 字幕跳转节点
|