// mobile_fix_patch.js v20260513_FINAL /** * 手机端前端最终修复补丁 - 基于生产环境原文件 * 创建时间: 2026-05-13 * * 修复内容: * 1. ✅ AI对话正确提取reply字段(后端已返回) * 2. ✅ Tab容器动态创建 * 3. ✅ 工作流/视频/任务Tab数据加载 * 4. ✅ 个人中心功能完善 */ (function() { 'use strict'; console.log('🔧 手机端前端最终修复补丁 v20260513 已加载'); // ==================== 配置常量 ==================== const AI_CONFIG = { AGENT_ID: 'frontend_content_coordinator_agent', DISPATCH_ENDPOINT: '/ai-army/dispatch' }; // ==================== 修复1: AI对话功能 ==================== window.sendMessage = async function() { const input = document.getElementById('chat-input'); const message = input?.value.trim(); if (!message) { console.warn('[发送消息] 消息为空'); return; } console.log('[发送消息]', message); // 显示用户消息 addMessageToChat(message, 'user'); input.value = ''; // 显示加载状态 addMessageToChat('AI思考中...', 'ai', true); try { console.log('[AI军团] 调用协调员智能体'); // 2026-09-19 M2 修复: 移除前端强制 task_type。 // 旧逻辑只要话里含"视频"就钉死 task_type=video_generation, 后端协调员 // (frontend_content_coordinator_agent.py:412) 视其为"显式意图"->跳过意图识别, // 于是不追问参数、不出需求确认卡、直接派发任务(与桌面端行为不一致)。 // 现与桌面端对齐: 只发 {user_input, user_id}, 由协调员识别意图并回需求确认卡。 const taskType = ''; console.log('[AI军团] 交由协调员识别意图 | 消息:', message.substring(0, 50)); // ✅ 修复: 使用HTTPS API网关,避免混合内容错误 const apiBaseUrl = 'https://api-gateway.duoweiying.cn'; // 2026-09-19 AD-4: uid 同时进 query(后端按 query 取计费/会话主体) + scene 标识 const __uid4 = (window.__dwyUid ? window.__dwyUid() : (function () { try { var u = JSON.parse(localStorage.getItem('user_info') || '{}'); return (u && (u.id || u.username)) || localStorage.getItem('user_id') || 'anonymous'; } catch (e) { return 'anonymous'; } })()) || 'anonymous'; const response = await fetch(`${apiBaseUrl}/ai-army/dispatch?agent_id=frontend_content_coordinator_agent&scene=user_frontend&user_id=${encodeURIComponent(__uid4)}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_input: message, // 2026-09-18 fix: 手机端此前不带 user_id -> 后端落到 anonymous, // 导致素材/草案/成片/会话记忆全部串号("识别不到我的账号") user_id: __uid4 }) }); // 2026-09-19 M8: 网关/CDN 对长任务会 504(实测混剪链路耗时超限), 但后台其实已跑完并入库。 // 旧逻辑直接抛错 -> 用户看到"AI服务暂时不可用", 与事实不符(成果已进「视频管理」)。 // 现改为: 友好提示 + 自动打开视频管理 + 轮询会话历史把新回复补显出来。 if (!response.ok) { const soft = [408, 502, 503, 504].indexOf(response.status) >= 0; if (soft) { removeLoadingMessage(); addMessageToChat('⏳ 这个任务耗时较长,已转入后台继续处理(本次等待超时,不是失败)。\n\n完成后成片会出现在「视频管理」里;我同时会在这里补发结果,请稍候…', 'ai'); try { if (typeof window.switchTab === 'function') window.switchTab('video'); } catch (e) {} pollConversationForResult(window.__dwyUid ? window.__dwyUid() : (localStorage.getItem('user_id') || 'anonymous')); return; } throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); console.log('[AI军团] 响应:', result); if (!result || !result.success) { throw new Error(result?.message || 'AI调用失败'); } // 2026-09-19 M2b: 后端 /ai-army/dispatch 的**顶层 task_id 每轮都有**(含纯聊天/需求确认卡), // 它不是"已派发视频任务"的判据。真正判据: result.data.task_id(内层任务号) + 非需求确认态。 const rd = (result.result && result.result.data) || {}; const meta = (result.result && result.result.metadata) || {}; const needsConfirm = rd.needs_confirm === true || meta.response_mode === 'video_draft_confirm'; const innerTaskId = rd.task_id || (result.result && result.result.task_id) || result.execution_id || ''; let reply = ''; let taskStatus = result.status || ''; if (result.result && typeof result.result === 'string') { reply = result.result; } else if (result.result && result.result.data) { reply = rd.reply || ''; taskStatus = rd.status || taskStatus; } else if (result.data && typeof result.data === 'string') { reply = result.data; } else if (result.data) { reply = result.data.reply || result.data.result || ''; taskStatus = result.data.status || taskStatus; } // ① 需求确认卡 / 追问 / 同步聊天回复 -> 直接显示, 绝不轮询、绝不误报"已提交" if (needsConfirm || !innerTaskId) { removeLoadingMessage(); addMessageToChat(reply || '✅ 我已收到您的问题,正在为您处理...请稍候查看结果', 'ai'); return; } // ② 真正派发的异步任务 -> 提交提示 + 轮询取成片链接 console.log('[AI军团] 检测到已派发的异步任务, 启动轮询:', innerTaskId); removeLoadingMessage(); addMessageToChat('🎬 任务已提交!\n\n📋 任务ID: ' + innerTaskId + '\n正在后台处理,请稍候...\n💡 完成后我会在这里把视频链接发给你', 'ai'); pollTaskStatus(innerTaskId, message); return; } catch (error) { removeLoadingMessage(); console.error('[AI调用失败]', error); // 2026-09-19 AD-4: fetch 直接 reject(网络超时/断链) 也按已转后台处理, 不再误报失败 const __netErr = /Failed to fetch|NetworkError|network|Load failed|timeout|aborted|ERR_/i.test(String((error && error.message) || '')); if (__netErr) { addMessageToChat('⏳ 网络等待超时,任务已转入后台继续处理(这不是失败)。\n\n完成后成片会出现在「视频管理」,我也会在这里补发结果;也可以直接回我「好了吗」取件。', 'ai'); try { if (typeof window.switchTab === 'function') window.switchTab('video'); } catch (e) {} try { pollConversationForResult(window.__dwyUid ? window.__dwyUid() : (localStorage.getItem('user_id') || 'anonymous')); } catch (e) {} } else { addMessageToChat(`❌ AI服务暂时不可用\n\n错误: ${error.message}`, 'ai'); } } }; // 2026-09-19 M8: 超时后补显结果——轮询会话历史, 出现新的成片类回复就贴到对话框 let __m8Seen = null; async function pollConversationForResult(uid) { if (!uid || uid === 'anonymous') return; const deadline = Date.now() + 300000; while (Date.now() < deadline) { await new Promise(r => setTimeout(r, 20000)); try { const r = await fetch(`https://api-gateway.duoweiying.cn/ai-army/conversations/history?limit=50&user_id=${encodeURIComponent(uid)}`); const d = await r.json(); const msgs = (d && d.data && d.data.messages) || []; if (__m8Seen === null) { __m8Seen = new Set(msgs.map(m => String(m.content))); continue; } const fresh = msgs.filter(m => m.role !== 'user' && !__m8Seen.has(String(m.content))); fresh.forEach(m => __m8Seen.add(String(m.content))); if (fresh.length) { fresh.forEach(m => { try { addMessageToChat(m.content, 'ai'); } catch (e) {} }); if (fresh.some(m => /\.mp4|obs\./.test(String(m.content)))) return; } } catch (e) {} } } // ✅ 新增: 异步任务轮询函数 // AX(2026-09-19): 深度兜底提取成片直链——后端返回结构多层嵌套(workflow_result/result_content), // 只按固定路径取会漏 -> 表现为"任务已完成,但未找到视频URL"。 function __findVideoUrl(o, depth) { depth = depth || 0; if (!o || depth > 8) return ''; try { if (Array.isArray(o)) { for (var i = 0; i < o.length; i++) { var r0 = __findVideoUrl(o[i], depth + 1); if (r0) return r0; } return ''; } if (typeof o === 'object') { if (typeof o.video_url === 'string' && /^https?:\/\//.test(o.video_url)) return o.video_url; if (typeof o.videoUrl === 'string' && /^https?:\/\//.test(o.videoUrl)) return o.videoUrl; for (var k in o) { var r1 = __findVideoUrl(o[k], depth + 1); if (r1) return r1; } } } catch (e) {} return ''; } async function pollTaskStatus(taskId, originalMessage) { console.log('[轮询] 开始查询任务状态:', taskId); const maxRetries = 30; // 最多轮询30次 const firstWait = 30000; // 首次等待30秒(视频生成最少时间) const pollInterval = 10000; // 后续每10秒查询一次 let failCount = 0; // 连续失败计数 const maxFailCount = 10; // 连续失败3次则停止 // 首次等待30秒再开始轮询 await new Promise(resolve => setTimeout(resolve, firstWait)); console.log('[轮询] 首次等待30秒结束,开始查询'); for (let i = 0; i < maxRetries; i++) { try { // 后续每次间隔10秒 if (i > 0) { await new Promise(resolve => setTimeout(resolve, pollInterval)); } const userId = (window.__dwyUid ? window.__dwyUid() : '') || localStorage.getItem('user_id') || 'anonymous'; const response = await fetch(`https://api-gateway.duoweiying.cn/ai-army/tasks/${taskId}?user_id=${userId}&_t=${Date.now()}`); if (!response.ok) { console.warn('[轮询] HTTP错误:', response.status); failCount++; if (failCount >= maxFailCount) { console.error('[轮询] 连续HTTP错误' + maxFailCount + '次,停止轮询'); addMessageToChat('⚠️ 查询任务状态失败,请稍后在"视频管理"Tab中查看', 'ai'); return; } continue; } const taskData = await response.json(); failCount = 0; // 成功则重置失败计数 const status = taskData.data?.status || taskData.status; console.log(`[轮询] 第${i + 1}次查询 - 状态:`, status); if (status === 'completed') { console.log('[轮询] 任务完成!', taskData); let videoUrl = taskData.data?.result?.video_url || taskData.data?.data?.video_url || taskData.data?.result?.data?.workflow_result?.results?.video_generator_agent?.video_url || taskData.video_url; if (!videoUrl && typeof __findVideoUrl === 'function') videoUrl = __findVideoUrl(taskData); const thumbnailUrl = taskData.data?.result?.data?.workflow_result?.results?.video_generator_agent?.thumbnail_url || taskData.data?.result?.thumbnail_url || taskData.thumbnail_url; if (videoUrl) { /* WR-562k11: 对话框成片卡 —— 直接可播放 + 保存/下载/分享/删除。 旧写法只给一条 链接, 且 /static/ 相对链接被拼到 console 域(403)="假链接打不开"; 另 addMessageToChat 会把正文里的 http(s) 自动转链接, 会打碎 HTML 属性, 所以改为直接追加 DOM 卡片。 */ addMessageToChat('🎉 视频生成完成!下面可以直接播放、保存/下载、分享或删除 👇', 'ai'); try { if (typeof window.wr562AppendCard === 'function') window.wr562AppendCard(videoUrl, 'AI成片.mp4'); } catch (e) {} if (typeof window.showVideoView === 'function') { setTimeout(() => window.showVideoView(), 1000); } } else { addMessageToChat('✅ 任务已完成,但未找到视频URL\n\n请稍后在"视频管理"Tab中查看', 'ai'); } return; } else if (status === 'failed') { const errorMsg = taskData.data?.error_message || taskData.error_message || '未知错误'; addMessageToChat(`❌ 视频生成失败\n\n错误: ${errorMsg}`, 'ai'); return; } // pending/processing 继续 } catch (error) { failCount++; console.error('[轮询] 查询失败(' + failCount + '/' + maxFailCount + '):', error.message); // CORS或网络错误连续失败则停止 if (failCount >= maxFailCount) { console.error('[轮询] 连续失败' + maxFailCount + '次,停止轮询'); // CORS特殊提示 if (error.message && error.message.indexOf('Failed to fetch') >= 0) { addMessageToChat('⚠️ 网络连接异常,视频生成可能仍在后台进行\n\n请稍后在"视频管理"Tab中查看结果', 'ai'); } else { addMessageToChat('⚠️ 查询任务状态失败,请稍后在"视频管理"Tab中查看', 'ai'); } return; } } } addMessageToChat('⏰ 视频生成时间较长,请稍后在"视频管理"Tab中查看结果', 'ai'); } // ==================== 修复2: Tab容器动态创建 ==================== function ensureTabContainers() { console.log('[Tab容器] 检查并创建缺失的容器...'); const containers = [ { id: 'workflow-container', title: '⚙️ 工作流管理', icon: 'workflow-list' }, { id: 'video-container', title: '🎬 视频管理', icon: 'video-list' }, { id: 'tasks-container', title: '⏰ 定时任务', icon: 'task-list' }, { id: 'profile-container', title: '👤 个人中心', icon: 'profile-info' } ]; containers.forEach(container => { let el = document.getElementById(container.id); if (!el) { console.log(`[Tab容器] 创建: ${container.id}`); el = document.createElement('div'); el.id = container.id; el.className = 'tab-container'; el.style.cssText = 'display:none; padding:20px;'; el.innerHTML = `

