补充软著核心功能图文素材

- 重拍创建多边形、AI自动推理、AI智能分割的高清截图与分段视频素材。

- 更新软著说明书,补充多边形绘制、传播范围、传播执行、AI点选分割和候选结果的图文说明。

- 清理说明书中的重复图片引用,修复不存在的工作区截图路径。

- 更新功能验证与素材清单,登记新增截图、MP4/WebM 视频和验证说明。

- 增加线上核心功能素材录制脚本,便于后续重新生成软著截图与视频。
This commit is contained in:
2026-05-08 02:12:24 +08:00
parent d369674906
commit abd8c73812
17 changed files with 239 additions and 34 deletions

View File

@@ -0,0 +1,189 @@
import { chromium } from 'playwright';
import fs from 'node:fs/promises';
import path from 'node:path';
const BASE_URL = process.env.SEG_DEMO_URL || 'https://seg.huijutec.cn/';
const USERNAME = process.env.SEG_DEMO_USER || 'admin';
const PASSWORD = process.env.SEG_DEMO_PASSWORD || '123456';
const OUT_ROOT = process.env.SEG_DEMO_OUT_DIR || path.resolve('新撰写软著文档');
const IMAGE_DIR = path.join(OUT_ROOT, 'images');
const VIDEO_DIR = path.join(OUT_ROOT, '系统使用视频');
const viewport = { width: 1920, height: 1080 };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function ensureDirs() {
await fs.mkdir(IMAGE_DIR, { recursive: true });
await fs.mkdir(VIDEO_DIR, { recursive: true });
}
async function login(page) {
await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 60000 });
await page.locator('input[type="text"]').first().fill(USERNAME);
await page.locator('input[type="password"]').first().fill(PASSWORD);
await page.getByRole('button', { name: /安全登录|登录/ }).click();
await page.waitForLoadState('networkidle').catch(() => {});
await sleep(1800);
}
async function shot(page, filename) {
await page.screenshot({
path: path.join(IMAGE_DIR, filename),
fullPage: false,
animations: 'disabled',
});
console.log(`screenshot ${filename}`);
}
async function clickTitle(page, title, settle = 1000) {
await page.getByTitle(title).first().click();
await page.waitForLoadState('networkidle').catch(() => {});
await sleep(settle);
}
async function openWorkspace(page) {
await clickTitle(page, '项目库', 800);
await page.getByText('演视LC视频序列').first().click();
await page.waitForLoadState('networkidle').catch(() => {});
await sleep(2200);
}
async function canvasBox(page) {
const canvas = page.locator('.konvajs-content canvas').first();
const box = await canvas.boundingBox();
if (!box) throw new Error('Canvas is not visible.');
return box;
}
async function drawPolygonDraft(page) {
await page.getByRole('button', { name: /肿瘤\/结节|胆囊/ }).first().click().catch(() => {});
await page.getByTitle('创建多边形 (P)').click();
await sleep(400);
const box = await canvasBox(page);
const points = [
[box.x + box.width * 0.38, box.y + box.height * 0.38],
[box.x + box.width * 0.57, box.y + box.height * 0.40],
[box.x + box.width * 0.52, box.y + box.height * 0.60],
[box.x + box.width * 0.35, box.y + box.height * 0.55],
];
for (const [x, y] of points) {
await page.mouse.click(x, y);
await sleep(250);
}
}
async function completePolygon(page) {
await page.keyboard.press('Enter');
await sleep(1000);
}
async function captureCoreScreenshots() {
const browser = await chromium.launch({ headless: true, args: ['--window-size=1920,1080'] });
const context = await browser.newContext({ viewport, deviceScaleFactor: 1 });
const page = await context.newPage();
await login(page);
await openWorkspace(page);
await shot(page, '24-workspace-current-frame-timeline.png');
await drawPolygonDraft(page);
await shot(page, '25-create-polygon-vertices.png');
await completePolygon(page);
await shot(page, '26-create-polygon-completed.png');
await page.getByTitle('AI自动推理').click();
await sleep(800);
await page.getByLabel('传播起始帧').fill('1').catch(() => {});
await page.getByLabel('传播结束帧').fill('3').catch(() => {});
await shot(page, '27-ai-auto-inference-range.png');
await page.getByRole('button', { name: '开始传播' }).click();
await sleep(1800);
await shot(page, '28-ai-auto-inference-running.png');
await page.waitForFunction(() => document.body.innerText.includes('已自动传播') || document.body.innerText.includes('没有生成新'), null, { timeout: 90000 }).catch(() => {});
await sleep(1000);
await shot(page, '29-ai-auto-inference-completed.png');
await clickTitle(page, 'AI智能分割', 1800);
await page.getByRole('button', { name: '正向选点' }).click();
const box = await canvasBox(page);
await page.mouse.click(box.x + box.width * 0.48, box.y + box.height * 0.48);
await sleep(700);
await shot(page, '30-ai-segmentation-positive-point.png');
await page.getByRole('button', { name: '执行高精度语义分割' }).click();
await page.waitForFunction(() => {
const text = document.body.innerText;
return text.includes('AI Mask') || text.includes('候选') || text.includes('推送至工作区编辑');
}, null, { timeout: 90000 }).catch(() => {});
await sleep(1200);
await shot(page, '31-ai-segmentation-result.png');
await browser.close();
}
async function recordVideo(name, action) {
const browser = await chromium.launch({ headless: true, args: ['--window-size=1920,1080'] });
const context = await browser.newContext({
viewport,
deviceScaleFactor: 1,
recordVideo: { dir: VIDEO_DIR, size: viewport },
});
const page = await context.newPage();
await action(page);
await sleep(800);
const video = page.video();
await context.close();
await browser.close();
const videoPath = await video?.path();
if (!videoPath) return;
const finalPath = path.join(VIDEO_DIR, `${name}.webm`);
await fs.rm(finalPath, { force: true });
await fs.rename(videoPath, finalPath);
console.log(`video ${path.basename(finalPath)}`);
}
async function captureVideos() {
await recordVideo('05-创建多边形标注演示', async (page) => {
await login(page);
await openWorkspace(page);
await drawPolygonDraft(page);
await sleep(900);
await completePolygon(page);
await sleep(1600);
});
await recordVideo('06-AI自动推理传播演示', async (page) => {
await login(page);
await openWorkspace(page);
await drawPolygonDraft(page);
await completePolygon(page);
await page.getByTitle('AI自动推理').click();
await sleep(900);
await page.getByLabel('传播起始帧').fill('1').catch(() => {});
await page.getByLabel('传播结束帧').fill('3').catch(() => {});
await sleep(900);
await page.getByRole('button', { name: '开始传播' }).click();
await sleep(5000);
});
await recordVideo('07-AI智能分割点选推理演示', async (page) => {
await login(page);
await openWorkspace(page);
await clickTitle(page, 'AI智能分割', 1800);
await page.getByRole('button', { name: '正向选点' }).click();
const box = await canvasBox(page);
await page.mouse.click(box.x + box.width * 0.48, box.y + box.height * 0.48);
await sleep(1000);
await page.getByRole('button', { name: '执行高精度语义分割' }).click();
await sleep(5000);
});
}
async function main() {
await ensureDirs();
await captureCoreScreenshots();
await captureVideos();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});