| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- <!doctype html>
- <html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>AI课件流程回归</title></head><body><div id="app"></div>
- <script type="module">
- import { createApp, h, ref, nextTick } from "vue"
- import { createRouter, createMemoryHistory } from "vue-router"
- import { store, useSlidesStore } from "/src/store/index.ts"
- import "/src/router/index.ts"
- import { httpAxios } from "/src/api/ApiInstance.ts"
- import { setToken } from "/src/libs/auth.ts"
- import AiDialog from "/src/views/Editor/EditorHeader/AiCoursewareDialog.vue"
- import Editor from "/src/views/Editor/index.vue"
- import usePptWork from "/src/store/pptWork.ts"
- import { useSnapshotStore } from "/src/store/index.ts"
- import { inspectAiPptResources } from "/src/utils/aiPptResources.ts"
- import { draftKey, loadAiDraft, saveAiDraft } from "/src/utils/aiCoursewareDraft.ts"
- import "/src/assets/styles/global.scss"
- import "/src/assets/styles/elementTheme.scss"
- import "/src/assets/styles/prosemirror.scss"
- // 所有业务接口使用本地夹具,不调用生成服务,不修改任何线上课件。
- const scenario = new URLSearchParams(location.search).get("scenario") || "normal"
- setToken(`local-ai-ppt-regression-${scenario}`)
- const generated = ref(0)
- const outlineCalls = ref(0), savedCalls = ref(0), submitted = ref(null)
- const savedText = ref("")
- let pollCount = 0
- const repairs = ref(0)
- const historicalPolls = ref(0)
- let repaired = false
- const resourceCheck = ref("检查中")
- const runtimeError = ref("")
- const types = ["cover", "scene_intro", "objectives", "music_knowledge", "rhythm_practice", "performance_activity", "practice_check", "creative_activity", "summary"]
- const layouts = ["cover_scene", "hero_focus", "objectives", "split_content", "diagram_focus", "activity", "text_focus", "activity", "summary"]
- const outline = {
- title: "声音强弱课堂测试", gradeName: "一年级", textbookName: "本地测试教材", chapterName: "声音的强弱", courseType: "music", templateId: "ethnic-flower",
- textbookResearch: { exactMatch: true, confidence: "HIGH", contentSummary: "仅用于软件回归的教学夹具", teachingObjectives: ["能用声音表现强弱"], sources: [{ title: "本地测试依据(非真实教材认证)", url: "https://example.com/test-only" }] },
- pages: types.map((type, index) => ({ pageNo:index+1, type, title: index === 0 ? "声音强弱课堂测试" : `任务${index}:表现声音强弱`,
- teachingBrief: index === 0 ? "从山谷故事引入,邀请学生寻找身边的声音" : "听两组声音,辨别强弱,再用拍手模仿,并说明判断理由",
- studentContent:{ points:["用声音表现强弱变化", "说出判断的理由"] }, durationMinutes:index===0?8:4, objectiveIds:["1"],
- teachingPurpose:"表现声音强弱", learningEvidence:"能表现强弱并说明理由", speakerNotes:"示范后请学生练习,根据表现及时反馈。",
- classroomAction:{studentTask:"完成声音强弱挑战", teacherInstruction:"示范强弱对比,邀请学生练习后互评"},
- interaction:type==="practice_check"?{question:"哪种方式声音更轻?",options:["轻声哼唱","大声呼喊"],answer:"轻声哼唱",explanation:"根据声音强弱判断"}:undefined,
- visualPlan:{layout:layouts[index]}, imageKeywords:[] }))
- }
- if (scenario === "failed-resume") {
- const key = await draftKey("local-ai-ppt-regression", "TEACHER")
- if (!await loadAiDraft(key)) await saveAiDraft({
- key, updatedAt: Date.now(), outline: structuredClone(outline), taskId: "historical-failed-task", extraPrompt: "",
- preferences: { minPages: 9, maxPages: 12, lessonMinutes: 40 }
- })
- }
- httpAxios.axioseRquest = async config => {
- let data
- if (config.url.endsWith("/outline")) { outlineCalls.value++; data = structuredClone(outline) }
- else if (config.url.endsWith("/generate")) {
- generated.value++
- submitted.value = JSON.parse(JSON.stringify(config.data))
- pollCount = 0
- if (config.data.repairOnly) { repairs.value++; repaired=true }
- data={taskId:`local-task-${generated.value}`,status:"PENDING",progress:0}
- }
- else if (config.url.endsWith("/result")) {
- data={outline:JSON.parse(JSON.stringify(submitted.value?.outline || outline)),context:{resources:[]}}
- data.outline.pages.forEach((page, index) => {
- const fixture = outline.pages[index % outline.pages.length]
- Object.assign(page, { ...structuredClone(fixture), pageNo: index + 1, title: page.title, teachingBrief: page.teachingBrief })
- page.studentContent = { points: ['哪种声音更轻?', '试着用拍手表现出来'] }
- page.bullets = [...page.studentContent.points]
- page.objectiveIds=["1"]
- })
- if (scenario.includes("draft") && !repaired) {
- data.outline.pages.forEach(page => page.objectiveIds=[])
- data.reviewMessage="声音强弱的练习与评价尚未完整关联"
- }
- }
- else if (config.url.endsWith("/task/historical-failed-task")) {
- historicalPolls.value++
- data={taskId:"historical-failed-task",status:"FAILED",progress:50,message:"教学目标1缺少对应实践或评价任务"}
- }
- else if (config.url.includes("/task/")) {
- pollCount++
- if (scenario.includes("network") && pollCount === 1) throw new Error("测试网络中断")
- data={taskId:`local-task-${generated.value}`,status:scenario.includes("failure") && generated.value === 1 ? "FAILED" : scenario.includes("slow") && pollCount < 6 ? "RUNNING" : scenario.includes("draft") ? "DRAFT" : "DONE",progress:pollCount < 6 && scenario.includes("slow") ? 50 : 100,message:scenario.includes("failure") ? "测试:生成服务暂不可用" : "正在完善课堂任务"}
- }
- else throw new Error("测试禁止未声明的业务请求")
- return {data:{code:200,data}}
- }
- const router=createRouter({history:createMemoryHistory(),routes:[{path:"/",component:{render:()=>null}}]})
- await router.push("/?id=local-ai-ppt-regression")
- await router.isReady()
- const app=createApp({setup(){
- const visible=ref(false)
- const slidesStore=useSlidesStore()
- const pptWork=usePptWork()
- // 仅隔离存储边界,按钮、编辑器和课件转换均使用正式组件。
- pptWork.updatePPT=async()=>{savedText.value=slidesStore.slides.flatMap(slide=>slide.elements.filter(el=>el.type==="text").map(el=>el.content.replace(/<[^>]+>/g,""))).join("、");savedCalls.value++;pptWork.isSave=true}
- if (!scenario.startsWith("editor")) nextTick(()=>{visible.value=true})
- return ()=>h("main",[
- runtimeError.value ? h("pre", runtimeError.value) : null,
- savedCalls.value ? h("p",{id:"saved-text"},`保存内容:${savedText.value}`):null,
- h("p",{id:"test-status"},`大纲请求:${outlineCalls.value};生成请求次数:${generated.value};完善请求:${repairs.value};保存请求:${savedCalls.value};历史失败任务查询:${historicalPolls.value};已应用页数:${slidesStore.slides.length};资源检查:${resourceCheck.value}`),
- submitted.value ? h("p",{id:"submitted-plan"},`模式:${submitted.value.outlineMode};提交的摘要:${submitted.value.outline.pages.map(p=>p.teachingBrief).join("、")};旧正文数量:${submitted.value.outline.pages.filter(p=>p.studentContent?.points?.length || p.bullets?.length).length};页数:${submitted.value.outline.pages.length}`):null,
- scenario.startsWith("editor") ? h("div",{style:"height:calc(100vh - 65px)"},[h(Editor)]) : h("button",{onClick:()=>{visible.value=true}},"打开AI课件"),
- scenario.startsWith("editor") ? null : h(AiDialog,{visible:visible.value,"onUpdate:visible":value=>{visible.value=value}}),
- h("button",{onClick:()=>useSnapshotStore().unDo()},"测试撤销")
- ])
- }})
- app.config.errorHandler = error => { runtimeError.value=String(error?.stack || error) }
- app.use(store).use(router).mount("#app")
- // 使用浏览器真实图片解码验证损坏资源阻断及谱图等比缩放。
- const svg='data:image/svg+xml;charset=utf-8,'+encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="400" height="100"><rect width="400" height="100" fill="white"/></svg>')
- const score={id:"image",type:"image",src:svg,left:0,top:0,width:200,height:200}
- const slides=[{elements:[score,{id:"broken",type:"image",src:"data:image/png;base64,invalid",left:0,top:0,width:100,height:100}]}]
- const issues=await inspectAiPptResources({pages:[{resourceBindings:[{resourceType:"MUSIC",scoreImageUrl:svg}]}]},slides)
- resourceCheck.value=issues.length===1&&score.width===200&&score.height===50&&score.top===75?"通过(损坏图片阻断、谱图等比缩放)":"失败"
- </script></body></html>
|