176:function displayWriteResult(result,autoExpanded){
220:async function confirmDraft(){if(!currentTaskId)return;addLog('info','确认草稿，生成场景图和参考图...');try{var r=await fetch(API_BASE+'/tasks/'+currentTaskId+'/confirm-draft',{method:'POST'});var d=await r.json();if(d.success){addLog('success','场景图和参考图生成完成');if(d.result){window._wr=d.result;displayWriteResult(d.result);}}else addLog('error','失败: '+(d.detail||d.message||'未知'));}catch(e){addLog('error','失败: '+e.message);}}
223:async function batchGenerateSceneImages(){if(!currentTaskId||!window._wr)return;const scenes=window._wr.scenes;if(!scenes||!scenes.length){addLog('warning','没有场景数据');return;}const missing=scenes.filter(s=>!s.image_path);if(!missing.length){addLog('info','所有场景图已生成');return;}addLog('info',`开始批量生成${missing.length}个场景图...`);for(let i=0;i<scenes.length;i++){if(!scenes[i].image_path){addLog('info',`生成场景${i+1}图: ${scenes[i].location||''}...`);try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'scene',index:i})});const d=await r.json();if(d.success&&d.result){window._wr=d.result;displayWriteResult(d.result,true);addLog('success',`场景${i+1}图生成成功`);}else addLog('error',`场景${i+1}图失败: ${d.detail||d.message||'未知'}`);}catch(e){addLog('error',`场景${i+1}图失败: ${e.message}`);}}}addLog('success','批量场景图生成完成');}
228:try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'shot',scene_index:sceneIdx,shot_index:shotIdx,prompt:p})});const d=await r.json();if(d.success&&d.result){window._wr=d.result;displayWriteResult(d.result,true);addLog('success','镜头'+sh.shot_id+'参考图生成成功');}else{addLog('error','镜头'+sh.shot_id+'参考图失败: '+(d.detail||d.message||'未知'));displayWriteResult(window._wr,true);}}catch(e){addLog('error','镜头'+sh.shot_id+'参考图失败: '+e.message);displayWriteResult(window._wr,true);}}
229:async function batchGenerateShotRefImages(regenerateAll=false){if(!currentTaskId||!window._wr)return;const scenes=window._wr.scenes;if(!scenes||!scenes.length){addLog('warning','没有场景数据');return;}if(regenerateAll){addLog('info','🔄 重新生成全部: 先生成背景图→再生成镜头参考图...');/* 1. 重新生成所有背景图 */for(let si=0;si<scenes.length;si++){addLog('info',`生成场景${si+1}背景图: ${scenes[si].location||''}...`);try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'scene',index:si})});const d=await r.json();if(d.success&&d.result){window._wr=d.result;displayWriteResult(d.result,true);addLog('success',`场景${si+1}背景图生成成功`);}else addLog('error',`场景${si+1}背景图失败: ${d.detail||d.message||'未知'}`);}catch(e){addLog('error',`场景${si+1}背景图失败: ${e.message}`);}}/* 2. 重新生成所有镜头参考图 */addLog('info','开始生成镜头参考图...');for(let si=0;si<window._wr.scenes.length;si++){const sc=window._wr.scenes[si];if(!sc.shots)continue;for(let shi=0;shi<sc.shots.length;shi++){addLog('info',`生成镜头${sc.shots[shi].shot_id}参考图...`);try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'shot',scene_index:si,shot_index:shi})});const d=await r.json();if(d.success&&d.result){window._wr=d.result;displayWriteResult(d.result,true);addLog('success',`镜头${sc.shots[shi].shot_id}参考图生成成功`);}else addLog('error',`镜头${sc.shots[shi].shot_id}失败: ${d.detail||d.message||'未知'}`);}catch(e){addLog('error',`镜头${sc.shots[shi].shot_id}失败: ${e.message}`);}}}addLog('success','✅ 全部重新生成完成');}else{/* 继续生成: 只生成缺失的 */let total=0,done=0;for(const sc of scenes){if(sc.shots)for(const sh of sc.shots){if(!sh.image_path)total++;}}if(!total){addLog('info','所有镜头参考图已生成');return;}addLog('info',`继续生成${total}个缺失的镜头参考图...`);for(let si=0;si<scenes.length;si++){const sc=scenes[si];if(!sc.shots)continue;for(let shi=0;shi<sc.shots.length;shi++){if(!sc.shots[shi].image_path){addLog('info',`生成镜头${sc.shots[shi].shot_id}参考图...`);try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'shot',scene_index:si,shot_index:shi})});const d=await r.json();if(d.success&&d.result){window._wr=d.result;displayWriteResult(d.result,true);done++;addLog('success',`镜头${sc.shots[shi].shot_id}参考图生成成功`);}else addLog('error',`镜头${sc.shots[shi].shot_id}失败: ${d.detail||d.message||'未知'}`);}catch(e){addLog('error',`镜头${sc.shots[shi].shot_id}失败: ${e.message}`);}}}}addLog('success',`继续生成完成: ${done}/${total}`);}}
231:async function modifyScene(sceneIdx){if(!currentTaskId||!window._wr)return;var sc=window._wr.scenes[sceneIdx];if(!sc)return;var loc=sc.location||sc.description?.substring(0,30)||'场景'+sc.scene_id;var p=await showPromptEditor('修改场景描述: '+loc,'场景描述: '+loc);if(p===null)return;addLog('info','修改场景'+sceneIdx+'描述...');try{var r=await fetch(API_BASE+'/tasks/'+currentTaskId+'/modify-scene',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({scene_index:sceneIdx,modification:p})});var d=await r.json();if(d.success){addLog('success','\u573a\u666f\u4fee\u6539\u6210\u529f');if(d.result){window._wr=d.result;displayWriteResult(d.result);}}else addLog('error','\u5931\u8d25: '+(d.detail||d.message||'\u672a\u77e5'));}catch(e){addLog('error','\u5931\u8d25: '+e.message);}}
233:async function modifyShot(sceneIdx,shotIdx){if(!currentTaskId||!window._wr)return;var sc=window._wr.scenes[sceneIdx];if(!sc)return;var sh=sc.shots[shotIdx];if(!sh)return;var p=await showPromptEditor('\u4fee\u6539\u955c\u5934'+sh.shot_id+'\u7684\u63cf\u8ff0',sh.description||'');if(p===null)return;addLog('info','\u4fee\u6539\u955c\u5934'+sh.shot_id+'...');try{var r=await fetch(API_BASE+'/tasks/'+currentTaskId+'/modify-shot',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({scene_index:sceneIdx,shot_index:shotIdx,modification:p})});var d=await r.json();if(d.success){addLog('success','\u5206\u955c\u4fee\u6539\u6210\u529f');if(d.result){window._wr=d.result;displayWriteResult(d.result);}}else addLog('error','\u5931\u8d25: '+(d.detail||d.message||'\u672a\u77e5'));}catch(e){addLog('error','\u5931\u8d25: '+e.message);}}
235:async function regenerateShotImage(sceneIdx,shotIdx){if(!currentTaskId||!window._wr)return;var sc=window._wr.scenes[sceneIdx];if(!sc)return;var sh=sc.shots[shotIdx];if(!sh)return;var p=await showPromptEditor('\u4fee\u6539\u955c\u5934'+sh.shot_id+'\u7684\u53c2\u8003\u56fe\u63d0\u793a\u8bcd',sh.image_prompt||'');if(p===null)return;addLog('info','\u91cd\u65b0\u751f\u6210\u955c\u5934'+sh.shot_id+'\u7684\u53c2\u8003\u56fe...');try{var r=await fetch(API_BASE+'/tasks/'+currentTaskId+'/regenerate-image',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'shot_ref',scene_index:sceneIdx,shot_index:shotIdx,prompt:p})});var d=await r.json();if(d.success){addLog('success','\u955c\u5934\u53c2\u8003\u56fe\u91cd\u751f\u6210\u6210\u529f');if(d.result){window._wr=d.result;displayWriteResult(d.result);}}else addLog('error','\u5931\u8d25: '+(d.detail||d.message||'\u672a\u77e5'));}catch(e){addLog('error','\u5931\u8d25: '+e.message);}}
246:try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'character',index:idx,prompt:p})});const d=await r.json();if(d.success){addLog('success','角色图片重生成成功');if(d.result){window._wr=d.result;displayWriteResult(d.result);}}else addLog('error','失败: '+(d.detail||d.message||'未知'));}catch(e){addLog('error','失败: '+e.message);}}
248:async function batchRegenerateCharImages(){if(!currentTaskId||!window._wr)return;const chars=window._wr.characters;if(!chars||!chars.length){addLog('warning','没有角色数据');return;}addLog('info',`🔄 重新生成全部${chars.length}个角色图...`);for(let i=0;i<chars.length;i++){addLog('info',`生成角色图: ${chars[i].name}...`);try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'character',index:i})});const d=await r.json();if(d.success&&d.result){window._wr=d.result;displayWriteResult(d.result,true);addLog('success',`角色${chars[i].name}图生成成功`);}else addLog('error',`角色${chars[i].name}图失败: ${d.detail||d.message||'未知'}`);}catch(e){addLog('error',`角色${chars[i].name}图失败: ${e.message}`);}}addLog('success','✅ 全部角色图重新生成完成');}
253:try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'scene',index:idx,prompt:p})});const d=await r.json();if(d.success){addLog('success','场景背景图重生成成功');if(d.result){window._wr=d.result;displayWriteResult(d.result);}}else addLog('error','失败: '+(d.detail||d.message||'未知'));}catch(e){addLog('error','失败: '+e.message);}}
255:async function batchRegenerateSceneImages(){if(!currentTaskId||!window._wr)return;const scenes=window._wr.scenes;if(!scenes||!scenes.length){addLog('warning','没有场景数据');return;}addLog('info',`🔄 重新生成全部${scenes.length}个背景图...`);for(let i=0;i<scenes.length;i++){addLog('info',`生成场景${i+1}背景图: ${scenes[i].location||''}...`);try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/regenerate-image`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'scene',index:i})});const d=await r.json();if(d.success&&d.result){window._wr=d.result;displayWriteResult(d.result,true);addLog('success',`场景${i+1}背景图生成成功`);}else addLog('error',`场景${i+1}背景图失败: ${d.detail||d.message||'未知'}`);}catch(e){addLog('error',`场景${i+1}背景图失败: ${e.message}`);}}addLog('success','✅ 全部背景图重新生成完成');}
272:async function stitchVideos(){if(!currentTaskId)return;const btn=document.getElementById('btnStitchVideos');btn.disabled=true;btn.innerHTML=' 拼接中...';addLog('info','开始拼接视频...');try{const ctrl=new AbortController();const tid=setTimeout(()=>ctrl.abort(),180000);const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/stitch`,{method:'POST',signal:ctrl.signal});clearTimeout(tid);const d=await r.json();if(d.success){addLog('success','拼接成功！'+d.message);await viewStepResult('produce');}else addLog('error','拼接失败: '+d.message);}catch(e){addLog('error',e.name==='AbortError'?'拼接超时，请稍后重试':'拼接失败: '+e.message);}finally{btn.disabled=false;btn.innerHTML='拼接视频';}}
275:async function executeStep(step,btnId,lt,ot){if(!currentTaskId){addLog('error','请先创建或选择任务');return;}const btn=document.getElementById(btnId);if(!btn)return;btn.disabled=true;btn.innerHTML=' '+lt+'...';updateStepStatus(step,'working');addLog('info','开始'+getStepName(step)+'...');try{let u=`${API_BASE}/tasks/${currentTaskId}/steps/${step}`;if(step==='produce'){const vm=currentTaskData?.video_mode||document.getElementById('taskVideoMode')?.value||'img2vid';u+='?video_mode='+vm;}const ctrl=new AbortController();const timer=setTimeout(()=>ctrl.abort(),600000);const r=await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},signal:ctrl.signal});clearTimeout(timer);if(!r.ok)throw new Error('HTTP '+r.status+': '+(await r.text()).substring(0,200));const d=await r.json();if(d.success){updateStepStatus(step,'completed',false,d.can_modify,d.can_regenerate);const nx={collect:'write',write:'produce',produce:'review'}[step];if(nx)updateStepStatus(nx,'pending',true);if(d.result){const fn={collect:displayCollectionResult,write:displayWriteResult,produce:displayProduceResult,review:displayReviewResult}[step];if(fn)fn(d.result);}await refreshTaskStatus();addLog('success',getStepName(step)+'完成！'+(d.message||''));}else throw new Error(d.message||'失败');}catch(e){updateStepStatus(step,'pending',true);if(e.name==='AbortError')addLog('error',getStepName(step)+'超时(10分钟)，服务可能仍在后台执行');else addLog('error',getStepName(step)+'失败: '+e.message);}finally{btn.disabled=false;btn.innerHTML=ot;}}
276:async function viewStepResult(step){if(!currentTaskId)return;try{const r=await fetch(`${API_BASE}/tasks/${currentTaskId}/steps/${step}`);if(r.ok){const d=await r.json();if(d.success&&d.result){const fn={collect:displayCollectionResult,write:displayWriteResult,produce:displayProduceResult,review:displayReviewResult}[step];if(fn)fn(d.result);addLog('success',getStepName(step)+'结果已展示');}else addLog('warning',getStepName(step)+'暂无结果');}}catch(e){addLog('error','查看失败: '+e.message);}}
281:async function _doAutoRefresh(){if(!currentTaskId){stopAutoRefresh();return;}try{const r=await fetch(API_BASE+'/tasks/'+currentTaskId+'/status');if(!r.ok)return;const d=await r.json();if(!d.success){stopAutoRefresh();return;}currentTaskData=d.task;updateTaskInfo(d.task);const step=d.task.current_step;if(step==='complete'||step==='completed'||step==='reviewed'){stopAutoRefresh();return;}for(const s of['collect','write','produce','review']){try{const r2=await fetch(API_BASE+'/tasks/'+currentTaskId+'/steps/'+s);if(r2.ok){const d2=await r2.json();if(d2.success&&d2.result){const fn={collect:displayCollectionResult,write:displayWriteResult,produce:displayProduceResult,review:displayReviewResult}[s];if(fn)fn(d2.result,true);}}}catch(e){}}refreshTaskList();}catch(e){}}
286:async function switchTask(taskId){stopAutoRefresh();currentTaskId=taskId;document.getElementById('taskInfo').style.display='block';document.getElementById('currentTaskId').textContent=taskId;addLog('info','切换到任务: '+taskId);['collect','write','produce','review'].forEach(s=>{const c=document.getElementById(s+'ResultContent');if(c)c.innerHTML='';const b=document.getElementById(s+'Body');if(b)b.classList.remove('expanded');});const resp=await fetch(`${API_BASE}/tasks/${taskId}/status`);if(resp.ok){const data=await resp.json();if(data.success){currentTaskData=data.task;updateTaskInfo(data.task);/* v3.10: 回显任务配置到侧边配置面板 */const cfgMap={cfgDuration:'duration',cfgVideoMode:'video_mode',cfgImageModel:'image_model',cfgVideoModel:'video_model'};Object.entries(cfgMap).forEach(([elId,key])=>{const el=document.getElementById(elId);if(el&&data.task[key])el.value=data.task[key];});const chs=data.task.channels||[];document.querySelectorAll('.cfg-ch').forEach(c=>{c.checked=chs.includes(c.value);});const step=data.task.current_step;const steps=['collect','write','produce','review'];const isCompleted=step==='complete'||step==='completed'||step==='reviewed'||data.task.status==='completed'||data.task.progress>=100;const idx=isCompleted?steps.length:steps.indexOf(step);const done=steps.slice(0,idx);steps.forEach((s,i)=>{if(i<idx)updateStepStatus(s,'completed',true,true,true);else if(i===idx&&idx<steps.length)updateStepStatus(s,'pending',true);else if(!isCompleted)updateStepStatus(s,'pending',false);else updateStepStatus(s,'completed',true,true,true);});for(const s of done){try{const r=await fetch(`${API_BASE}/tasks/${taskId}/steps/${s}`);if(r.ok){const d=await r.json();if(d.success&&d.result){const fn={collect:displayCollectionResult,write:displayWriteResult,produce:displayProduceResult,review:displayReviewResult}[s];if(fn)fn(d.result,true);}}}catch(e){}}refreshTaskList();const isInProgress=!isCompleted&&step!=='complete'&&step!=='completed'&&step!=='reviewed'&&data.task.status!=='completed'&&data.task.progress<100;if(isInProgress)startAutoRefresh();}}}
330:function displayWritePhase1Result(result){
401:            displayWritePhase2Result(d.result);
416:        if(d.success&&d.result){displayWritePhase1Result(d.result);addLog('success','大纲重新生成完成');}