Skill: zai-org/glmv-pdf-to-web (version 1.0.1)

--- name: glmv-pdf-to-web description: Convert a PDF (research paper, technical report, or project document) into a beautiful single-page academic/project website with a structured outline JSON. Trigger this skill when the user wants to make a paper page, project homepage, or academic website from a PDF — in Chinese or English. metadata: openclaw: emoji: "🌐" homepage: https://github.com/zai-org/GLM-V/tree/main/skills/glmv-pdf-to-web --- # PDF → Academic Project Website Skill Convert a research paper or technical document PDF into a polished single-page project website — the kind used for NeurIPS/CVPR/ICLR paper releases. Pages are converted locally at DPI 120, a structured `outline.json` is saved, images are cropped locally, and the final page is saved with `generate_web.py`. **Scripts are in:** `{SKILL_DIR}/scripts/` ## Dependencies Python packages (install once): ```bash pip install pymupdf pillow ``` System tools: `curl` (pre-installed on macOS/Linux). ## When to Use Trigger when the user asks to create a webpage or project page from a PDF — phrases like: "make a project page from a PDF", "create a paper website", "build an academic website for this paper", "论文主页", "做项目主页", "根据pdf做网页", "把论文做成主页", or any similar intent in Chinese or English. ## Output Directory Convention All output goes under `{WORKSPACE}/web/<pdf_stem>_<timestamp>/`: ``` web/ └── <pdf_stem>_<timestamp>/ ├── outline.json ← structured web plan (WebPlan schema) ├── crops/ ← locally-saved cropped images │ ├── fig_arch_crop.png │ ├── table_results_crop.png │ └── ... └── index.html ← the website ``` - `<pdf_stem>` = PDF filename without extension - `<timestamp>` = format `YYYYMMDD_HHMMSS` - HTML references images via relative path `crops/<name>_crop.png` ## Input `$ARGUMENTS` is the path to the PDF file (local) or an HTTP/HTTPS URL. - If user provides a **URL**: download with curl first, then convert - If user provides a **local PDF path**: convert directly --- ## Workflow ### Phase 0 — Create Output Directory ```python import os, datetime pdf_stem = os.path.splitext(os.path.basename(pdf_path))[0] timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") out_dir = os.path.join(workspace, "web", f"{pdf_stem}_{timestamp}") ``` ```bash mkdir -p "<out_dir>/crops" ``` --- ### Phase 1 — Convert PDF Pages to Images (DPI 120) If the input is a URL, download it first: ```bash pdf_stem=$(basename "$ARGUMENTS" .pdf) curl -L -o "/tmp/${pdf_stem}.pdf" "$ARGUMENTS" ``` Then convert (pass either the downloaded path or the original local path): ```bash python {SKILL_DIR}/scripts/pdf_to_images.py "<pdf_path>" --dpi 120 ``` Outputs JSON to stdout: ```json [{"page": 1, "path": "/abs/path/page_001.png"}, ...] ``` Parse and store the full `page → path` map. --- ### Phase 2 — Read All Pages in Order View **all page images** sequentially before planning. Goal: pure understanding of the document's content, figures, and structure. While reading, note: - Title, authors, affiliations, venue, year - Abstract text (verbatim) - Key contributions - Paper/Code/Dataset links (arXiv, GitHub, etc.) - Figures, tables, diagrams — which pages, rough regions - Teaser/hero figure if present Do NOT plan sections yet — read everything first. --- ### Phase 3 — Plan Sections & Save outline.json Plan the website sections. Standard structure for academic papers (adapt as needed): | `section_id` | Purpose | |---|---| | `hero` | Title, authors, venue badge, link buttons | | `abstract` | Full abstract text | | `contributions` | 3–5 key contribution cards | | `method` | Architecture figure + method explanation | | `results` | Quantitative table + qualitative figures | | `conclusion` | Brief conclusion | | `citation` | BibTeX block | For each section that needs an image, identify: - Which page it comes from (the local page path from Phase 1) - A description of what the visual shows and why it belongs in this section **Save as `<out_dir>/outline.json`** using exactly this schema: ```json { "project_title": "Paper Title", "lang": "English", "authors": ["Author One", "Author Two"], "sections_plan": [ { "section_index": 1, "section_id": "hero", "title": "Hero", "content": "Title, authors, venue, teaser figure description", "required_images": [ { "url": "<local_page_path_from_phase1>", "visual_description": "Figure 1: teaser showing input-output examples", "usage_reason": "Hero section visual to immediately show the paper's output" } ] } ] } ``` Field notes: - `lang`: `"Chinese"` or `"English"` — match the PDF language - `required_images`: empty array `[]` if section needs no images - `url`: the **local file path** of the source page (from Phase 1 `path` field) - For images that need cropping, note the approximate region — exact crop boxes are determined in Phase 4 Write `outline.json` using the Write tool to `<out_dir>/outline.json`. --- ### Phase 4 — Crop Required Images (Grounding + Subagent) **IMPORTANT: You MUST delegate ALL cropping to a clean subagent using the Agent tool.** By this phase your context is very long (all page images + outline), which degrades visual coordinate accuracy. A fresh subagent with only the target image produces much more precise coordinates. **IMPORTANT: You MUST use the provided `{SKILL_DIR}/scripts/crop.py` script for ALL image cropping. Do NOT write your own cropping code, do NOT use PIL/Pillow directly, do NOT use any other method.** Read `outline.json`. Collect all crops needed, then launch **one subagent per source page** (or one per crop if pages differ). The subagent uses **grounding-style localization** — it views the image, locates the target element, and outputs a precise bounding box in normalized 0–999 coordinates. Use the Agent tool like this: ``` Agent tool call: description: "Grounding crop page N" prompt: | You are a visual grounding and cropping assistant. Your task is to precisely locate specified visual elements in a page image and crop them out. ## Grounding method Use visual grounding to locate each target: 1. Read the source image using the Read tool to view it 2. Identify the target element described below 3. Determine its bounding box as normalized coordinates in the 0–999 range: - 0 = left/top edge of the image - 999 = right/bottom edge of the image - These are thousandths, NOT pixels, NOT percentages (0–100) - Format: [x1, y1, x2, y2] where (x1,y1) is top-left, (x2,y2) is bottom-right - Example: [0, 0, 500, 500] = top-left quarter of the image 4. Be precise: tightly bound the target element with a small margin (~10–20 units) around it. Do NOT crop too wide or too narrow. ## Source image <page_image_path> ## Crops needed For each crop below, first do grounding (locate the element), then crop: 1. Name: "<descriptive_name>" Target: "<visual_description from outline.json>" Context: "<usage_reason from outline.json>" ## Crop command After determining the bounding box [X1, Y1, X2, Y2] for each target, run: ```bash python <SKILL_DIR>/scripts/crop.py \ --path "<page_image_path>" \ --box X1 Y1 X2 Y2 \ --name "<crop_name>" \ --out-dir "<out_dir>/crops" ``` ## Verification After each crop, READ the output image to visually verify the correct region was captured. If the crop missed the target or is too wide/narrow, adjust the coordinates and re-run crop.py. ## Output Report the final results as a list: - crop_name: <name>, file: <output_filename>, box: [X1, Y1, X2, Y2] ``` Replace `<page_image_path>`, `<SKILL_DIR>`, `<out_dir>`, and crop details with actual values from your context. The crop.py script outputs JSON: `{"path": "/abs/path/<name>_crop.png"}` Collect results from all subagents and build the mapping: `section_id → [crop filename, ...]` to reference in HTML. **Launch subagents for independent pages in parallel** when possible. Wait for all to complete before proceeding. --- ### Phase 5 — Measure Cropped Image Dimensions ```bash python3 -c " from PIL import Image; import os, json d = '<out_dir>/crops' sizes = {} for f in sorted(os.listdir(d)): if f.endswith('.png'): w, h = Image.open(os.path.join(d, f)).size sizes[f] = {'width': w, 'height': h, 'aspect': round(w/h, 2)} print(json.dumps(sizes, indent=2)) " ``` | Aspect ratio | Layout recommendation | |---|---| | **< 0.7** (tall/narrow) | `max-width: 400–500px`, centered | | **0.7 – 1.3** (square-ish) | `max-width: 600–700px` | | **> 1.3** (wide) | Full-width, `max-width: 100%` | | **> 2.0** (very wide, e.g. tables) | Full-width with horizontal scroll fallback | --- ### Phase 6 — Generate the Single-Page HTML **Step A — Write HTML** to `/tmp/website.html` - All `<img src="...">` must use **relative paths**: `crops/<name>_crop.png` - Do NOT use absolute paths **Step B — Save:** ```bash python {SKILL_DIR}/scripts/generate_web.py \ --html-file /tmp/website.html \ --title "<paper title>" \ --out-dir "<out_dir>/" ``` --- ## HTML Spec A **single self-contained HTML file** — embedded CSS, minimal vanilla JS only. No external JS frameworks. Google Fonts CDN is fine. **Page layout:** - Max content width: `900px`, centered, comfortable side padding - Sticky top nav with section anchor links + smooth scroll - Looks good at 1200px wide; readable at 768px **Typography:** - Two Google Fonts: one for headings, one for body/UI - Body: 17–18px, line-height 1.7 - Strong heading hierarchy (h1 >> h2 >> h3) **Visual style:** - If the user specifies a style, follow it exactly - Otherwise, infer an appropriate aesthetic from the paper's domain and tone (e.g. CV/ML paper → clean modern academic; systems paper → dark technical; humanities → warm editorial serif) - Define colors and fonts as CSS variables; no fixed palette or font choices are required **Section guidelines:** `hero`: - Large title (2–3rem), authors list with affiliation superscripts, venue badge pill - Link buttons: `[📄 Paper] [💻 Code] [🗄️ Dataset]` — grey out if no URL - Teaser figure below (if found) `abstract`: - Verbatim text with subtle left border accent `contributions`: - Cards in a 2–3 column CSS grid, each with Unicode symbol + heading + description `method`: - Full-width architecture figure (`<figure><img><figcaption>`) + prose explanation `results`: - **Quantitative table** as real `<table>` — use actual numbers from the PDF, best numbers **bolded** - **Qualitative figures** in a grid (2–4 images with captions) `conclusion`: - 2–3 paragraphs `citation`: - `<pre><code>` BibTeX block reconstructed from PDF metadata - "Copy" button using `navigator.clipboard` vanilla JS **Images:** - All `<img>` use relative paths: `crops/<name>_crop.png` - Add `loading="lazy"` and descriptive `alt` - Wrap in `<figure>` with `<figcaption>` **Animations (subtle only):** - Fade-in on scroll via `IntersectionObserver` + CSS transitions - Hover states on buttons/cards --- --- ## Quality Checklist - [ ] Output directory named `<pdf_stem>_<timestamp>/` - [ ] `outline.json` saved with valid WebPlan schema - [ ] All crops saved to `crops/` (local only) - [ ] All metadata (title, authors, venue, year) from the PDF - [ ] Abstract is verbatim - [ ] Quantitative table has real numbers from the paper - [ ] All crop images referenced via `crops/<name>_crop.png` - [ ] BibTeX block accurate and copyable - [ ] Nav anchors scroll to correct sections - [ ] `generate_web.py` called and confirmed success --- ## Language Match the PDF language. English paper → English website. Chinese paper → Chinese. No mixing.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)