HelioAI's picture
LFM3-1.7B inference modules: experimental release (part 2)
cd61333 verified
Raw History Blame Contribute Delete
5.34 kB
"""Headless renderer for visual answers (SVG / HTML / canvas / three.js) with objective checks.
render(kind, source, png_path) opens the page in headless Chrome (the installed browser, via playwright) at 1024x768,
waits for it to settle and returns a report:
errors JavaScript exceptions and console errors (messages)
failed network requests that failed (e.g. a wrong library URL)
blank the screenshot is (almost) a single colour
overflow the page is wider than the viewport (horizontal scroll)
animated two screenshots 0.7 s apart differ (for animation tasks)
coverage share of the viewport that is not background colour
parse_error for SVG: the XML does not parse
The screenshot is saved to png_path. Used by the visual benchmark and by the render-check-repair module.
"""
import io
import os
import re
import tempfile
import xml.dom.minidom
import numpy as np
from PIL import Image
from playwright.sync_api import sync_playwright
W, H = 1024, 768
TMP = os.path.join(tempfile.gettempdir(), "lfm3_render")
os.makedirs(TMP, exist_ok=True)
SVG_PAGE = """<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;background:#fff}
#c{width:1024px;height:768px;display:flex;align-items:center;justify-content:center}
#c>svg{max-width:1024px;max-height:768px;width:100%;height:100%}</style></head><body><div id="c">__SVG__</div></body></html>"""
def _stats(png):
a = np.asarray(Image.open(io.BytesIO(png)).convert("RGB")).astype(np.int16)
flat = a.reshape(-1, 3)
vals, counts = np.unique(flat[::7], axis=0, return_counts=True)
bg = vals[counts.argmax()]
diff = np.abs(a - bg).sum(axis=2) > 24
return a, float(diff.mean())
def extract(text, kind):
if text is None:
return None
blocks = re.findall(r"```(\w*)\s*\n(.*?)```", text, re.S)
for lang, body in reversed(blocks):
if kind == "svg" and ("<svg" in body):
return body[body.index("<svg"):].strip()
if kind == "html" and ("<html" in body.lower() or "<body" in body.lower() or "<canvas" in body or "<script" in body):
return body.strip()
if kind == "svg":
m = re.search(r"<svg.*</svg>", text, re.S)
return m.group(0) if m else None
m = re.search(r"<!doctype html.*</html>|<html.*</html>", text, re.S | re.I)
return m.group(0) if m else None
class Renderer:
def __init__(self, channel="chrome"):
self.pw = sync_playwright().start()
self.browser = self.pw.chromium.launch(channel=channel, headless=True,
args=["--use-angle=swiftshader", "--enable-unsafe-swiftshader"])
def close(self):
self.browser.close()
self.pw.stop()
def render(self, kind, source, png_path, wait_ms=1500):
rep = {"errors": [], "failed": [], "parse_error": None}
if kind == "svg":
try:
xml.dom.minidom.parseString(source.encode("utf-8"))
except Exception as e:
rep["parse_error"] = str(e)[:200]
html = SVG_PAGE.replace("__SVG__", source)
else:
html = source
path = os.path.join(TMP, f"page_{os.getpid()}_{abs(hash(png_path)) % 10**8}.html")
with open(path, "w", encoding="utf-8") as f:
f.write(html)
page = self.browser.new_page(viewport={"width": W, "height": H})
page.on("pageerror", lambda e: rep["errors"].append(f"Uncaught {e.name}: {e.message}"[:300]))
page.on("console", lambda m: rep["errors"].append(("console.error: " + m.text)[:300]) if m.type == "error" else None)
page.on("requestfailed", lambda r: rep["failed"].append(r.url[:200]))
try:
page.goto("file:///" + path.replace("\\", "/"), wait_until="load", timeout=20000)
page.wait_for_timeout(wait_ms)
png1 = page.screenshot()
page.wait_for_timeout(700)
png2 = page.screenshot()
rep["overflow"] = bool(page.evaluate("document.documentElement.scrollWidth > window.innerWidth + 2"))
rep["page_height"] = int(page.evaluate("document.documentElement.scrollHeight"))
except Exception as e:
rep["errors"].append(f"page failed to load: {str(e)[:200]}")
png1 = png2 = page.screenshot() if not page.is_closed() else None
rep["overflow"], rep["page_height"] = False, 0
finally:
page.close()
try:
os.remove(path)
except OSError:
pass
if png1 is None:
rep.update(blank=True, coverage=0.0, animated=False)
return rep
with open(png_path, "wb") as f:
f.write(png2)
a1, cov = _stats(png1)
a2, _ = _stats(png2)
rep["coverage"] = round(cov, 4)
rep["blank"] = cov < 0.003
rep["animated"] = bool((np.abs(a1 - a2).sum(axis=2) > 24).mean() > 0.0005)
return rep
if __name__ == "__main__":
import json
import sys
r = Renderer()
kind, src, out = sys.argv[1], open(sys.argv[2], encoding="utf-8").read(), sys.argv[3]
print(json.dumps(r.render(kind, src, out), ensure_ascii=False))
r.close()