hendrix / source /scripts /check_huggingface_space.mjs
jackboyla's picture
Update Hendrix music transcription demo
2115e2f verified
Raw History Blame Contribute Delete
9.31 kB
#!/usr/bin/env node
// Check examples, real audio gains, live CPU inference, and responsive layouts.
import assert from "node:assert/strict";
import { mkdir, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
import { chromium } from "../demo/web/node_modules/playwright/index.mjs";
const base = process.env.HENDRIX_SPACE_URL || "https://jackboyla-hendrix.static.hf.space";
const resultPath = process.env.HENDRIX_SPACE_RESULT || "progress/evaluations/huggingface_space_redesign_check.json";
const screenshotPath = process.env.HENDRIX_SPACE_SCREENSHOT || "progress/evaluations/huggingface_space_redesign.png";
const browser = await chromium.launch({ headless: true, args: ["--disable-gpu"] });
const errors = [], failedResponses = [], audioUploads = [], results = [];
async function check(page, embedded) {
await page.addInitScript(() => {
window.__blendTargets = [];
const original = AudioParam.prototype.setTargetAtTime;
AudioParam.prototype.setTargetAtTime = function (value, time, constant) {
window.__blendTargets.push(value);
return original.call(this, value, time, constant);
};
});
const modelRequests = [];
page.on("pageerror", error => errors.push(String(error)));
page.on("request", request => {
if (request.method() === "POST" && request.url().startsWith(base)) audioUploads.push(request.url());
if (request.url().includes("/transcribe")) audioUploads.push(request.url());
if (request.url().includes("/models/")) modelRequests.push(request.url());
});
page.on("response", response => {
if (response.status() >= 400 && response.url().startsWith(base)) failedResponses.push({ url: response.url(), status: response.status() });
});
let app;
if (embedded) {
const repoId = process.env.HENDRIX_SPACE_REPO || "jackboyla/hendrix";
await page.goto(`https://huggingface.co/spaces/${repoId}`, { waitUntil: "domcontentloaded", timeout: 60_000 });
await page.waitForFunction(origin => [...document.querySelectorAll("iframe")].some(frame => frame.src.startsWith(origin)), base, { timeout: 60_000 });
app = page.frameLocator(`iframe[src^="${base}"]`).first();
} else {
await page.goto(`${base}/local.html?backend=wasm`, { waitUntil: "domcontentloaded", timeout: 60_000 });
app = page.mainFrame();
}
const download = app.getByRole("link", { name: "Download MIDI" });
await download.waitFor({ timeout: 60_000 });
assert.equal(await app.locator(".example-card").count(), 14);
assert.doesNotMatch(await app.locator("body").textContent(), /D26|research checkpoint/);
const isolation = await app.locator("html").evaluate(() => ({ isolated: crossOriginIsolated, secure: isSecureContext, sharedArrayBuffer: typeof SharedArrayBuffer !== "undefined" }));
assert.equal(isolation.secure, true);
if (!embedded) assert.equal(isolation.isolated, true);
assert.deepEqual(modelRequests, [], "prepared examples must work without downloading the inference model");
// Verify every catalog asset, not just the featured example.
const catalogResult = await app.locator("html").evaluate(async () => {
const catalog = await (await fetch("/examples/catalog.json")).json();
const bad = [];
for (const example of catalog.examples) {
for (const url of [example.audio_url, example.notes_url]) {
const response = await fetch(url);
if (!response.ok) bad.push({ url, status: response.status });
}
}
return { count: catalog.examples.length, categories: [...new Set(catalog.examples.map(example => example.category))], bad };
});
assert.equal(catalogResult.count, 14);
assert.deepEqual(catalogResult.bad, []);
const blend = app.getByRole("slider", { name: "Original and MIDI blend" });
await blend.waitFor();
// MIDI endpoints stay disabled until the actual sampled-instrument synth is ready.
await app.getByRole("button", { name: "MIDI", exact: true }).waitFor();
const synthStarted = Date.now();
while (await blend.isDisabled()) {
if (Date.now() - synthStarted > 60_000) throw new Error("MIDI instrument sounds did not become ready");
await new Promise(resolve => setTimeout(resolve, 100));
}
await app.getByRole("button", { name: "Play recording" }).click();
await app.getByRole("button", { name: "Pause playback" }).waitFor();
await blend.focus();
await page.keyboard.press("End");
await app.locator(".blend-readout").filter({ hasText: "100% MIDI" }).waitFor();
const midiGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4));
assert.deepEqual(midiGains, [0, 1, 0, 0], "MIDI endpoint must mute original gain and open MIDI gain");
assert.equal(await app.getByRole("button", { name: "Pause playback" }).count(), 1);
await page.keyboard.press("Home");
await app.locator(".blend-readout").filter({ hasText: "100% original" }).waitFor();
const originalGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4));
assert.deepEqual(originalGains, [1, 0, 0, 0], "Original endpoint must open original gain and mute MIDI gain");
await page.keyboard.press("ArrowRight");
await app.locator(".blend-readout").filter({ hasText: "99% original" }).waitFor();
const mixedGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4));
assert.deepEqual(mixedGains, [0.99, 0.01, 0, 0], "slider must drive both actual audio gains");
await app.getByRole("button", { name: "Pause playback" }).click();
const preparedMidi = await app.locator("html").evaluate(async (_html, url) => Array.from(new Uint8Array(await (await fetch(url)).arrayBuffer())), await download.getAttribute("href"));
assert.equal(Buffer.from(preparedMidi).subarray(0, 4).toString(), "MThd");
// Browse and load a second source; then return to the featured mix.
await app.getByRole("button", { name: "Guitar", exact: true }).click();
assert.equal(await app.locator(".example-card").count(), 4);
await app.getByRole("button", { name: "Load Funk guitar", exact: true }).click();
await app.getByRole("heading", { name: "Funk guitar", exact: true }).waitFor();
await download.waitFor();
await app.getByRole("button", { name: "Pause playback" }).waitFor();
await app.getByRole("button", { name: "Pause playback" }).click();
await app.getByRole("button", { name: "All", exact: true }).click();
await app.getByRole("button", { name: "Load Full-band groove", exact: true }).click();
await app.getByRole("heading", { name: "Full-band groove", exact: true }).waitFor();
await app.getByRole("button", { name: "Pause playback" }).waitFor();
await app.getByRole("button", { name: "Pause playback" }).click();
assert.deepEqual(modelRequests, [], "all prepared listening checks must stay independent of inference download");
if (!embedded) {
await mkdir(dirname(screenshotPath), { recursive: true });
await page.screenshot({ path: screenshotPath, fullPage: true });
await page.setViewportSize({ width: 390, height: 844 });
const layout = await app.locator("html").evaluate(() => ({ width: innerWidth, scrollWidth: document.documentElement.scrollWidth }));
assert.ok(layout.scrollWidth <= layout.width, `mobile layout overflow: ${JSON.stringify(layout)}`);
await page.screenshot({ path: screenshotPath.replace(/\.png$/, "_mobile.png"), fullPage: true });
await page.setViewportSize({ width: 1280, height: 1000 });
}
// Check the live path too; use a short excerpt to keep this a bounded test.
await app.locator(".limit-label select").selectOption("2.05");
const started = Date.now();
await app.getByRole("button", { name: "Transcribe audio" }).click();
await app.locator('.run-status:has-text("Audio never left this device"), .run-status.error').waitFor({ timeout: 180_000 });
const status = await app.locator(".run-status").textContent();
const diagnostic = await app.locator(".diagnostic-output").textContent().catch(() => "");
assert.match(status, /^[1-9]\d* notes from /, `${status}; ${diagnostic}`);
assert.match(diagnostic, /backend=wasm cache=cpu/);
const midi = await app.locator("html").evaluate(async (_html, url) => Array.from(new Uint8Array(await (await fetch(url)).arrayBuffer())), await download.getAttribute("href"));
assert.equal(Buffer.from(midi).subarray(0, 4).toString(), "MThd");
const result = { embedded, ...isolation, catalog: catalogResult, midiGains, originalGains, mixedGains,
preparedMidiBytes: preparedMidi.length, status, diagnostic, liveMidiBytes: midi.length,
liveWallSeconds: (Date.now() - started) / 1000, modelRequests };
results.push(result);
console.log(JSON.stringify(result));
}
try {
await check(await browser.newPage({ viewport: { width: 1280, height: 1000 } }), false);
if (process.env.HENDRIX_SPACE_SKIP_EMBED !== "1") {
await check(await browser.newPage({ viewport: { width: 1440, height: 1000 } }), true);
}
assert.deepEqual(errors, []);
assert.deepEqual(failedResponses, []);
assert.deepEqual(audioUploads, []);
await mkdir(dirname(resultPath), { recursive: true });
await writeFile(resultPath, JSON.stringify({ base, results, errors, failedResponses, audioUploads }, null, 2) + "\n");
console.log(`Example, audio blend, MIDI, layout, and live transcription checks passed: ${resultPath}`);
} finally { await browser.close(); }