website-builde / lib /extract.ts
Daviddolor's picture
Single-process Chromium, fixed visual-polish prompt, Docker config for HF Space
ec675f2
Raw
History Blame Contribute Delete
4.51 kB
import { Page } from "playwright";
export async function extractPageData(page: Page) {
return await page.evaluate(() => {
const title = document.title;
const description =
document.querySelector('meta[name="description"]')?.getAttribute("content") ||
document.querySelector('meta[property="og:description"]')?.getAttribute("content") ||
"";
const faviconEl =
document.querySelector('link[rel="icon"]') ||
document.querySelector('link[rel="shortcut icon"]') ||
document.querySelector('link[rel="apple-touch-icon"]');
const favicon = faviconEl
? new URL(faviconEl.getAttribute("href") || "", location.href).href
: new URL("/favicon.ico", location.href).href;
const headings: { level: number; text: string }[] = [];
const seenHeadings = new Set<string>();
document.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((el) => {
const t = el.textContent?.trim();
const key = `${el.tagName}-${t}`;
if (t && !seenHeadings.has(key)) {
seenHeadings.add(key);
headings.push({ level: Number(el.tagName[1]), text: t });
}
});
const images = [...new Set(
Array.from(document.querySelectorAll("img[src]")).map((img) =>
new URL(img.getAttribute("src") || "", location.href).href
)
)].slice(0, 100);
const links = [...new Set(
Array.from(document.querySelectorAll("a[href]")).map((a) =>
new URL(a.getAttribute("href") || "", location.href).href
)
)].slice(0, 150);
const buttons = [...new Set(
Array.from(document.querySelectorAll(
"button, input[type='submit'], input[type='button'], a[class*='btn'], a[role='button']"
)).map((el) => (el.textContent || (el as HTMLInputElement).value || "").trim())
.filter((t) => t.length > 0 && t.length <= 60)
)].slice(0, 50);
const css = [...new Set(
Array.from(document.querySelectorAll('link[rel="stylesheet"]')).map((l) =>
new URL(l.getAttribute("href") || "", location.href).href
)
)];
document.querySelectorAll("nav, header, footer, aside, script, style, noscript").forEach((el) => el.remove());
const colors = new Set<string>();
const fonts = new Set<string>();
const fontSizes = new Set<string>();
const spacing = new Set<string>();
const borderRadius = new Set<string>();
const animations = new Set<string>();
const gradients = new Set<string>();
const elements = Array.from(document.querySelectorAll("*")).slice(0, 800);
for (const el of elements) {
const cs = getComputedStyle(el);
[cs.color, cs.backgroundColor, cs.borderTopColor, cs.borderRightColor, cs.borderBottomColor, cs.borderLeftColor]
.forEach((c) => {
if (c && c !== "rgba(0, 0, 0, 0)" && c !== "transparent") colors.add(c);
});
if (cs.fontFamily) fonts.add(cs.fontFamily.split(",")[0].replace(/['"]/g, "").trim());
if (cs.fontSize) fontSizes.add(cs.fontSize);
[cs.marginTop, cs.marginBottom, cs.marginLeft, cs.marginRight,
cs.paddingTop, cs.paddingBottom, cs.paddingLeft, cs.paddingRight].forEach((v) => {
if (v && v !== "0px") spacing.add(v);
});
if (cs.borderRadius && cs.borderRadius !== "0px") borderRadius.add(cs.borderRadius);
if (cs.animationName && cs.animationName !== "none") {
animations.add(`${cs.animationName} ${cs.animationDuration} ${cs.animationTimingFunction}`);
}
if (cs.transitionProperty && cs.transitionProperty !== "all" && cs.transitionDuration !== "0s") {
animations.add(`transition: ${cs.transitionProperty} ${cs.transitionDuration}`);
}
if (cs.backgroundImage && cs.backgroundImage.includes("gradient")) {
gradients.add(cs.backgroundImage);
}
}
const text = (document.body.innerText || "")
.replace(/\n{3,}/g, "\n\n")
.replace(/[ \t]{2,}/g, " ")
.trim();
return {
title, description, favicon, headings, images, links, buttons, css,
design: {
colors: Array.from(colors).slice(0, 30),
fonts: Array.from(fonts).slice(0, 15),
fontSizes: Array.from(fontSizes).sort((a, b) => parseFloat(a) - parseFloat(b)),
spacing: Array.from(spacing).slice(0, 30),
borderRadius: Array.from(borderRadius).slice(0, 15),
animations: Array.from(animations).slice(0, 30),
gradients: Array.from(gradients).slice(0, 15),
},
text: text.slice(0, 8000),
};
});
}