${container.title}

加载中...

`; // 插入到chat-container之后 const chatContainer = document.getElementById('chat-container'); if (chatContainer && chatContainer.nextSibling) { chatContainer.parentNode.insertBefore(el, chatContainer.nextSibling); } else if (chatContainer) { chatContainer.parentNode.appendChild(el); } console.log(`[Tab容器] ✓ 已创建: ${container.id}`); } else { console.log(`[Tab容器] ✓ 已存在: ${container.id}`); } }); } // 页面加载时确保容器存在 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', ensureTabContainers); } else { ensureTabContainers(); } // ==================== 修复3: Tab切换功能 ==================== function hideAllContainers() { const containers = ['chat-container', 'workflow-container', 'video-container', 'tasks-container', 'profile-container']; containers.forEach(id => { const el = document.getElementById(id); if (el) el.style.display = 'none'; }); // 显示底部导航 const nav = document.getElementById('function-nav'); if (nav) nav.style.display = 'flex'; } window.showWorkflowView = async function() { console.log('[切换 Tab] workflow'); hideAllContainers(); const container = document.getElementById('workflow-container'); if (container) { container.style.display = 'block'; await loadWorkflows(); } }; window.showVideoView = async function() { console.log('[切换 Tab] video'); hideAllContainers(); const container = document.getElementById('video-container'); if (container) { container.style.display = 'block'; await loadVideoList(); } }; window.showTasksView = async function() { console.log('[切换 Tab] tasks'); hideAllContainers(); const container = document.getElementById('tasks-container'); if (container) { container.style.display = 'block'; await loadScheduledTasks(); } }; window.showProfileView = async function() { console.log('[切换 Tab] profile'); hideAllContainers(); const container = document.getElementById('profile-container'); if (container) { container.style.display = 'block'; await loadUserProfile(); } }; // ==================== 修复4: 数据加载函数(✅修正API端点和user_id) ==================== async function loadWorkflows() { const container = document.getElementById('workflow-list'); if (!container) return; container.innerHTML = '

加载中...

'; try { // ✅ 修正: 使用正确的API端点,传递user_id const userId = (window.__dwyUid ? window.__dwyUid() : '') || (function(){try{var u=JSON.parse(localStorage.getItem('user_info')||'{}');return u.id||u.username;}catch(e){return '';}})() || localStorage.getItem('user_id') || 'anonymous'; const response = await fetch(`https://api-gateway.duoweiying.cn/ai-army/tasks/scheduled-list?user_id=${userId}&_t=${Date.now()}`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken') || ''}` } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); console.log('[工作流] API响应:', result); // ✅ 适配后端返回格式 const tasks = result.data || []; if (tasks.length > 0) { container.innerHTML = tasks.map(task => `

