tfrere's picture
tfrere HF Staff
feat(demo): add trio Playwright orchestration + seed embeds for the pitch video
f5f4567
Raw
History Blame Contribute Delete
3.54 kB
/**
* demo/solo.ts
*
* Single-persona demo: opens the collab-editor as "Alice", types the title
* and one paragraph with human-like rhythm. Use this to visually validate
* that the typing feels natural before scaling to multiple personas.
*
* Prereqs:
* - Frontend dev server running on http://localhost:5678
* - Backend dev server running on http://localhost:8080
*
* Run:
* cd backend && npx tsx demo/solo.ts
*
* Tuning:
* - Pass `--speed 1.5` to type faster, `--speed 0.7` to slow it down.
* - Pass `--url http://localhost:5678` to override the target.
*/
import { chromium } from "playwright";
import { humanType, humanTypeInto, pause } from "./human-typing.js";
import { PERSONAS, FALLBACK_USER_KEY } from "./personas.js";
interface CliArgs {
url: string;
speed: number;
headless: boolean;
}
function parseArgs(argv: string[]): CliArgs {
const args: CliArgs = {
url: "http://localhost:5678",
speed: 1,
headless: false,
};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === "--url") args.url = argv[++i];
else if (arg === "--speed") args.speed = Number(argv[++i]);
else if (arg === "--headless") args.headless = true;
}
return args;
}
async function run() {
const args = parseArgs(process.argv.slice(2));
const persona = PERSONAS.alice;
console.log(`[demo/solo] launching as ${persona.name} (${persona.color})`);
console.log(`[demo/solo] target: ${args.url} speed: ${args.speed}x`);
const browser = await chromium.launch({
headless: args.headless,
args: ["--window-size=1280,800"],
});
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 2,
});
await context.addInitScript(
({ key, value }) => {
try {
localStorage.setItem(key, value);
} catch {
/* ignore */
}
},
{
key: FALLBACK_USER_KEY,
value: JSON.stringify({ name: persona.name, color: persona.color }),
}
);
const page = await context.newPage();
console.log(`[demo/solo] goto ${args.url}`);
try {
await page.goto(args.url, { waitUntil: "domcontentloaded", timeout: 15_000 });
} catch (err) {
console.error(
`[demo/solo] cannot reach ${args.url}. Is the frontend dev server running?`
);
await browser.close();
process.exit(1);
}
await page.waitForSelector("[aria-label='Undo']", { timeout: 20_000 });
console.log(`[demo/solo] editor ready, starting scenario`);
await pause(1200, 2000);
const title = page.getByPlaceholder("Article title");
await humanTypeInto(page, title, "Climate signals in early 2026", {
speed: args.speed,
typoRate: 0.03,
});
await pause(900, 1600);
const body = page.locator(".ProseMirror").first();
await body.click();
await pause(600, 1200);
await humanType(
page,
"Over the last six months, a handful of monitoring stations picked up anomalies that deserve a closer look. ",
{ speed: args.speed, typoRate: 0.035, thinkRate: 0.22 }
);
await pause(800, 1400);
await humanType(
page,
"We cross-checked three independent datasets before drawing any conclusions.",
{ speed: args.speed, typoRate: 0.025 }
);
await pause(1500, 2500);
console.log(`[demo/solo] scenario complete. Leaving browser open for inspection.`);
console.log(`[demo/solo] press Ctrl+C to quit.`);
await new Promise(() => {});
}
run().catch((err) => {
console.error("[demo/solo] fatal:", err);
process.exit(1);
});