diff --git a/frontend/app/web-gold/src/views/trends/Forecast.vue b/frontend/app/web-gold/src/views/trends/Forecast.vue index 645b7189fa..aa1fa230ac 100644 --- a/frontend/app/web-gold/src/views/trends/Forecast.vue +++ b/frontend/app/web-gold/src/views/trends/Forecast.vue @@ -4,7 +4,6 @@ import { message } from 'ant-design-vue' import { CommonService } from '@/api/common' import { UserPromptApi } from '@/api/userPrompt' import { useUserStore } from '@/stores/user' -import GradientButton from '@/components/GradientButton.vue' import PromptSelector from '@/components/PromptSelector.vue' import { getVoiceText } from '@gold/hooks/web/useVoiceText' import TikhubService, { InterfaceType, MethodType, ParamType } from '@/api/tikhub' @@ -44,7 +43,6 @@ const topicDetails = reactive({ const allPrompts = ref([]) const loadingPrompts = ref(false) const promptSearchKeyword = ref('') -const DISPLAY_COUNT = 6 // 工具函数 const formatNumber = (num) => { @@ -52,7 +50,7 @@ const formatNumber = (num) => { return num >= 10000 ? `${(num / 10000).toFixed(1)}w` : num.toString() } -const truncateTitle = (title, maxLength = 30) => { +const truncateTitle = (title, maxLength = 28) => { if (!title) return '' return title.length <= maxLength ? title : `${title.substring(0, maxLength)}...` } @@ -86,7 +84,7 @@ async function loadUserPrompts() { pageSize: 100, status: 1 }) - + if (response?.data?.list) { allPrompts.value = response.data.list } @@ -113,26 +111,25 @@ async function analyzeVoice(audioUrl) { console.warn('音频链接为空,无法分析') return } - + isAnalyzing.value = true try { - message.info('正在分析语音链接,提取文案...') + message.info('正在分析语音...') const transcriptions = await getVoiceText([{ audio_url: audioUrl }]) const transcript = transcriptions?.[0]?.value?.trim() || '' - + if (transcript) { const hasExistingContent = topicDetails.copywriting?.trim() - topicDetails.copywriting = hasExistingContent + topicDetails.copywriting = hasExistingContent ? `${topicDetails.copywriting}\n\n${transcript}` : transcript - message.success(`语音分析完成,已提取 ${transcript.length} 字文案内容`) + message.success('语音分析完成') } else { - console.warn('转写结果为空:', transcriptions) - message.warning('未从语音链接获取到可用的文案内容,请检查音频链接是否有效') + message.warning('未获取到可用文案') } } catch (error) { console.error('分析语音失败:', error) - message.error(`分析语音失败: ${error?.message || '请稍后重试'}`) + message.error('分析语音失败') } finally { isAnalyzing.value = false } @@ -142,19 +139,17 @@ async function analyzeVoice(audioUrl) { async function handleCreate(topic) { selectedTopic.value = topic.id topicDetails.title = topic.title - - // 自动选中第一个提示词(如果还没有选中) + if (!topicDetails.stylePromptId && allPrompts.value.length > 0) { const firstPrompt = allPrompts.value[0] topicDetails.stylePromptId = firstPrompt.id topicDetails.stylePrompt = firstPrompt.content || '' } - - // 自动分析语音 + if (topic.audioUrl?.trim()) { await analyzeVoice(topic.audioUrl) } else { - message.info('该视频暂无音频链接,无法自动提取语音文案') + message.info('该视频暂无音频') } } @@ -164,52 +159,51 @@ async function handleGenerate() { message.warning('请输入文案内容') return } - + if (!topicDetails.stylePrompt?.trim()) { message.warning('请先选择文案风格') return } - + isGenerating.value = true generatedContent.value = '' - + try { const requestData = { audio_prompt: topicDetails.stylePrompt, user_text: topicDetails.copywriting.trim(), amplitude: 50 } - + const ctrl = new AbortController() let fullText = '' let errorOccurred = false let isResolved = false - + await new Promise((resolve, reject) => { let timeout = null - + const cleanup = () => { if (timeout) { clearTimeout(timeout) timeout = null } } - + timeout = setTimeout(() => { if (!isResolved) { cleanup() ctrl.abort() - reject(new Error('请求超时,请稍后重试')) + reject(new Error('请求超时')) } }, 180000) - + CommonService.callWorkflowStream({ data: requestData, ctrl, onMessage: (event) => { if (errorOccurred || !event?.data) return - - // 处理特殊标记 + const dataStr = event.data.trim() if (dataStr === '[DONE]') { cleanup() @@ -219,7 +213,7 @@ async function handleGenerate() { } return } - + if (dataStr.startsWith('[TIMEOUT]')) { cleanup() if (!isResolved) { @@ -229,7 +223,7 @@ async function handleGenerate() { } return } - + try { const obj = JSON.parse(event.data) const piece = obj?.text || obj?.content || obj?.data || '' @@ -238,12 +232,9 @@ async function handleGenerate() { generatedContent.value = fullText } } catch (e) { - // 如果不是JSON,可能是纯文本 if (event.data && !event.data.startsWith('[')) { fullText += event.data generatedContent.value = fullText - } else { - console.warn('解析流数据异常:', e) } } }, @@ -253,12 +244,7 @@ async function handleGenerate() { errorOccurred = true isResolved = true ctrl.abort() - - // 尝试解析错误中的状态码和业务码 - const status = err?.response?.status - const data = err?.response?.data const errorMsg = err?.message || '网络请求失败' - console.error('SSE请求错误:', err) message.error(errorMsg) reject(new Error(errorMsg)) } @@ -272,12 +258,12 @@ async function handleGenerate() { } }) }) - + generatedContent.value = fullText.trim() message.success('文案生成成功') } catch (error) { console.error('生成文案失败:', error) - message.error('生成文案失败,请重试') + message.error('生成文案失败') } finally { isGenerating.value = false } @@ -287,7 +273,6 @@ async function handleGenerate() { function extractAudioUrl(video) { const urlList = video?.play_addr?.url_list if (Array.isArray(urlList) && urlList.length > 0) { - // 优先使用最后一个,如果为空则使用第一个 const lastUrl = urlList[urlList.length - 1] const firstUrl = urlList[0] return (lastUrl && lastUrl.trim()) || (firstUrl && firstUrl.trim()) || '' @@ -296,10 +281,10 @@ function extractAudioUrl(video) { } function extractCover(video) { - return video?.origin_cover?.url_list?.[0] - || video?.cover?.url_list?.[0] - || video?.dynamic_cover?.url_list?.[0] - || video?.animated_cover?.url_list?.[0] + return video?.origin_cover?.url_list?.[0] + || video?.cover?.url_list?.[0] + || video?.dynamic_cover?.url_list?.[0] + || video?.animated_cover?.url_list?.[0] || '' } @@ -307,7 +292,7 @@ function processSearchResults(response, startId = 1) { try { currentCursor.value = response?.data?.cursor || null const dataList = response?.data?.data || [] - + return dataList .map(el => el.aweme_info) .filter(Boolean) @@ -319,8 +304,8 @@ function processSearchResults(response, startId = 1) { audioUrl: extractAudioUrl(item.video), cover: extractCover(item.video) || item?.cover?.url_list?.[0] || '', author: item.author?.nickname || item.author?.unique_id || '未知', - authorAvatar: item.author?.avatar_thumb?.url_list?.[0] - || item.author?.avatar_larger?.url_list?.[0] + authorAvatar: item.author?.avatar_thumb?.url_list?.[0] + || item.author?.avatar_larger?.url_list?.[0] || '', playCount: item.statistics?.play_count || 0, diggCount: item.statistics?.digg_count || 0, @@ -342,7 +327,6 @@ async function handleSearch() { return } - // 重置状态 searchParams.offset = 0 searchParams.keyword = keyword currentCursor.value = null @@ -351,7 +335,7 @@ async function handleSearch() { try { const urlParams = { - keyword:searchParams.keyword, + keyword: searchParams.keyword, offset: String(searchParams.offset), sort_type: String(searchParams.sort_type), publish_time: String(searchParams.publish_time), @@ -377,7 +361,7 @@ async function handleSearch() { message.success(`找到 ${searchResults.length} 个结果`) } catch (error) { console.error('搜索失败:', error) - message.error(error?.message || '搜索失败,请稍后重试') + message.error(error?.message || '搜索失败') hotTopics.value = [] } finally { isLoading.value = false @@ -397,796 +381,861 @@ onMounted(async () => { -