yonge 4 days ago
parent
commit
6f87216970

+ 1 - 1
dist/assets/index-BmSNSE_0.js → dist/assets/index-BaymfMqL.js

@@ -1 +1 @@
-import{u as s,ah as r}from"./index-BLanZz14.js";import{a5 as c,cD as n,R as p,ae as t,af as _,al as u,ag as i,bH as l}from"./index-DBM-UbfS.js";const m={class:"pptScreen"},d=c({__name:"pptScreen",setup(f){const a=n(),e=p(!0),o=s();return a.setScreenMode("pptScreen"),o.initPPTData().then(()=>{e.value=!1}),(S,k)=>(t(),_("div",m,[e.value?i("",!0):(t(),u(r,{key:0}))]))}}),P=l(d,[["__scopeId","data-v-678905e9"]]);export{P as default};
+import{u as s,ah as r}from"./index-CPfrsi3J.js";import{a5 as c,cD as n,R as p,ae as t,af as _,al as u,ag as i,bH as l}from"./index-CV9b2pRy.js";const m={class:"pptScreen"},d=c({__name:"pptScreen",setup(f){const a=n(),e=p(!0),o=s();return a.setScreenMode("pptScreen"),o.initPPTData().then(()=>{e.value=!1}),(S,k)=>(t(),_("div",m,[e.value?i("",!0):(t(),u(r,{key:0}))]))}}),P=l(d,[["__scopeId","data-v-678905e9"]]);export{P as default};

File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-CDik53et.css


File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-CNPrdphH.css


+ 1 - 1
dist/assets/index-BLanZz14.js → dist/assets/index-CPfrsi3J.js

