// WenwuMap 演示视频配置(使用 tools/demo-video-kit) // 运行:node tools/demo-video-kit/src/cli.mjs e2e/demo.config.mjs --out e2e/videos-kit import { NARRATION } from "./narration.mjs"; const N = (k) => NARRATION[k] || k; export default { baseUrl: "http://localhost:3000/map", viewport: { width: 1440, height: 900 }, brand: "中华文明全图鉴 · 功能演示", outDir: "e2e/videos-kit", voice: { name: "Cherry", model: "qwen-tts", apiKeyEnv: "AI_API_KEY", fallbackVoice: "Tingting" }, intro: { narration: N("__intro"), sub: "中华文明全图鉴 · Cultural Heritage Atlas" }, outro: { narration: N("__outro") }, steps: [ { label: "搜索", narration: N("搜索“青铜”"), run: async ({ page, pause }) => { const input = page.getByPlaceholder("搜索文物、机构、城市…"); await input.click(); await input.pressSequentially("青铜", { delay: 120 }); await pause(1500); await input.fill(""); await pause(500); }, }, { label: "城市分布翻页", narration: N("城市分布翻页"), run: async ({ page, pause }) => { const next = page.locator("aside").first().getByRole("button", { name: "下一页" }); for (let i = 0; i < 2; i++) { await next.click({ timeout: 4000 }); await pause(900); } }, }, { label: "门类筛选:青铜器", narration: N("门类筛选:青铜器"), run: async ({ page, pause }) => { const b = page.getByRole("button", { name: "青铜器" }).first(); await b.click({ timeout: 5000 }); await pause(1500); await b.click({ timeout: 5000 }); await pause(600); }, }, { label: "年代筛选:唐代", narration: N("年代筛选:唐代"), run: async ({ page, pause }) => { const b = page.getByRole("button", { name: "唐代", exact: true }).first(); await b.click({ timeout: 5000 }); await pause(1500); await b.click({ timeout: 5000 }); await pause(600); }, }, { label: "左栏收起/展开", narration: N("左栏收起/展开"), run: async ({ page, pause }) => { const t = page.getByTitle(/收起左栏|展开左栏/); await t.click({ timeout: 5000 }); await pause(1100); await t.click({ timeout: 5000 }); await pause(800); }, }, { label: "右栏收起/展开", narration: N("右栏收起/展开"), run: async ({ page, pause }) => { const t = page.getByTitle(/收起右栏|展开右栏/); await t.click({ timeout: 5000 }); await pause(1100); await t.click({ timeout: 5000 }); await pause(800); }, }, { label: "点击地图标记 · 查看馆藏", narration: N("点击地图标记 · 查看馆藏"), run: async ({ page, pause }) => { const markers = page.locator("div.cursor-pointer.flex-col"); await markers.first().waitFor({ state: "attached", timeout: 25000 }); await pause(1200); const aside = page.locator("aside").last(); const total = Math.min(await markers.count(), 8); for (let i = 0; i < total; i++) { const box = await markers.nth(i).boundingBox(); if (!box) continue; await page.mouse.click(box.x + box.width / 2, box.y + box.height - 3); await pause(1300); if ( (await aside.getByText("机构藏品").count()) > 0 || (await page.getByRole("button", { name: /文物信息/ }).count()) > 0 ) break; } }, }, { label: "进入文物详情", narration: N("进入文物详情"), run: async ({ page, pause }) => { const aside = page.locator("aside").last(); if ((await aside.getByText("机构藏品").count()) > 0) { await aside.locator("button:has(div.truncate)").first().click({ timeout: 4000 }); await pause(1600); } await page.getByRole("button", { name: /文物信息/ }).waitFor({ timeout: 8000 }); }, }, { label: "展开同一机构列表", narration: N("展开同一机构列表"), run: async ({ page, pause }) => { await page.getByText(/还有 \d+ 件/).first().click({ timeout: 4000 }); await pause(1500); }, }, { label: "文物信息折叠/展开", narration: N("文物信息折叠/展开"), run: async ({ page, pause }) => { const btn = page.getByRole("button", { name: /文物信息/ }); await btn.click({ timeout: 4000 }); await pause(1000); await btn.click({ timeout: 4000 }); await pause(800); }, }, { label: "AI 文物对话 · 流式回答与追问建议", narration: N("AI 文物对话 · 流式回答与追问建议"), run: async ({ page, pause }) => { const aside = page.locator("aside").last(); await page.getByRole("button", { name: "讲解员" }).first().click({ timeout: 4000 }); await pause(800); await page.getByText("它为什么珍贵?").first().click({ timeout: 4000 }); await page.locator("div.md-chat").first().waitFor({ timeout: 30000 }); await pause(3500); await aside.getByText("下一步 · 你可以问").waitFor({ timeout: 20000 }); await aside.locator('button:has-text("?")').first().click({ timeout: 4000 }); await page.locator("div.md-chat").nth(1).waitFor({ timeout: 30000 }); await pause(3500); }, }, { label: "图片放大与无极缩放(马踏飞燕)", narration: N("图片放大与无极缩放(马踏飞燕)"), run: async ({ page, pause }) => { const input = page.getByPlaceholder("搜索文物、机构、城市…"); await input.fill("马踏飞燕"); await page .waitForFunction(() => document.querySelectorAll("div.cursor-pointer.flex-col").length <= 2, null, { timeout: 8000, }) .catch(() => {}); await pause(900); const markers = page.locator("div.cursor-pointer.flex-col"); await markers.first().waitFor({ state: "attached", timeout: 15000 }); const box = await markers.first().boundingBox(); await page.mouse.click(box.x + box.width / 2, box.y + box.height - 3); await pause(1700); const aside = page.locator("aside").last(); if ((await aside.getByText("机构藏品").count()) > 0) { await aside.locator("button:has(div.truncate)").first().click({ timeout: 4000 }); await pause(1500); } const cover = page.locator("img.cursor-zoom-in").first(); await cover.waitFor({ state: "visible", timeout: 6000 }); const cb = await cover.boundingBox(); await page.mouse.click(cb.x + cb.width / 2, cb.y + cb.height / 2); await pause(1400); await page.mouse.move(720, 450); for (let i = 0; i < 6; i++) { await page.mouse.wheel(0, -320); await pause(420); } await pause(1200); await page.keyboard.press("Escape"); await pause(900); }, }, { label: "文物南迁北归 · 重走万里守护之路", narration: N("文物南迁北归 · 重走万里守护之路"), run: async ({ page, pause }) => { await page.getByPlaceholder("搜索文物、机构、城市…").fill(""); await pause(700); await page.getByRole("button", { name: "文物南迁北归之路" }).click({ timeout: 6000 }); await pause(2600); await page.getByRole("button", { name: "播放" }).click({ timeout: 6000 }); await pause(12000); await page.getByRole("button", { name: "退出路线" }).click({ timeout: 6000 }); await pause(1000); }, }, { label: "国宝海外回归 · 选择文物,重走回家之路", narration: N("国宝海外回归 · 选择文物,重走回家之路"), run: async ({ page, pause }) => { await page.getByRole("button", { name: "国宝海外回归" }).click({ timeout: 6000 }); await page.getByText("国宝海外回归 · 选择文物").first().waitFor({ timeout: 6000 }); await pause(1600); await page.locator('button:has-text("现藏")').first().click({ timeout: 6000 }); await pause(2600); await page.getByRole("button", { name: "播放" }).click({ timeout: 6000 }); await pause(7000); await page.getByRole("button", { name: "退出路线" }).click({ timeout: 6000 }); await pause(1000); }, }, ], };