提交 46c835f3 authored 作者: 王鹏飞's avatar 王鹏飞

feat: 优化直播记录的AI分析

上级 3a747802
...@@ -13,6 +13,8 @@ import dayjs from 'dayjs' ...@@ -13,6 +13,8 @@ import dayjs from 'dayjs'
import { useLiveChat } from '../composables/useLiveChat' import { useLiveChat } from '../composables/useLiveChat'
import { useSpeechTranscriber } from '../composables/useSpeechTranscriber' import { useSpeechTranscriber } from '../composables/useSpeechTranscriber'
import { appendUpload } from '@/utils/oss' import { appendUpload } from '@/utils/oss'
import { useLiveRecord } from '../composables/useLiveRecord'
import { Check } from '@element-plus/icons-vue'
const props = defineProps({ const props = defineProps({
orderCount: { type: Number, default: 0 }, orderCount: { type: Number, default: 0 },
...@@ -22,7 +24,8 @@ const props = defineProps({ ...@@ -22,7 +24,8 @@ const props = defineProps({
onSentenceEnd: { type: Function, default: () => {} }, onSentenceEnd: { type: Function, default: () => {} },
onStatsChange: { type: Function, default: () => {} }, onStatsChange: { type: Function, default: () => {} },
}) })
const dialogVisible = ref(false)
const { statusText, startPolling, isPolling } = useLiveRecord()
const { messages, viewers, stats, currentTime, start: startChat, stop: stopChat } = useLiveChat() const { messages, viewers, stats, currentTime, start: startChat, stop: stopChat } = useLiveChat()
watch( watch(
() => [stats, viewers], () => [stats, viewers],
...@@ -149,6 +152,9 @@ const handleUpdateRecord = async (params) => { ...@@ -149,6 +152,9 @@ const handleUpdateRecord = async (params) => {
if (!requestParams.live_practice_id) return if (!requestParams.live_practice_id) return
const res = await saveTestRecord(requestParams) const res = await saveTestRecord(requestParams)
recordId.value = res?.data.id recordId.value = res?.data.id
// 开始AI评分轮询
startPolling(recordId.value)
dialogVisible.value = true
} }
// 上传视频 // 上传视频
...@@ -234,6 +240,32 @@ defineExpose({ enabled, start, stop }) ...@@ -234,6 +240,32 @@ defineExpose({ enabled, start, stop })
> >
</div> </div>
</div> </div>
<el-dialog title="提示" :model-value="dialogVisible" :showClose="false" width="500px">
<div class="modal-content">
<template v-if="isPolling">
<div class="progress-circle">
<div class="spinner"></div>
</div>
<div class="progress-text">{{ statusText }}</div>
</template>
<template v-else>
<div class="success-icon">
<el-icon><Check /></el-icon>
</div>
<div class="progress-text">AI评价完成</div>
</template>
</div>
<template #footer>
<el-button auto-insert-space @click="$router.push({ path: '/live/test' })">确定</el-button>
<el-button
type="primary"
auto-insert-space
@click="$router.push({ path: '/live/test/view', query: { ...$route.query, record_id: recordId } })"
v-if="!isPolling && recordId"
>查看AI评价</el-button
>
</template>
</el-dialog>
</template> </template>
<style lang="scss"> <style lang="scss">
...@@ -268,4 +300,53 @@ defineExpose({ enabled, start, stop }) ...@@ -268,4 +300,53 @@ defineExpose({ enabled, start, stop })
text-align: center; text-align: center;
} }
} }
.modal-content {
text-align: center;
padding: 1rem;
}
.progress-circle {
width: 80px;
height: 80px;
margin: 0 auto 2rem;
position: relative;
}
.spinner {
width: 100%;
height: 100%;
border: 8px solid #f5f7fa;
border-top-color: #d91f5d;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.progress-text {
font-size: 1rem;
color: #333;
margin-bottom: 1rem;
text-align: center;
}
.success-icon {
width: 100px;
height: 100px;
background: #ba003f;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 2rem;
i {
font-size: 3rem;
color: white;
}
}
</style> </style>
差异被折叠。
import { getRecord, updateRecord } from '../api'
import { useChat } from '@ezijing/ai-vue'
import { systemPrompt, getUserPrompt } from './prompt'
export function useLiveRecord({ id = '', immediate = false }: { id?: string; immediate?: boolean } = {}) {
const { isLoading, generateText } = useChat({ provider: 'volcano' })
const recordId = ref(id)
const liveRecord = ref<any>(null)
const statusText = ref('')
const isPolling = ref(false)
let pollingTimer: ReturnType<typeof setTimeout> | null = null
const fetchLiveRecord = async () => {
if (!recordId.value) return
const res = await getRecord({ id: recordId.value })
const resDetail = res.data.detail
liveRecord.value = { ...resDetail, live_info: JSON.parse(resDetail.live_info) }
return liveRecord.value
}
onMounted(() => {
if (immediate && recordId.value) {
fetchLiveRecord().then(() => {
if (!liveRecord.value?.ai_analyze) handleAIScore()
})
}
})
const handleAIScore = async () => {
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: getUserPrompt(liveRecord.value) },
]
const result = await generateText({ messages, response_format: { type: 'json_object' } } as any)
if (!result) return
const aiAnalyze = result.content
.replaceAll('卖点讲解覆盖情况', '')
.replaceAll('违禁词分析报告', '')
.replaceAll('直播优化建议', '')
.replaceAll('语速分析报告', '')
await updateRecord({ id: recordId.value, ai_analyze: aiAnalyze })
if (immediate) fetchLiveRecord()
}
const startPolling = (id = '', interval = 3000) => {
if (isPolling.value) return
if (id) recordId.value = id
isPolling.value = true
const poll = async () => {
if (!isPolling.value) return
statusText.value = '口播内容识别中,请稍后...'
const record = await fetchLiveRecord()
if (record?.subtitle) {
statusText.value = 'AI分析中,大约需要1分钟,请耐心等待...'
await handleAIScore()
stopPolling()
} else {
pollingTimer = setTimeout(poll, interval)
}
}
poll()
}
const stopPolling = () => {
isPolling.value = false
statusText.value = ''
if (pollingTimer) {
clearTimeout(pollingTimer)
pollingTimer = null
}
}
onUnmounted(() => {
stopPolling()
})
return { liveRecord, fetchLiveRecord, handleAIScore, startPolling, stopPolling, isPolling, isLoading, statusText }
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论