@@ -1,4 +1,4 @@
-import{cH as pw,cI as gw,aG as mw,cJ as yw,cK as Sw,cL as bw,cM as xw,ah as Wt,cN as Cw,cO as ww,cP as Tw,cQ as Iw,aM as _w,aF as Aw,cR as Ew,aB as d1,bg as Pw,cS as Mw,cT as Rw,cU as Dw,cV as Lw,cW as kw,cX as Ow,cY as Bw,aE as Nw,cZ as Fw,s as Te,bl as $w,al as je,ag as Fe,af as he,ak as Z,c_ as zw,c$ as Uw,d0 as Hw,d1 as Vw,bk as Gw,cy as cl,aS as wv,aq as Ye,d2 as Ww,d3 as jw,a5 as ze,d4 as Kw,d5 as Xw,d6 as Yw,d7 as Zw,d8 as Qw,d9 as Jw,da as qw,db as eT,dc as tT,dd as rT,de as nT,v as iT,df as aT,dg as oT,dh as sT,aW as lT,di as uT,dj as cT,dk as fT,dl as dT,dm as hT,ay as ur,dn as vT,dp as pT,dq as gT,dr as mT,A as yT,ds as ST,dt as bT,bq as xT,du as CT,dv as wT,dw as TT,ap as IT,ac as Tv,ai as ct,dx as _T,au as xe,dy as AT,X as h1,y as ET,dz as PT,aP as MT,dA as RT,w as Er,dB as DT,dC as LT,H as kT,dD as OT,bA as hn,aD as BT,ae as ne,bP as ea,$ as Tc,dE as NT,bO as ta,dF as FT,aC as $T,aO as zT,R as fe,dG as UT,br as ih,b3 as _r,aj as Hs,b1 as lt,b2 as Ua,an as Iv,dH as HT,dI as VT,dJ as GT,dK as WT,dL as jT,dM as KT,dN as XT,Q as YT,dO as ZT,dP as QT,dQ as JT,at as Mt,dR as qT,aR as eI,aY as tI,ad as fl,aZ as rI,dS as nI,dT as iI,dU as aI,P as le,a6 as oI,dV as sI,dW as lI,dX as uI,dY as cI,a7 as fI,dZ as dI,bL as hI,d_ as vI,d$ as pI,bK as gI,b4 as v1,aA as _v,e0 as mI,e1 as yI,E as Jt,x as SI,e2 as bI,e3 as xI,e4 as CI,am as _n,e5 as wI,az as sr,aU as Vs,e6 as TI,as as pr,e7 as II,e8 as Av,cG as yr,by as ah,cv as oh,bI as _I,bH as Ve,bu as sh,bv as Ev,bx as jt,bB as Ic,bN as di,e9 as Tu,bE as hi,ea as AI,bJ as ra,bC as EI,bw as Lt,eb as PI,ec as ui,cw as MI,ed as RI,ee as DI,ef as LI,eg as kI,bM as Jp,eh as OI,bF as qp,cD as _c,cC as BI,ei as Vu,ej as _l}from"./index-DBM-UbfS.js";/**
+import{cH as pw,cI as gw,aG as mw,cJ as yw,cK as Sw,cL as bw,cM as xw,ah as Wt,cN as Cw,cO as ww,cP as Tw,cQ as Iw,aM as _w,aF as Aw,cR as Ew,aB as d1,bg as Pw,cS as Mw,cT as Rw,cU as Dw,cV as Lw,cW as kw,cX as Ow,cY as Bw,aE as Nw,cZ as Fw,s as Te,bl as $w,al as je,ag as Fe,af as he,ak as Z,c_ as zw,c$ as Uw,d0 as Hw,d1 as Vw,bk as Gw,cy as cl,aS as wv,aq as Ye,d2 as Ww,d3 as jw,a5 as ze,d4 as Kw,d5 as Xw,d6 as Yw,d7 as Zw,d8 as Qw,d9 as Jw,da as qw,db as eT,dc as tT,dd as rT,de as nT,v as iT,df as aT,dg as oT,dh as sT,aW as lT,di as uT,dj as cT,dk as fT,dl as dT,dm as hT,ay as ur,dn as vT,dp as pT,dq as gT,dr as mT,A as yT,ds as ST,dt as bT,bq as xT,du as CT,dv as wT,dw as TT,ap as IT,ac as Tv,ai as ct,dx as _T,au as xe,dy as AT,X as h1,y as ET,dz as PT,aP as MT,dA as RT,w as Er,dB as DT,dC as LT,H as kT,dD as OT,bA as hn,aD as BT,ae as ne,bP as ea,$ as Tc,dE as NT,bO as ta,dF as FT,aC as $T,aO as zT,R as fe,dG as UT,br as ih,b3 as _r,aj as Hs,b1 as lt,b2 as Ua,an as Iv,dH as HT,dI as VT,dJ as GT,dK as WT,dL as jT,dM as KT,dN as XT,Q as YT,dO as ZT,dP as QT,dQ as JT,at as Mt,dR as qT,aR as eI,aY as tI,ad as fl,aZ as rI,dS as nI,dT as iI,dU as aI,P as le,a6 as oI,dV as sI,dW as lI,dX as uI,dY as cI,a7 as fI,dZ as dI,bL as hI,d_ as vI,d$ as pI,bK as gI,b4 as v1,aA as _v,e0 as mI,e1 as yI,E as Jt,x as SI,e2 as bI,e3 as xI,e4 as CI,am as _n,e5 as wI,az as sr,aU as Vs,e6 as TI,as as pr,e7 as II,e8 as Av,cG as yr,by as ah,cv as oh,bI as _I,bH as Ve,bu as sh,bv as Ev,bx as jt,bB as Ic,bN as di,e9 as Tu,bE as hi,ea as AI,bJ as ra,bC as EI,bw as Lt,eb as PI,ec as ui,cw as MI,ed as RI,ee as DI,ef as LI,eg as kI,bM as Jp,eh as OI,bF as qp,cD as _c,cC as BI,ei as Vu,ej as _l}from"./index-CV9b2pRy.js";/**
 * vue v3.4.34
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT

File diff suppressed because it is too large
+ 1 - 1
dist/assets/index-CV9b2pRy.js


File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-CxIwZYRC.js


+ 1 - 1
dist/assets/index-VFagXLE3.js → dist/assets/index-D9APes4i.js

@@ -1 +1 @@
-import{u as c,ah as l}from"./index-BLanZz14.js";import{w as p,bA as d,a5 as g,cD as m,R as t,ae as r,af as u,al as f,ag as w,bH as k}from"./index-DBM-UbfS.js";import"./shapes-KZZr0Zhq.js";const S=()=>{function o(a){if(window.parent===window)return;const{api:e,playState:n}=a.data||{};e==="headerTogge"?window.parent.postMessage({api:"headerTogge",playState:n},"*"):e==="changeTogge"?window.parent.postMessage({api:"changeTogge"},"*"):["clickTempo","clickViewFigner"].includes(e)&&window.parent.postMessage({api:e},"*")}p(()=>{window.addEventListener("message",o)}),d(()=>{window.removeEventListener("message",o)})},_=g({__name:"mobileScreen",setup(o){S();const a=m(),e=t(!0),n=t(!1),i=c();return a.setScreenMode("mobileScreen"),i.initPPTData().then(()=>{e.value=!1}),(v,s)=>(r(),u("div",{class:"mobileScreen",onClick:s[0]||(s[0]=b=>n.value=!1)},[e.value?w("",!0):(r(),f(l,{key:0}))]))}}),P=k(_,[["__scopeId","data-v-90b2692a"]]);export{P as default};
+import{u as c,ah as l}from"./index-CPfrsi3J.js";import{w as p,bA as d,a5 as g,cD as m,R as t,ae as r,af as u,al as f,ag as w,bH as k}from"./index-CV9b2pRy.js";import"./shapes-CcFuW7MI.js";const S=()=>{function o(a){if(window.parent===window)return;const{api:e,playState:n}=a.data||{};e==="headerTogge"?window.parent.postMessage({api:"headerTogge",playState:n},"*"):e==="changeTogge"?window.parent.postMessage({api:"changeTogge"},"*"):["clickTempo","clickViewFigner"].includes(e)&&window.parent.postMessage({api:e},"*")}p(()=>{window.addEventListener("message",o)}),d(()=>{window.removeEventListener("message",o)})},_=g({__name:"mobileScreen",setup(o){S();const a=m(),e=t(!0),n=t(!1),i=c();return a.setScreenMode("mobileScreen"),i.initPPTData().then(()=>{e.value=!1}),(v,s)=>(r(),u("div",{class:"mobileScreen",onClick:s[0]||(s[0]=b=>n.value=!1)},[e.value?w("",!0):(r(),f(l,{key:0}))]))}}),P=k(_,[["__scopeId","data-v-90b2692a"]]);export{P as default};

+ 1 - 1
dist/assets/index-Dm89N0o3.js → dist/assets/index-DAJloYKi.js

@@ -1 +1 @@
-import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-DXNQHm98.js";import{a5 as n,ae as i,af as r,ak as o,aq as _,am as d,P as l,aS as p,bO as m,bP as f,bH as u}from"./index-DBM-UbfS.js";const s=a=>(m("data-v-340bbdaa"),a=a(),f(),a),h={class:"login"},b={class:"error"},g=s(()=>o("img",{src:t,class:"img",alt:""},null,-1)),k=s(()=>o("div",{class:"tit"},"登录已过期或服务器错误!",-1)),v=n({__name:"login",setup(a){function e(){window.close()}return(x,B)=>(i(),r("div",h,[o("div",b,[g,k,_(l(c),{class:"backBtn",type:"primary",plain:"",onClick:e},{default:d(()=>[p("关闭页面")]),_:1})])]))}}),C=u(v,[["__scopeId","data-v-340bbdaa"]]);export{C as default};
+import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-DNLDuM9e.js";import{a5 as n,ae as i,af as r,ak as o,aq as _,am as d,P as l,aS as p,bO as m,bP as f,bH as u}from"./index-CV9b2pRy.js";const s=a=>(m("data-v-340bbdaa"),a=a(),f(),a),h={class:"login"},b={class:"error"},g=s(()=>o("img",{src:t,class:"img",alt:""},null,-1)),k=s(()=>o("div",{class:"tit"},"登录已过期或服务器错误!",-1)),v=n({__name:"login",setup(a){function e(){window.close()}return(x,B)=>(i(),r("div",h,[o("div",b,[g,k,_(l(c),{class:"backBtn",type:"primary",plain:"",onClick:e},{default:d(()=>[p("关闭页面")]),_:1})])]))}}),C=u(v,[["__scopeId","data-v-340bbdaa"]]);export{C as default};

File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-DNLDuM9e.js


+ 1 - 1
dist/assets/index-rcv5Xrtm.js → dist/assets/index-uHnm71Uq.js

@@ -1 +1 @@
-import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,en as t,ae as r,af as c,bO as _,bP as d,ak as s,bH as p}from"./index-DBM-UbfS.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};
+import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,en as t,ae as r,af as c,bO as _,bP as d,ak as s,bH as p}from"./index-CV9b2pRy.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};

+ 1 - 1
dist/assets/shapes-KZZr0Zhq.js → dist/assets/shapes-CcFuW7MI.js

@@ -1,4 +1,4 @@
-import{cG as k,bB as $1}from"./index-DBM-UbfS.js";import{ai as Z,aj as H1,ak as U,al as n0,am as c0,an as i0,ao as j1,ap as K1,aq as Y1,ar as R}from"./index-BLanZz14.js";var z1={exports:{}};/*!
+import{cG as k,bB as $1}from"./index-CV9b2pRy.js";import{ai as Z,aj as H1,ak as U,al as n0,am as c0,an as i0,ao as j1,ap as K1,aq as Y1,ar as R}from"./index-CPfrsi3J.js";var z1={exports:{}};/*!
  * clipboard.js v2.0.11
  * https://clipboardjs.com/
  *

+ 1 - 1
dist/index.html

@@ -10,7 +10,7 @@
     <meta name="description" content="ppt编辑器" />
     <meta name="keywords" content="ppt,powerpoint,office powerpoint,在线ppt,幻灯片,演示文稿,ppt在线制作,Vue3,TypeScript" />
     <title>PPT</title>
-    <script type="module" crossorigin src="./assets/index-DBM-UbfS.js"></script>
+    <script type="module" crossorigin src="./assets/index-CV9b2pRy.js"></script>
     <link rel="stylesheet" crossorigin href="./assets/index-AavUEaYy.css">
   </head>
   <body>

+ 1 - 1
scripts/aiPptSelfTest.mjs

@@ -40,7 +40,7 @@ try {
     style: "cartoon",
     pages: [
       page(1, "cover", "凤鸣山谷的故事", "cover_scene", ["聆听自然的声音", "发现音乐的强弱变化", "用声音和乐器讲故事"], { image: { url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" } }),
-      page(2, "scene_intro", "山谷里传来了什么声音?", "story_stage", ["观察画面", "模仿你听到的声音"], { interaction: { type: "question", question: "你觉得是谁在歌唱?", options: ["小鸟", "溪水", "风声"] } }),
+      page(2, "scene_intro", "山谷里传来了什么声音?", "story_stage", ["观察画面", "想一想……"], { interaction: { type: "question", question: "你觉得是谁在歌唱?", options: ["小鸟", "溪水", "风声"] } }),
       page(3, "objectives", "今天我们要学会", "goal_cards", ["分辨声音的高低", "感受音乐的强弱", "合作完成声音表演"]),
       page(4, "work_context", "走进凤鸣山谷", "split_image", ["清晨的山谷十分安静", "鸟鸣由远处慢慢靠近", "溪水一直轻轻流淌"]),
       page(5, "first_listen", "第一次完整聆听", "listen_wave", ["安静听完音乐", "用手势表示声音的远近", "记住最喜欢的声音"], { resourceBindings: [{ resourceId: "song-demo", resourceType: "SONG", name: "凤鸣山谷", contentUrl: "https://example.com/fengming.mp3", verified: true }] }),

+ 1 - 3
src/utils/aiPptQuality.ts

@@ -63,7 +63,6 @@ const ALLOWED_LAYOUTS = new Set([
 ])
 const PLACEHOLDER_PATTERN = /(?:待补充|待完善|请输入|可参考教材|placeholder|lorem ipsum|\{\{.+?\}\}|\[.+?占位.+?\])/i
 const BROKEN_ELLIPSIS_PATTERN = /^(?:0?\d{1,3}|[A-Z])[.…]{1,3}$/i
-const TRUNCATED_TEXT_PATTERN = /(?:…|\.\.\.)$/
 const TEACHER_LANGUAGE_PATTERN = /(?:教师|老师)(?:讲述|提问|引导|指导|组织|巡视|示范)|(?:引导|指导|组织)学生/
 // 模板内部使用1600x900,输出到编辑器前会统一缩放为1920x1080。
 const SLIDE_WIDTH = 1920
@@ -201,7 +200,6 @@ function inspectSlides(slides: Slide[], issues: AiPptQualityIssue[], preferences
         texts.push(text)
         if (element.type !== "line") textElements.push({ element, text })
         if (BROKEN_ELLIPSIS_PATTERN.test(text)) push(issues, "rendered_broken_ellipsis", "error", pageNo, `渲染后出现异常截断:“${text}”`)
-        if (TRUNCATED_TEXT_PATTERN.test(text)) push(issues, "rendered_truncated_text", "error", pageNo, `文字被省略号截断:“${short(text)}”`)
         if (PLACEHOLDER_PATTERN.test(text)) push(issues, "rendered_placeholder", "error", pageNo, `渲染后仍有占位文本:“${short(text)}”`)
         if (TEACHER_LANGUAGE_PATTERN.test(text)) push(issues, "rendered_teacher_text", "error", pageNo, `学生画面出现教师指令:“${short(text)}”`)
         const minimumSize = minimumFontSize(element)
@@ -236,7 +234,7 @@ function qualityScores(outline: AiCoursewareOutline, issues: AiPptQualityIssue[]
   const templateFit = outline.templateId ? scoreAiCoursewareTemplate(outline, outline.templateId).score : 80
   return {
     teaching: 100 - deduct(["teacher_text_visible", "rendered_teacher_text", "teacher_notes_fallback", "missing_slide_remark", "invalid_options", "instruction_as_option"], 18, 6),
-    readability: 100 - deduct(["small_font", "small_font_warning", "text_overflow", "text_overlap", "out_of_bounds", "rendered_truncated_text"], 18, 7),
+    readability: 100 - deduct(["small_font", "small_font_warning", "text_overflow", "text_overlap", "out_of_bounds"], 18, 7),
     visual: 100 - deduct(["generated_focal_visual", "low_content_coverage", "layout_variety", "layout_consecutive", "card_ratio", "duplicate_image", "unnecessary_media_slot"], 16, 6),
     templateFit
   }

+ 44 - 186
src/views/Editor/EditorHeader/AiCoursewareDialog.vue

@@ -32,14 +32,6 @@
         <span>正在生成大纲,请稍候</span>
       </div>
 
-      <div v-else-if="taskInfo" class="progress-strip">
-        <div class="current-step">{{ currentProgressLabel }}</div>
-        <div class="progress-line">
-          <div class="progress-bar" :style="{ width: `${progressValue}%` }"></div>
-        </div>
-        <div class="progress-value">{{ progressValue }}%</div>
-      </div>
-
       <div v-if="workflowStep === 1" class="setup-stage simplified-setup">
         <div class="stage-intro">
           <div>
@@ -140,20 +132,12 @@
           </div>
         </div>
 
-        <div v-if="draftPreparationState !== 'idle'" :class="['compact-process-state', draftPreparationState]">
-          <span v-if="draftPreparationState === 'optimizing'" class="loading-dot"></span>
-          <span v-else class="process-state-icon">{{ draftPreparationState === "ready" ? "✓" : "!" }}</span>
-          <div>
-            <strong>{{ draftStatusTitle }}</strong>
-            <span>{{ draftStatusDescription }}</span>
-          </div>
-        </div>
-
         <div v-if="!workspaceSlide" :class="['final-preview-placeholder', draftPreparationState]">
           <span v-if="draftPreparationState === 'optimizing'" class="loading-dot"></span>
           <span v-else class="placeholder-icon">!</span>
-          <strong>{{ draftPreparationState === "failed" ? "最终课件暂未生成成功" : "正在生成并优化最终课件" }}</strong>
-          <small>{{ draftPreparationState === "failed" ? "请更换模板或返回修改需求后重试" : "完成后将直接展示可应用的最终效果" }}</small>
+          <strong>{{ draftPreparationState === "failed" ? "课件生成失败" : generationStatusTitle }}</strong>
+          <small>{{ draftPreparationState === "failed" ? draftFailureMessage : "完成后可直接预览、编辑和应用" }}</small>
+          <Button v-if="draftPreparationState === 'failed'" type="primary" @click="regenerateFinalPreview">重新生成</Button>
         </div>
 
         <div v-if="workspaceSlide" class="unified-review-workspace">
@@ -191,8 +175,8 @@
               />
               <div v-if="working" class="workspace-processing-overlay">
                 <span class="loading-dot"></span>
-                <strong>正在生成并优化新版本</strong>
-                <small>完成后自动更新预览</small>
+                <strong>{{ generationStatusTitle }}</strong>
+                <small>完成后自动更新当前预览</small>
               </div>
             </div>
             <div v-if="sampleBackgroundState.status === 'error'" class="template-load-error">模板背景加载失败,请更换模板或稍后重试</div>
@@ -227,7 +211,6 @@
         </template>
         <template v-else-if="isStyleStep">
           <Button @click="workflowStep = 1" :disabled="working">返回修改需求</Button>
-          <span v-if="draftPreparationState === 'optimizing'" class="footer-progress">正在自动检查并优化,无需操作…</span>
           <Button
             v-if="displayedConverted && draftPreparationState === 'ready'"
             type="primary"
@@ -329,8 +312,7 @@ const displayedConverted = ref<{ title: string; theme: Partial<SlideTheme>; slid
 const displayedTemplateId = ref<AiCoursewareTemplateId>()
 const draftQuality = ref<AiPptQualityReport>()
 const draftPreparationState = ref<"idle" | "optimizing" | "ready" | "failed">("idle")
-const automaticRepairAttempts = ref(0)
-const MAX_AUTOMATIC_REPAIR_ATTEMPTS = 3
+const draftFailureMessage = ref("请稍后重试")
 type ProtectedPageContent = {
   title: string
   subtitle?: string
@@ -387,6 +369,10 @@ const currentProgressLabel = computed(() => {
   const matched = [...progressSteps].reverse().find(step => progressValue.value >= step.threshold)
   return shortProgressLabel(taskInfo.value?.message) || matched?.label || "处理中"
 })
+const generationStatusTitle = computed(() => {
+  if (!taskInfo.value) return "正在生成课件"
+  return `${currentProgressLabel.value} ${progressValue.value}%`
+})
 const pageTitle = computed(() => outline.value?.chapterName || outline.value?.title || "AI生成课件")
 const pageMeta = computed(() => {
   if (!outline.value) return `根据当前教材生成${preferences.minPages}至${preferences.maxPages}页音乐课件`
@@ -398,17 +384,6 @@ const selectedWorkspacePageIndex = computed(() => selectedDraftPageIndex.value)
 const workspacePage = computed(() => workspacePages.value[selectedWorkspacePageIndex.value])
 const workspaceSlide = computed(() => workspaceSlides.value[selectedWorkspacePageIndex.value])
 const canEditWorkspaceText = computed(() => !working.value && Boolean(displayedConverted.value) && draftPreparationState.value === "ready")
-const draftStatusTitle = computed(() => {
-  if (draftPreparationState.value === "ready") return "课件已准备完成"
-  if (draftPreparationState.value === "failed") return "自动优化未能完成"
-  return "正在自动检查和优化课件"
-})
-const draftStatusDescription = computed(() => {
-  if (draftPreparationState.value === "ready") return "当前展示的就是优化后的最终课件,可直接编辑或应用。"
-  if (draftPreparationState.value === "failed")
-    return displayedConverted.value ? "新版本未生成成功,已保留上一次最终预览。" : "请更换模板或返回修改需求后重新生成。"
-  return `系统正在自动处理问题页面(${Math.max(1, automaticRepairAttempts.value)}/${MAX_AUTOMATIC_REPAIR_ATTEMPTS})。`
-})
 const previewSlides = computed(() => {
   if (!outline.value?.pages.length) return undefined
   const previewOutline = { ...outline.value, style: selectedStyle.value }
@@ -552,6 +527,7 @@ async function handleOutline() {
     displayedTemplateId.value = undefined
     draftQuality.value = undefined
     draftPreparationState.value = "idle"
+    draftFailureMessage.value = "请稍后重试"
     outline.value = res.data as AiCoursewareOutline
     if (templateSelectionMode.value === "auto") selectedTemplateId.value = recommendAiCoursewareTemplate(outline.value).id
     outline.value.style = selectedStyle.value
@@ -604,6 +580,7 @@ function handleFinalPreparationFailure(message = "自动生成和优化未能完
     ElMessage.warning(`${message},已保留上一次最终预览`)
     return
   }
+  draftFailureMessage.value = message
   draftPreparationState.value = "failed"
 }
 
@@ -618,7 +595,7 @@ async function handleGenerate() {
   draftConverted.value = undefined
   draftQuality.value = undefined
   draftPreparationState.value = "optimizing"
-  automaticRepairAttempts.value = 0
+  draftFailureMessage.value = "请稍后重试"
   sourceOutline.style = selectedStyle.value
   sourceOutline.templateId = selectedTemplateId.value
   restoreProtectedPageContent(sourceOutline)
@@ -631,7 +608,7 @@ async function handleGenerate() {
     preferences: { ...preferences }
   })
   if (res.code !== 200) {
-    handleFinalPreparationFailure()
+    handleFinalPreparationFailure(res.message || "课件生成请求失败,请重试")
     return
   }
 
@@ -651,6 +628,7 @@ async function fetchTask(taskId: string) {
   const res = await httpAjaxErrMsg(aiCoursewareTaskApi, taskId)
   if (res.code !== 200) {
     clearTask()
+    handleFinalPreparationFailure(res.message || "未能取得课件生成进度,请重试")
     return
   }
   taskInfo.value = res.data as AiCoursewareTaskInfo
@@ -661,13 +639,8 @@ async function fetchTask(taskId: string) {
     clearPollTimer()
     const message = taskInfo.value.message || "AI课件生成失败"
     clearTask()
-    if (draftOutline.value && draftQuality.value) {
-      console.error("AI课件自动优化失败", message)
-      await continueAutomaticRepairAfterFailure()
-    } else {
-      console.error("AI课件生成失败", message)
-      handleFinalPreparationFailure(message)
-    }
+    console.error("AI课件生成失败", message)
+    handleFinalPreparationFailure(message)
   }
 }
 
@@ -687,11 +660,16 @@ async function loadDraft(taskId: string) {
   const res = await httpAjaxErrMsg(aiCoursewareResultApi, taskId)
   if (res.code !== 200) {
     clearTask()
-    handleFinalPreparationFailure("未能取得最终课件")
+    handleFinalPreparationFailure(res.message || "未能取得最终课件")
     return
   }
 
   const result = res.data as AiCoursewareGenerateResult
+  if (!result?.outline?.pages?.length) {
+    clearTask()
+    handleFinalPreparationFailure("服务器未返回完整课件数据,请重新生成")
+    return
+  }
   draftOutline.value = result.outline
   restoreProtectedPageContent(draftOutline.value)
   draftOutline.value.templateId = selectedTemplateId.value
@@ -720,7 +698,7 @@ function rebuildDraftPreview() {
 async function regenerateDraftPage(index: number) {
   if (!draftOutline.value || !pptWork.id) return
   draftPreparationState.value = "optimizing"
-  automaticRepairAttempts.value = 0
+  draftFailureMessage.value = "请稍后重试"
   restoreProtectedPageContent(draftOutline.value)
   if (sampleFeedbackTags.value.includes("画风不合适")) paletteVariant.value = (paletteVariant.value + 1) % 3
   const feedback = [...sampleFeedbackTags.value, sampleFeedbackText.value.trim()].filter(Boolean).join(";")
@@ -734,7 +712,7 @@ async function regenerateDraftPage(index: number) {
     regeneratePageNo: index + 1
   })
   if (res.code !== 200) {
-    await continueAutomaticRepairAfterFailure()
+    handleFinalPreparationFailure(res.message || "当前页面更新失败,请重试")
     return
   }
   taskInfo.value = res.data as AiCoursewareTaskInfo
@@ -807,7 +785,6 @@ async function selectTemplate(templateId: AiCoursewareTemplateId, fromSample = f
   if (fromSample) {
     templatePickerExpanded.value = false
     showAllTemplates.value = false
-    ElMessage.info(`正在生成并优化${getAiCoursewareTemplate(templateId).name}`)
   }
   await regenerateFinalPreview()
 }
@@ -815,7 +792,6 @@ async function selectTemplate(templateId: AiCoursewareTemplateId, fromSample = f
 async function useRecommendedTemplate() {
   templateSelectionMode.value = "auto"
   selectedTemplateId.value = templateRecommendation.value.id
-  ElMessage.info(`正在生成并优化${recommendedTemplate.value.name}`)
   await regenerateFinalPreview()
 }
 
@@ -846,60 +822,24 @@ function templateFit(templateId: AiCoursewareTemplateId) {
 async function prepareDraftForUse() {
   if (!draftOutline.value) return
   draftPreparationState.value = "optimizing"
-  normalizeDraftLocally()
-  restoreProtectedPageContent(draftOutline.value)
-  rebuildDraftPreview()
-
-  if (draftQuality.value?.passed) {
+  try {
+    normalizeDraftLocally()
+    restoreProtectedPageContent(draftOutline.value)
+    rebuildDraftPreview()
+    if (!draftConverted.value?.slides.length || draftConverted.value.slides.length !== draftOutline.value.pages.length) {
+      handleFinalPreparationFailure("课件页面数据不完整,请重新生成")
+      return
+    }
+    if (draftQuality.value?.errors.length) {
+      console.warn("AI课件质量检查发现非阻断问题", draftQuality.value.errors)
+    }
     publishFinalPreview()
+    draftFailureMessage.value = "请稍后重试"
     draftPreparationState.value = "ready"
-    ElMessage.success("最终课件已完成优化,可以直接预览并应用")
-    return
-  }
-
-  console.warn("AI课件自动检查发现待修复项", draftQuality.value?.errors)
-  if (automaticRepairAttempts.value >= MAX_AUTOMATIC_REPAIR_ATTEMPTS) {
-    handleFinalPreparationFailure()
-    return
-  }
-
-  automaticRepairAttempts.value += 1
-  await requestAutomaticRepair()
-}
-
-async function requestAutomaticRepair() {
-  if (!draftOutline.value || !pptWork.id || !draftQuality.value) {
-    handleFinalPreparationFailure()
-    return
-  }
-  const repairSummary = draftQuality.value.errors
-    .slice(0, 12)
-    .map(issue => `${issue.pageNo ? `第${issue.pageNo}页` : "整套课件"}:${issue.message}`)
-    .join(";")
-  const res = await httpAjaxErrMsg(aiCoursewareGenerateApi, {
-    id: pptWork.id,
-    fromType: queryParams.pptResourcesType || queryParams.fromType,
-    outline: draftOutline.value,
-    templateId: selectedTemplateId.value,
-    visualPrompt: `${buildVisualPrompt()}系统检查发现以下问题,请在保持教学目标和页数不变的前提下全部修正:${repairSummary}。`,
-    preferences: { ...preferences }
-  })
-  if (res.code !== 200) {
-    await continueAutomaticRepairAfterFailure()
-    return
+  } catch (error) {
+    console.error("AI课件预览转换失败", error)
+    handleFinalPreparationFailure(error instanceof Error ? error.message : "课件预览转换失败,请重试")
   }
-  taskInfo.value = res.data as AiCoursewareTaskInfo
-  await pollTask(taskInfo.value.taskId)
-}
-
-async function continueAutomaticRepairAfterFailure() {
-  clearTask()
-  if (automaticRepairAttempts.value >= MAX_AUTOMATIC_REPAIR_ATTEMPTS) {
-    handleFinalPreparationFailure()
-    return
-  }
-  automaticRepairAttempts.value += 1
-  await requestAutomaticRepair()
 }
 
 function normalizeDraftLocally() {
@@ -1164,7 +1104,6 @@ function shortProgressLabel(message?: string) {
   font-size: 12px;
   font-weight: 600;
 }
-.progress-strip,
 .loading-strip {
   margin-top: 12px;
   height: 42px;
@@ -1184,34 +1123,6 @@ function shortProgressLabel(message?: string) {
   border-radius: 50%;
   animation: loading-rotate 0.8s linear infinite;
 }
-.current-step {
-  width: 64px;
-  flex-shrink: 0;
-  color: $themeColor;
-  font-size: 13px;
-  font-weight: 600;
-  white-space: nowrap;
-}
-.progress-value {
-  width: 42px;
-  text-align: right;
-  font-size: 13px;
-  color: $themeColor;
-  font-weight: 700;
-}
-.progress-line {
-  height: 6px;
-  flex: 1;
-  overflow: hidden;
-  border-radius: 999px;
-  background: #e7edf5;
-}
-.progress-bar {
-  height: 100%;
-  border-radius: inherit;
-  background: $themeColor;
-  transition: width 0.25s ease;
-}
 @keyframes loading-rotate {
   to {
     transform: rotate(360deg);
@@ -1479,56 +1390,6 @@ function shortProgressLabel(message?: string) {
   flex-direction: column;
   overflow: hidden;
 }
-.compact-process-state {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  margin-top: 8px;
-  padding: 7px 10px;
-  border-radius: 7px;
-  background: #f5f8fc;
-  color: #52616b;
-  font-size: 11px;
-  &.ready {
-    background: #eff9f5;
-    color: #16776f;
-  }
-  &.failed {
-    background: #fff7ed;
-    color: #9a5b08;
-  }
-  > div {
-    display: flex;
-    min-width: 0;
-    align-items: baseline;
-    gap: 8px;
-  }
-  strong {
-    color: #263442;
-    font-size: 12px;
-  }
-  span:last-child {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  }
-  .loading-dot {
-    width: 12px;
-    height: 12px;
-  }
-}
-.process-state-icon {
-  display: grid;
-  width: 18px;
-  height: 18px;
-  flex: 0 0 18px;
-  place-items: center;
-  border-radius: 50%;
-  background: currentColor;
-  color: #fff;
-  font-size: 11px;
-  font-weight: 700;
-}
 .final-preview-placeholder {
   min-height: 320px;
   flex: 1;
@@ -1557,6 +1418,10 @@ function shortProgressLabel(message?: string) {
     width: 20px;
     height: 20px;
   }
+  :deep(.button.primary) {
+    min-width: 104px;
+    margin-top: 6px;
+  }
 }
 .placeholder-icon {
   display: grid;
@@ -2711,13 +2576,6 @@ function shortProgressLabel(message?: string) {
     font-weight: 700;
   }
 }
-.footer-progress {
-  min-width: 154px;
-  color: $themeColor;
-  font-size: 13px;
-  font-weight: 600;
-  text-align: center;
-}
 .ai-dialog.review-mode .footer {
   margin-top: 10px;
   padding-top: 10px;

Some files were not shown because too many files changed in this diff