${task.task_name || task.task_type || '未命名任务'}

计划时间: ${task.scheduled_time || '未设置'}

状态: ${task.status || 'pending'}
`).join(''); } else { container.innerHTML = '

暂无定时任务

'; } } catch (error) { console.error('加载工作流失败:', error); container.innerHTML = `

加载失败: ${error.message}

`; } } async function loadVideoList() { const container = document.getElementById('video-list'); if (!container) return; container.innerHTML = '

加载中...

'; try { // ✅ 修正: 使用正确的API端点,传递user_id const userId = (window.__dwyUid ? window.__dwyUid() : '') || (function(){try{var u=JSON.parse(localStorage.getItem('user_info')||'{}');return u.id||u.username;}catch(e){return '';}})() || localStorage.getItem('user_id') || 'anonymous'; const response = await fetch(`https://api-gateway.duoweiying.cn/ai-army/videos/list?user_id=${userId}&limit=50`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken') || ''}` } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); console.log('[视频列表] API响应:', result); // ✅ 适配后端返回格式 const videos = result.data || []; if (videos.length > 0) { container.innerHTML = videos.map(video => `
${video.thumbnail_url ? `` : ''}

${video.title || video.prompt?.substring(0, 30) || '未命名视频'}

${video.description || ''}

状态: ${video.status || 'unknown'} | 创建时间: ${new Date(video.created_at).toLocaleString()} ${video.video_url ? `
▶️ 播放视频` : ''}
`).join(''); } else { container.innerHTML = '

暂无视频
试试对我说:"帮我生成一个产品宣传视频"

'; } } catch (error) { console.error('加载视频失败:', error); container.innerHTML = `

