feat: 优化
This commit is contained in:
@@ -15,21 +15,19 @@ import BenchmarkTable from './components/BenchmarkTable.vue'
|
||||
import BatchAnalyzeModal from './components/BatchAnalyzeModal.vue'
|
||||
import SavePromptModal from './components/SavePromptModal.vue'
|
||||
|
||||
// ==================== 初始化 ====================
|
||||
const router = useRouter()
|
||||
const promptStore = usePromptStore()
|
||||
|
||||
// ==================== 数据管理 ====================
|
||||
const {
|
||||
data,
|
||||
selectedRowKeys,
|
||||
expandedRowKeys,
|
||||
saveTableDataToSession,
|
||||
loadTableDataFromSession,
|
||||
processApiResponse,
|
||||
clearData,
|
||||
} = useBenchmarkData()
|
||||
|
||||
// ==================== 分析功能 ====================
|
||||
const {
|
||||
loading,
|
||||
batchAnalyzeLoading,
|
||||
@@ -37,9 +35,8 @@ const {
|
||||
globalLoadingText,
|
||||
batchAnalyze,
|
||||
getVoiceText,
|
||||
} = useBenchmarkAnalysis(data, saveTableDataToSession)
|
||||
} = useBenchmarkAnalysis(data, expandedRowKeys, saveTableDataToSession)
|
||||
|
||||
// ==================== 表单状态 ====================
|
||||
const form = ref({
|
||||
platform: '抖音',
|
||||
url: '',
|
||||
@@ -47,7 +44,6 @@ const form = ref({
|
||||
sort_type: 0,
|
||||
})
|
||||
|
||||
// ==================== 弹窗状态 ====================
|
||||
const modalVisible = ref(false)
|
||||
const batchPromptMergedText = ref('')
|
||||
const batchPromptTextCount = ref(0)
|
||||
@@ -55,10 +51,6 @@ const batchPromptTextCount = ref(0)
|
||||
const savePromptModalVisible = ref(false)
|
||||
const savePromptContent = ref('')
|
||||
|
||||
// ==================== API 调用函数 ====================
|
||||
/**
|
||||
* 分析用户主页,获取视频列表
|
||||
*/
|
||||
async function handleAnalyzeUser() {
|
||||
const sec_user_id = resolveId(form.value.url, {
|
||||
queryKeys: ['user'],
|
||||
@@ -94,16 +86,13 @@ async function handleAnalyzeUser() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出数据到 Excel
|
||||
*/
|
||||
async function handleExportToExcel() {
|
||||
if (!data.value || data.value.length === 0) {
|
||||
if (!data.value?.length) {
|
||||
message.warning('暂无数据可导出')
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedRowKeys.value.length === 0) {
|
||||
if (!selectedRowKeys.value.length) {
|
||||
message.warning('请先选择要导出的行')
|
||||
return
|
||||
}
|
||||
@@ -116,15 +105,13 @@ async function handleExportToExcel() {
|
||||
const selectedRows = data.value.filter(item => selectedRowKeys.value.includes(item.id))
|
||||
const rowsNeedTranscription = selectedRows.filter(row => !row.transcriptions)
|
||||
|
||||
// 导出时只获取语音转写,不进行 AI 对话分析
|
||||
if (rowsNeedTranscription.length > 0) {
|
||||
if (rowsNeedTranscription.length) {
|
||||
globalLoading.value = true
|
||||
globalLoadingText.value = `正在分析中...`
|
||||
globalLoadingText.value = '正在分析中...'
|
||||
|
||||
try {
|
||||
const transcriptions = await getVoiceText(rowsNeedTranscription)
|
||||
|
||||
// 更新转写数据
|
||||
for (const row of rowsNeedTranscription) {
|
||||
const transcription = transcriptions.find(item => item.audio_url === row.audio_url)
|
||||
if (transcription) {
|
||||
@@ -163,9 +150,6 @@ async function handleExportToExcel() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分析处理
|
||||
*/
|
||||
async function handleBatchAnalyze() {
|
||||
try {
|
||||
await batchAnalyze(selectedRowKeys, async (mergedText, textCount) => {
|
||||
@@ -174,22 +158,17 @@ async function handleBatchAnalyze() {
|
||||
modalVisible.value = true
|
||||
})
|
||||
} finally {
|
||||
// 批量分析完成后清空选中项(无论成功还是失败)
|
||||
selectedRowKeys.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
async function handleResetForm() {
|
||||
form.value = { platform: '抖音', url: '', count: 20, sort_type: 0 }
|
||||
await clearData()
|
||||
}
|
||||
|
||||
// ==================== 批量提示词操作函数 ====================
|
||||
function handleCopyBatchPrompt(prompt) {
|
||||
if (!prompt || !prompt.trim()) {
|
||||
if (!prompt?.trim()) {
|
||||
message.warning('没有提示词可复制')
|
||||
return
|
||||
}
|
||||
@@ -202,7 +181,7 @@ function handleCopyBatchPrompt(prompt) {
|
||||
}
|
||||
|
||||
function handleUseBatchPrompt(prompt) {
|
||||
if (!prompt || !prompt.trim()) {
|
||||
if (!prompt?.trim()) {
|
||||
message.warning('暂无批量生成的提示词')
|
||||
return
|
||||
}
|
||||
@@ -211,11 +190,9 @@ function handleUseBatchPrompt(prompt) {
|
||||
router.push('/content-style/copywriting')
|
||||
}
|
||||
|
||||
// ==================== 保存提示词到服务器 ====================
|
||||
function handleOpenSavePromptModal(batchPrompt = null) {
|
||||
// 批量提示词:使用传入的 batchPrompt(AI 生成的内容),而不是原始的 mergedText
|
||||
const promptToSave = batchPrompt || batchPromptMergedText.value
|
||||
if (!promptToSave || !promptToSave.trim()) {
|
||||
if (!promptToSave?.trim()) {
|
||||
message.warning('没有提示词可保存')
|
||||
return
|
||||
}
|
||||
@@ -223,7 +200,6 @@ function handleOpenSavePromptModal(batchPrompt = null) {
|
||||
savePromptModalVisible.value = true
|
||||
}
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
onMounted(async () => {
|
||||
await loadTableDataFromSession()
|
||||
})
|
||||
@@ -252,8 +228,7 @@ defineOptions({ name: 'ContentStyleBenchmark' })
|
||||
@batch-analyze="handleBatchAnalyze"
|
||||
/>
|
||||
|
||||
<!-- 空态显示 -->
|
||||
<section class="card results-card empty-state" v-if="data.length === 0 && !loading">
|
||||
<section v-if="!data.length && !loading" class="card results-card empty-state">
|
||||
<a-empty description="暂无数据,请点击开始分析">
|
||||
<template #image>
|
||||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { EditOutlined, CopyOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import ChatMessageRenderer from '@/components/ChatMessageRenderer.vue'
|
||||
import ChatMessageRendererV2 from '@/components/ChatMessageRendererV2.vue'
|
||||
import { ChatMessageApi } from '@/api/chat'
|
||||
import { streamChat } from '@/utils/streamChat'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
mergedText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
textCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
const props = withDefaults(defineProps<{
|
||||
visible: boolean
|
||||
mergedText: string
|
||||
textCount: number
|
||||
}>(), {
|
||||
visible: false,
|
||||
mergedText: '',
|
||||
textCount: 0,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'copy', 'save', 'use'])
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
'copy': [text: string]
|
||||
'save': [text: string]
|
||||
'use': [text: string]
|
||||
}>()
|
||||
|
||||
const batchPrompt = ref('')
|
||||
const batchPromptEditMode = ref(false)
|
||||
const batchPromptGenerating = ref(false)
|
||||
const hasGenerated = ref(false)
|
||||
|
||||
function resetModal() {
|
||||
batchPrompt.value = ''
|
||||
batchPromptEditMode.value = false
|
||||
batchPromptGenerating.value = false
|
||||
hasGenerated.value = false
|
||||
}
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal && props.mergedText && !hasGenerated.value) {
|
||||
generateBatchPrompt()
|
||||
} else if (!newVal) {
|
||||
batchPrompt.value = ''
|
||||
batchPromptEditMode.value = false
|
||||
batchPromptGenerating.value = false
|
||||
hasGenerated.value = false
|
||||
resetModal()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -47,47 +50,61 @@ watch(() => props.mergedText, (newVal) => {
|
||||
|
||||
async function generateBatchPrompt() {
|
||||
if (!props.mergedText || hasGenerated.value) return
|
||||
|
||||
|
||||
hasGenerated.value = true
|
||||
|
||||
try {
|
||||
batchPromptGenerating.value = true
|
||||
const createPayload = { roleId: 20 }
|
||||
console.debug('createChatConversationMy payload(batch):', createPayload)
|
||||
const conversationResp = await ChatMessageApi.createChatConversationMy(createPayload)
|
||||
|
||||
let conversationId = null
|
||||
if (conversationResp?.data) {
|
||||
conversationId = typeof conversationResp.data === 'object' ? conversationResp.data.id : conversationResp.data
|
||||
}
|
||||
|
||||
if (!conversationId) {
|
||||
throw new Error('创建对话失败:未获取到 conversationId')
|
||||
}
|
||||
|
||||
const conversationId = await createConversation()
|
||||
|
||||
const aiContent = await streamChat({
|
||||
conversationId,
|
||||
content: props.mergedText,
|
||||
onUpdate: (fullText) => {
|
||||
onUpdate: (fullText: string) => {
|
||||
batchPrompt.value = fullText
|
||||
},
|
||||
enableTypewriter: true,
|
||||
typewriterSpeed: 10,
|
||||
typewriterBatchSize: 2
|
||||
typewriterBatchSize: 2,
|
||||
onComplete: () => {},
|
||||
onError: (error: Error) => {
|
||||
console.error('流式聊天错误:', error)
|
||||
},
|
||||
enableContext: false,
|
||||
enableWebSearch: false,
|
||||
timeout: 180000,
|
||||
attachmentUrls: []
|
||||
})
|
||||
|
||||
if (aiContent && aiContent !== batchPrompt.value) {
|
||||
batchPrompt.value = aiContent
|
||||
}
|
||||
|
||||
|
||||
message.success(`批量分析完成:已基于 ${props.textCount} 个视频的文案生成综合提示词`)
|
||||
} catch (aiError) {
|
||||
console.error('AI生成失败:', aiError)
|
||||
} catch (error) {
|
||||
console.error('AI生成失败:', error)
|
||||
message.error('AI生成失败,请稍后重试')
|
||||
} finally {
|
||||
batchPromptGenerating.value = false
|
||||
hasGenerated.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createConversation() {
|
||||
const createPayload = { roleId: 20 }
|
||||
const conversationResp = await ChatMessageApi.createChatConversationMy(createPayload)
|
||||
|
||||
const conversationId = conversationResp?.data
|
||||
? (typeof conversationResp.data === 'object' ? conversationResp.data.id : conversationResp.data)
|
||||
: null
|
||||
|
||||
if (!conversationId) {
|
||||
throw new Error('创建对话失败:未获取到 conversationId')
|
||||
}
|
||||
|
||||
return conversationId
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
@@ -112,131 +129,86 @@ function handleUse() {
|
||||
:width="800"
|
||||
:maskClosable="false"
|
||||
:keyboard="false"
|
||||
@cancel="handleClose">
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<div class="batch-prompt-modal">
|
||||
<!-- 内容显示模式 -->
|
||||
<div v-if="!batchPromptEditMode" class="batch-prompt-display">
|
||||
<ChatMessageRenderer
|
||||
<ChatMessageRendererV2
|
||||
:content="batchPrompt"
|
||||
:is-streaming="batchPromptGenerating"
|
||||
/>
|
||||
</div>
|
||||
<a-textarea
|
||||
|
||||
<!-- 编辑模式 -->
|
||||
<a-textarea
|
||||
v-else
|
||||
v-model:value="batchPrompt"
|
||||
:rows="15"
|
||||
placeholder="内容将在这里显示..." />
|
||||
v-model:value="batchPrompt"
|
||||
:rows="15"
|
||||
placeholder="内容将在这里显示..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button size="small" :title="batchPromptEditMode ? '取消编辑' : '编辑'" @click="batchPromptEditMode = !batchPromptEditMode">
|
||||
<template #icon>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button size="small" title="复制" @click="handleCopy">
|
||||
<template #icon>
|
||||
<CopyOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button size="small" title="保存提示词" @click="handleSave" :disabled="!batchPrompt.trim()">
|
||||
保存提示词
|
||||
</a-button>
|
||||
<a-button @click="handleClose">取消</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="batchPromptGenerating || !batchPrompt.trim()"
|
||||
@click="handleUse">去创作</a-button>
|
||||
</a-space>
|
||||
<div class="footer-actions">
|
||||
<div class="left-actions">
|
||||
<a-button type="text" @click="batchPromptEditMode = !batchPromptEditMode">
|
||||
{{ batchPromptEditMode ? '取消编辑' : '编辑' }}
|
||||
</a-button>
|
||||
<a-button type="text" @click="handleCopy">复制</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
@click="handleSave"
|
||||
:disabled="!batchPrompt.trim()"
|
||||
>
|
||||
保存提示词
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="right-actions">
|
||||
<a-button @click="handleClose">取消</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="batchPromptGenerating || !batchPrompt.trim()"
|
||||
@click="handleUse"
|
||||
>
|
||||
去创作
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.batch-prompt-modal {
|
||||
min-height: 200px;
|
||||
|
||||
.batch-prompt-display {
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
}
|
||||
|
||||
.batch-prompt-display {
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
background: #0d0d0d;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.batch-prompt-display :deep(h1) {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 12px 0;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.left-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(h2) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 16px 0 8px 0;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(h3) {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 12px 0 6px 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(p) {
|
||||
margin: 8px 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(ul),
|
||||
.batch-prompt-display :deep(ol) {
|
||||
margin: 8px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(li) {
|
||||
margin: 4px 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(strong) {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(code) {
|
||||
background: #1a1a1a;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
color: #e11d48;
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(pre) {
|
||||
background: #1a1a1a;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(pre code) {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.batch-prompt-display :deep(blockquote) {
|
||||
border-left: 3px solid var(--color-primary);
|
||||
padding-left: 12px;
|
||||
margin: 8px 0;
|
||||
color: var(--color-text-secondary);
|
||||
.right-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { CopyOutlined, SaveOutlined } from '@ant-design/icons-vue'
|
||||
import ChatMessageRenderer from '@/components/ChatMessageRenderer.vue'
|
||||
import ChatMessageRendererV2 from '@/components/ChatMessageRendererV2.vue'
|
||||
|
||||
const props = defineProps({
|
||||
record: {
|
||||
@@ -26,7 +26,6 @@ function handleCreateContent() {
|
||||
|
||||
<template>
|
||||
<div class="expanded-content">
|
||||
<!-- 未分析的行显示提示 -->
|
||||
<div v-if="!record.transcriptions && !record.prompt" class="no-analysis-tip">
|
||||
<a-empty description="该视频尚未分析">
|
||||
<template #image>
|
||||
@@ -42,10 +41,8 @@ function handleCreateContent() {
|
||||
</a-button>
|
||||
</a-empty>
|
||||
</div>
|
||||
|
||||
<!-- 已分析的行显示内容 -->
|
||||
|
||||
<div v-else class="two-col">
|
||||
<!-- 左侧:原配音内容 -->
|
||||
<section class="col left-col">
|
||||
<div class="sub-title">原配音</div>
|
||||
<div class="transcript-box" v-if="record.transcriptions">
|
||||
@@ -54,44 +51,43 @@ function handleCreateContent() {
|
||||
<div v-else class="no-transcript">暂无转写文本,请先点击"分析"获取</div>
|
||||
</section>
|
||||
|
||||
<!-- 右侧:提示词 -->
|
||||
<section class="col right-col">
|
||||
<div class="sub-title">提示词</div>
|
||||
|
||||
|
||||
<div class="prompt-display-wrapper">
|
||||
<ChatMessageRenderer
|
||||
<ChatMessageRendererV2
|
||||
:content="record.prompt || ''"
|
||||
:is-streaming="record._analyzing || false"
|
||||
/>
|
||||
<div v-if="!record.prompt" class="no-prompt">暂无提示词</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="right-actions">
|
||||
<a-space>
|
||||
<a-button
|
||||
size="small"
|
||||
type="text"
|
||||
<a-button
|
||||
size="small"
|
||||
type="text"
|
||||
class="copy-btn"
|
||||
:title="'复制'"
|
||||
title="复制"
|
||||
@click="handleCopy">
|
||||
<template #icon>
|
||||
<CopyOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
<a-button
|
||||
v-if="record.prompt"
|
||||
size="small"
|
||||
type="text"
|
||||
size="small"
|
||||
type="text"
|
||||
class="save-server-btn"
|
||||
:title="'保存'"
|
||||
title="保存"
|
||||
@click="handleSaveToServer">
|
||||
<template #icon>
|
||||
<SaveOutlined />
|
||||
</template>
|
||||
保存
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
<a-button
|
||||
type="dashed"
|
||||
:disabled="!record.prompt || record._analyzing"
|
||||
@click="handleCreateContent">基于提示词去创作</a-button>
|
||||
</a-space>
|
||||
@@ -177,7 +173,6 @@ function handleCreateContent() {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
.no-analysis-tip {
|
||||
padding: var(--space-8) var(--space-5);
|
||||
text-align: center;
|
||||
|
||||
@@ -12,43 +12,35 @@ export function useBenchmarkAnalysis(data, expandedRowKeys, saveTableDataToSessi
|
||||
const globalLoadingText = ref('')
|
||||
const { getVoiceText } = useVoiceText()
|
||||
|
||||
/**
|
||||
* 分析单个视频,获取提示词
|
||||
*/
|
||||
async function analyzeVideo(row) {
|
||||
try {
|
||||
if (row._analyzing) return
|
||||
|
||||
|
||||
row._analyzing = true
|
||||
|
||||
// 1) 获取音频转写
|
||||
message.info('正在获取音频转写...')
|
||||
const transcriptions = await getVoiceText([row])
|
||||
row.transcriptions = transcriptions.find(item => item.audio_url === row.audio_url)?.value
|
||||
|
||||
// 2) 检查是否有语音文案
|
||||
if (!row.transcriptions || !row.transcriptions.trim()) {
|
||||
if (!row.transcriptions?.trim()) {
|
||||
message.warning('未提取到语音内容,请检查音频文件或稍后重试')
|
||||
row._analyzing = false
|
||||
return false
|
||||
}
|
||||
|
||||
// 3) 创建对话
|
||||
message.info('正在创建对话...')
|
||||
const createPayload = { roleId: 20, role_id: 20 }
|
||||
console.debug('createChatConversationMy payload:', createPayload)
|
||||
const conversationResp = await ChatMessageApi.createChatConversationMy(createPayload)
|
||||
|
||||
let conversationId = null
|
||||
if (conversationResp?.data) {
|
||||
conversationId = typeof conversationResp.data === 'object' ? conversationResp.data.id : conversationResp.data
|
||||
}
|
||||
|
||||
|
||||
const conversationId = conversationResp?.data
|
||||
? (typeof conversationResp.data === 'object' ? conversationResp.data.id : conversationResp.data)
|
||||
: null
|
||||
|
||||
if (!conversationId) {
|
||||
throw new Error('创建对话失败:未获取到 conversationId')
|
||||
}
|
||||
|
||||
// 4) 基于转写构建提示,流式生成并实时写入 UI
|
||||
|
||||
message.info('正在生成提示词...')
|
||||
const content = buildPromptFromTranscription(row.transcriptions)
|
||||
const index = data.value.findIndex(item => item.id === row.id)
|
||||
@@ -63,19 +55,16 @@ export function useBenchmarkAnalysis(data, expandedRowKeys, saveTableDataToSessi
|
||||
typewriterBatchSize: 2
|
||||
})
|
||||
|
||||
// 5) 兜底处理
|
||||
const finalPrompt = aiContent || row.transcriptions || ''
|
||||
if (index !== -1) data.value[index].prompt = finalPrompt
|
||||
|
||||
// 6) 分析完成后自动展开该行
|
||||
const rowId = String(row.id) // 确保类型一致
|
||||
|
||||
const rowId = String(row.id)
|
||||
if (!expandedRowKeys.value.includes(rowId)) {
|
||||
expandedRowKeys.value.push(rowId)
|
||||
}
|
||||
|
||||
// 7) 保存数据到 session
|
||||
|
||||
await saveTableDataToSession()
|
||||
|
||||
|
||||
message.success('分析完成')
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -87,11 +76,8 @@ export function useBenchmarkAnalysis(data, expandedRowKeys, saveTableDataToSessi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分析选中的视频
|
||||
*/
|
||||
async function batchAnalyze(selectedRowKeys, onBatchComplete) {
|
||||
if (selectedRowKeys.value.length === 0) {
|
||||
if (!selectedRowKeys.value.length) {
|
||||
message.warning('请先选择要分析的视频')
|
||||
return
|
||||
}
|
||||
@@ -101,26 +87,23 @@ export function useBenchmarkAnalysis(data, expandedRowKeys, saveTableDataToSessi
|
||||
globalLoadingText.value = `正在批量分析 ${selectedRowKeys.value.length} 个视频...`
|
||||
|
||||
try {
|
||||
// 1. 获取所有选中视频的语音转写
|
||||
globalLoadingText.value = '正在获取中...'
|
||||
const selectedRows = data.value.filter(item => selectedRowKeys.value.includes(item.id))
|
||||
const transcriptions = await getVoiceText(selectedRows)
|
||||
|
||||
// 2. 收集所有转写内容
|
||||
|
||||
const allTexts = []
|
||||
for (const id of selectedRowKeys.value) {
|
||||
const row = data.value.find(item => item.id === id)
|
||||
if (row && row.audio_url) {
|
||||
if (row?.audio_url) {
|
||||
const transcription = transcriptions.find(item => item.audio_url === row.audio_url)
|
||||
if (transcription && transcription.value && transcription.value.trim()) {
|
||||
if (transcription?.value?.trim()) {
|
||||
allTexts.push({ id: row.id, url: row.audio_url, text: transcription.value })
|
||||
row.transcriptions = transcription.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 检查是否有可用的语音内容
|
||||
if (allTexts.length === 0) {
|
||||
if (!allTexts.length) {
|
||||
message.warning('未提取到任何语音内容,请检查音频文件或稍后重试')
|
||||
batchAnalyzeLoading.value = false
|
||||
globalLoading.value = false
|
||||
@@ -130,8 +113,7 @@ export function useBenchmarkAnalysis(data, expandedRowKeys, saveTableDataToSessi
|
||||
|
||||
await saveTableDataToSession()
|
||||
const mergedText = allTexts.map(item => item.text).join('\n\n---\n\n')
|
||||
|
||||
// 4. 通知父组件打开弹窗并开始生成
|
||||
|
||||
if (onBatchComplete) {
|
||||
await onBatchComplete(mergedText, allTexts.length)
|
||||
}
|
||||
|
||||
@@ -9,12 +9,8 @@ export function useBenchmarkData() {
|
||||
const selectedRowKeys = ref([])
|
||||
const expandedRowKeys = ref([])
|
||||
|
||||
/**
|
||||
* 保存表格数据到 session
|
||||
*/
|
||||
async function saveTableDataToSession() {
|
||||
try {
|
||||
// 过滤掉不需要持久化的临时字段(如 _analyzing)
|
||||
const persistData = (data.value || []).map((item) => {
|
||||
const rest = { ...item }
|
||||
delete rest._analyzing
|
||||
@@ -26,14 +22,10 @@ export function useBenchmarkData() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 session 加载表格数据
|
||||
*/
|
||||
async function loadTableDataFromSession() {
|
||||
try {
|
||||
const savedData = await storage.getJSON(TABLE_DATA_STORAGE_KEY)
|
||||
if (savedData && Array.isArray(savedData) && savedData.length > 0) {
|
||||
// 强制恢复临时字段的初始状态
|
||||
if (savedData?.length) {
|
||||
data.value = savedData.map((item) => ({ ...item, _analyzing: false }))
|
||||
console.log('从session加载了表格数据:', savedData.length, '条')
|
||||
}
|
||||
@@ -42,9 +34,6 @@ export function useBenchmarkData() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 API 响应数据
|
||||
*/
|
||||
function processApiResponse(resp, platform) {
|
||||
if (platform === '抖音') {
|
||||
const awemeList = resp?.data?.aweme_list || []
|
||||
@@ -57,9 +46,6 @@ export function useBenchmarkData() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空数据
|
||||
*/
|
||||
async function clearData() {
|
||||
data.value = []
|
||||
selectedRowKeys.value = []
|
||||
|
||||
Reference in New Issue
Block a user