"""
Slidev 渲染器:把 Proposer 生成的 slides.md 真正渲染成“每页一张 PNG”。
这是本实验的关键——Reviewer 之所以能看到 Proposer 看不到的“新信息”,
正是因为我们真的把代码跑起来、渲染出了像素级的截图。
实现:调用本地安装的 Slidev CLI
npx slidev export slides.md --format png --output
--timeout 60000
Slidev 的 PNG 导出底层用 playwright-chromium 打开每一页并截图。
"""
import glob
import os
import shutil
import subprocess
WORKSPACE = os.path.join(os.path.dirname(__file__), "slidev_workspace")
class RenderError(RuntimeError):
pass
def render_slides(slides_md: str, out_subdir: str) -> list[str]:
"""
将 slides_md 文本写入工作区并导出为逐页 PNG。
参数:
slides_md: 完整的 Slidev markdown 源码
out_subdir: 输出子目录名(如 'proposer_iter1')
返回:
按页码排序的 PNG 绝对路径列表。
"""
os.makedirs(WORKSPACE, exist_ok=True)
slides_path = os.path.join(WORKSPACE, "slides.md")
with open(slides_path, "w", encoding="utf-8") as f:
f.write(slides_md)
out_dir = os.path.join(WORKSPACE, "exports", out_subdir)
if os.path.exists(out_dir):
shutil.rmtree(out_dir)
os.makedirs(out_dir, exist_ok=True)
# Slidev 的 PNG 导出会在 output 目录下生成 1.png, 2.png ...
cmd = [
"npx", "--no-install", "slidev", "export", "slides.md",
"--format", "png",
"--output", out_dir,
"--timeout", "60000",
"--dark", "false",
]
env = dict(os.environ)
# 让 playwright 使用项目内安装的 chromium(package.json 里的 playwright-chromium)
proc = subprocess.run(
cmd, cwd=WORKSPACE, env=env,
capture_output=True, text=True, timeout=600,
)
if proc.returncode != 0:
raise RenderError(
"Slidev export 失败:\n"
f"stdout:\n{proc.stdout[-2000:]}\n"
f"stderr:\n{proc.stderr[-2000:]}"
)
pngs = sorted(
glob.glob(os.path.join(out_dir, "*.png")),
key=lambda p: _page_num(p),
)
if not pngs:
raise RenderError(
f"Slidev export 未产出 PNG。stdout:\n{proc.stdout[-2000:]}"
)
return pngs
def _page_num(path: str) -> int:
base = os.path.splitext(os.path.basename(path))[0]
digits = "".join(ch for ch in base if ch.isdigit())
return int(digits) if digits else 0
if __name__ == "__main__":
# 自检:渲染一个最小的两页 slidev
demo = """---
theme: default
---
# Hello Slidev
第一页
---
# 第二页
- 渲染自检成功
"""
paths = render_slides(demo, "selftest")
print("渲染出的 PNG:")
for p in paths:
print(" ", p)