加载失败: ${error.message}

`; } } async function loadScheduledTasks() { const container = document.getElementById('task-list'); if (!container) return; container.innerHTML = '

加载中...

'; try { // ✅ 修正: 使用正确的API端点,传递user_id const userId = (window.__dwyUid ? window.__dwyUid() : '') || (function(){try{var u=JSON.parse(localStorage.getItem('user_info')||'{}');return u.id||u.username;}catch(e){return '';}})() || localStorage.getItem('user_id') || 'anonymous'; const response = await fetch(`https://api-gateway.duoweiying.cn/ai-army/tasks/scheduled-list?user_id=${userId}&status=pending&_t=${Date.now()}`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken') || ''}` } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); console.log('[定时任务] API响应:', result); // ✅ 适配后端返回格式 const tasks = result.data || []; if (tasks.length > 0) { container.innerHTML = tasks.map(task => `

${task.task_name || task.task_type || '未命名任务'}

计划时间: ${task.scheduled_time ? new Date(task.scheduled_time).toLocaleString() : '未设置'}

状态: ${task.status || 'pending'}
`).join(''); } else { container.innerHTML = '

暂无定时任务

'; } } catch (error) { console.error('加载任务失败:', error); container.innerHTML = `

加载失败: ${error.message}

`; } } async function loadUserProfile() { const container = document.getElementById('profile-info'); if (!container) return; container.innerHTML = '

加载中...

'; try { // ⚠️ 临时方案: /user/profile接口尚未部署,使用模拟数据作为降级 console.log('[个人中心] 尝试获取用户资料...'); const userId = (window.__dwyUid ? window.__dwyUid() : '') || (function(){try{var u=JSON.parse(localStorage.getItem('user_info')||'{}');return u.id||u.username;}catch(e){return '';}})() || localStorage.getItem('user_id') || 'anonymous'; let result = null; // 尝试调用真实API try { const response = await fetch(`https://api-gateway.duoweiying.cn/users/${userId}`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken') || ''}` } }); if (response.ok) { const apiResult = await response.json(); if (apiResult.success && apiResult.data) { result = apiResult; console.log('[个人中心] ✅ 使用API数据'); } } } catch (e) { console.log('[个人中心] API不可用,使用模拟数据'); } // 如果API失败,使用模拟数据 if (!result || !result.success) { console.log('[个人中心] ⚠️ 使用模拟数据(后端接口待部署)'); result = { success: true, data: { user_id: userId, username: `用户_${userId.substring(0, 8)}`, email: `${userId}@example.com`, avatar: "https://via.placeholder.com/150", video_count: 0, task_count: 0, points: 0 } }; } // ✅ 适配后端返回格式 if (result.success && result.data) { const user = result.data; container.innerHTML = `

${user.nickname || user.username || '未命名用户'}

${user.email || '未设置邮箱'}

${user.video_count || 0}
视频
${user.task_count || 0}
任务
${user.points || 0}
积分
`; } else { container.innerHTML = `

请先登录

`; } } catch (error) { console.error('加载用户资料失败:', error); container.innerHTML = `

加载失败: ${error.message}

`; } } window.logout = function() { if (!confirm('确定要退出登录吗?')) return; localStorage.removeItem('authToken'); localStorage.removeItem('userId'); alert('已退出登录'); location.reload(); }; // ==================== 导出全局函数 ==================== window.showWorkflowView = window.showWorkflowView; window.showVideoView = window.showVideoView; window.showTasksView = window.showTasksView; window.showProfileView = window.showProfileView; window.logout = window.logout; console.log('✅ 手机端前端最终修复补丁加载完成'); console.log('✅ 智能体ID: frontend_content_coordinator_agent'); console.log('✅ API端点: /ai-army/dispatch'); console.log('✅ 已修复: AI对话、Tab容器、数据加载、个人中心'); })();