125 lines
4.7 KiB
TypeScript
125 lines
4.7 KiB
TypeScript
import { demoScenes } from "./scenes.ts";
|
|
import { z } from "zod";
|
|
import { chromium } from "@playwright/test";
|
|
import { mkdir, writeFile, access } from "node:fs/promises";
|
|
const browser = await chromium.launch();
|
|
const people = demoScenes.map((scene) => scene.id);
|
|
const scenes = demoScenes.map((scene) => scene.prompt);
|
|
for (const [index, id] of people.entries()) {
|
|
if (process.env["ONLY"] && id !== process.env["ONLY"]) continue;
|
|
const dir = `reports/image-reveal-demo/${id}`;
|
|
if (
|
|
!process.env["ONLY"] &&
|
|
(await access(`${dir}/generated.webp`).then(
|
|
() => true,
|
|
() => false,
|
|
))
|
|
)
|
|
continue;
|
|
await mkdir(dir, { recursive: true });
|
|
const context = await browser.newContext({
|
|
viewport: { width: 540, height: 900 },
|
|
reducedMotion: "no-preference",
|
|
recordVideo: { dir, size: { width: 540, height: 900 } },
|
|
});
|
|
await context.addInitScript(
|
|
({ id }) => {
|
|
sessionStorage.setItem(
|
|
`stx:onboarding:v2:demo-${id}`,
|
|
JSON.stringify({
|
|
version: 2,
|
|
locale: "fr",
|
|
intensity: "beginner",
|
|
adultConfirmed: true,
|
|
warningsAccepted: true,
|
|
participationConfirmed: true,
|
|
}),
|
|
);
|
|
localStorage.setItem("stx:metrics-tour:completed:v2", "true");
|
|
},
|
|
{ id },
|
|
);
|
|
const recordingStarted = Date.now();
|
|
const page = await context.newPage();
|
|
const errors: string[] = [];
|
|
page.on("pageerror", (e) => errors.push(e.message));
|
|
// The demo uses explicit, nonfigurative art direction. The real image endpoint and Rev Animated run unchanged.
|
|
await page.route("**/api/ai/image", async (route) => {
|
|
const data = z
|
|
.object({
|
|
forwardedProps: z.record(z.string(), z.unknown()),
|
|
data: z.record(z.string(), z.unknown()),
|
|
})
|
|
.loose()
|
|
.parse(route.request().postDataJSON());
|
|
const prompt = `${scenes[index]}, atmospheric concept art, vaporous and dreamy, painterly illustration, soft bloom, no people, no bodies, no text`;
|
|
data.forwardedProps["prompt"] = prompt;
|
|
data.data["prompt"] = prompt;
|
|
data.forwardedProps["negativePrompt"] =
|
|
"people, bodies, nudity, sexual content, text, watermark, sharp noise";
|
|
data.data["negativePrompt"] = data.forwardedProps["negativePrompt"];
|
|
await writeFile(
|
|
`${dir}/image-request.json`,
|
|
JSON.stringify(
|
|
{
|
|
model: "rev-animated",
|
|
steps: 3,
|
|
cfgScale: 1,
|
|
prompt,
|
|
negativePrompt: data.data["negativePrompt"],
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
const response = await route.fetch({ postData: JSON.stringify(data), timeout: 180000 });
|
|
const body = await response.text();
|
|
await writeFile(`${dir}/image.sse`, body);
|
|
await route.fulfill({ response, body });
|
|
});
|
|
page.on("response", async (response) => {
|
|
if (response.url().endsWith("/api/ai/chat")) {
|
|
try {
|
|
await writeFile(`${dir}/chat.sse`, await response.text());
|
|
} catch {}
|
|
}
|
|
});
|
|
await page.goto(
|
|
`http://127.0.0.1:3000/chat?persona-id=${id}&model-id=gemini&gateway-id=nanogpt&session-id=demo-${id}`,
|
|
);
|
|
const input = page.getByRole("textbox");
|
|
await input.waitFor();
|
|
await input.fill(
|
|
"Je choisis le mode texte et image. Ouvre un paysage de rêve, dans la brume. Deux phrases sur les étoiles, sans personnages ni contenu sexuel, et une illustration.",
|
|
);
|
|
await input.press("Enter");
|
|
try {
|
|
await page.locator('[data-image-reveal="summon"]').waitFor({ timeout: 180000 });
|
|
await writeFile(
|
|
`${dir}/timing.json`,
|
|
JSON.stringify({ summon: Date.now() - recordingStarted }),
|
|
);
|
|
await page.waitForTimeout(550);
|
|
await page.screenshot({ path: `${dir}/summon.png` });
|
|
await page.locator('[data-image-reveal="reveal"]').waitFor();
|
|
await page.waitForTimeout(1700);
|
|
await page.screenshot({ path: `${dir}/reveal.png` });
|
|
await page.locator("[data-image-reveal]").waitFor({ state: "detached", timeout: 15000 });
|
|
await page.waitForTimeout(1500);
|
|
await page.screenshot({ path: `${dir}/dialogue.png` });
|
|
const image = page.locator("[data-image-slot] img").last();
|
|
const src = await image.getAttribute("src");
|
|
if (src?.startsWith("data:"))
|
|
await writeFile(`${dir}/generated.webp`, Buffer.from(src.split(",")[1]!, "base64"));
|
|
console.log(`${id}: live image and screenshots captured`);
|
|
} catch (error) {
|
|
console.log(`${id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
await page.screenshot({ path: `${dir}/failure.png` });
|
|
}
|
|
await writeFile(`${dir}/page.txt`, await page.locator("body").innerText());
|
|
await writeFile(`${dir}/errors.json`, JSON.stringify(errors));
|
|
const video = page.video();
|
|
await context.close();
|
|
await video?.saveAs(`${dir}/session.webm`);
|
|
}
|
|
await browser.close();
|