hanhaonan02 Claude Fable 5.1 commited on
Commit
10726af
·
1 Parent(s): 38c5f38

Redesign demo UI, add five curated examples, trim README

Browse files

- New compact layout: hero, inputs left, poster + downloads right, example gallery
- Generation chains ZeroGPU steps automatically; downloads only appear when ready
- Five examples (NeurIPS 2022/2024, ICML 2022 x2, ICLR 2025) from the released checkpoint
- Remove placeholder example; LFS for png/pdf/zip; shorter README

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

.gitattributes CHANGED
@@ -1,36 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
36
- examples/gaussian_privacy/poster.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.zip filter=lfs diff=lfs merge=lfs -text
2
+ *.pdf filter=lfs diff=lfs merge=lfs -text
3
+ *.png filter=lfs diff=lfs merge=lfs -text
 
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__/
2
+ _stub/
3
+ *.bak
README.md CHANGED
@@ -9,52 +9,21 @@ python_version: 3.12
9
  app_file: app.py
10
  pinned: false
11
  startup_duration_timeout: 1h
12
- short_description: Turn a research paper into an editable academic poster
13
  ---
14
 
15
- # RAP — Paper to Poster
16
 
17
- Upload a paper PDF and a design instruction, then download an editable HTML
18
- poster, a PNG preview, and a ZIP of the selected figure/table/equation assets.
 
19
 
20
- Model: RL checkpoint-240, BF16, **no quantization**, no thinking; total context
21
- is limited to 16,384 tokens. The model revision is pinned in `app.py`.
22
- The private model repository is accessed through a server-side HF_TOKEN Secret;
23
- it is never included in the app source or browser output.
 
 
24
 
25
- ## How to use
26
-
27
- 1. Upload a text-based research PDF (at most 40 MB).
28
- 2. Set your instruction, canvas, columns and accent color. An optional advanced
29
- layout specification overrides the automatic one.
30
- 3. Click **Analyze PDF**. The training pipeline's LayoutLMv3 checkpoint extracts
31
- assets from up to 12 pages; section-aware budgeting preserves method/results.
32
- 4. Click **Generate / Continue**. Long outputs are generated over multiple
33
- ZeroGPU requests; partial previews are explicitly labeled incomplete.
34
- 5. Download the completed poster and check its scientific content and layout.
35
-
36
- A saved, actually generated example is available in the page without using GPU
37
- quota. It is clearly labeled as a saved output, not an interactive generation.
38
-
39
- ## Runtime and limitations
40
-
41
- - ZeroGPU xlarge (96 GB) is requested per generation; no dedicated paid GPU.
42
- - Visitors use their own Hugging Face quota. A complete long poster can require
43
- more time than a free visitor's daily quota. This is not an unlimited service.
44
- - Sign in before live generation. Anonymous quota is too small for the current
45
- 120-second xlarge reservation. Viewing the saved example needs no GPU quota.
46
- - Each request reserves 120 seconds; generation stops after approximately 100
47
- seconds and can resume without adding chat turns or changing the prompt.
48
- - The first PDF extraction after a cold start installs an isolated CPU parser
49
- environment, which may take several minutes. Subsequent PDFs reuse it.
50
- - The parser uses the original detector, but a new paper's automatically
51
- selected assets/layout plan differ from the GT-derived test-set specifications.
52
- This public demo is not an exact reproduction of the reported benchmark.
53
- - Scanned PDFs are not supported. Do not upload confidential papers.
54
- - Generated HTML is sanitized; scripts/external resources are disabled for
55
- rendering. Scientific statements may still be wrong: review all outputs.
56
- - Files are temporary and may disappear on restart. Model weights are unchanged.
57
-
58
- The original detector and copied training preprocessing modules are described
59
- in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md); upstream license text is
60
- included in [layout_vendor/LICENSE.md](layout_vendor/LICENSE.md).
 
9
  app_file: app.py
10
  pinned: false
11
  startup_duration_timeout: 1h
12
+ short_description: Turn a research paper PDF into an editable academic poster
13
  ---
14
 
15
+ # RAP · Paper to Poster
16
 
17
+ Upload a research paper PDF and get an editable academic poster. A 27B vision-language model
18
+ (Qwen3.8-27B, SFT + GRPO on real conference posters) reads the paper, selects figures and tables,
19
+ and writes the poster as standalone HTML; the Space renders it to PNG.
20
 
21
+ - Model: RL checkpoint-240, BF16, no quantization, 16K context.
22
+ - Generation runs in ZeroGPU steps of up to 120 s; a full poster usually takes 3–6 steps and the
23
+ demo chains them automatically. Sign in to Hugging Face to use your GPU quota.
24
+ - The examples on the page were generated by the same checkpoint with the default instruction and settings.
25
+ - Research demo: scanned PDFs are not supported, uploads are temporary, and every statement,
26
+ number and caption should be checked before a poster is used.
27
 
28
+ Third-party components (PDF-Extract-Kit LayoutLMv3 detector and the training preprocessing
29
+ modules) are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
demo_ui.py CHANGED
@@ -1,4 +1,4 @@
1
- """Public PDF -> assets -> model HTML -> rendered poster workflow."""
2
  import json
3
  import re
4
  import tempfile
@@ -7,153 +7,189 @@ from pathlib import Path
7
  import gradio as gr
8
  import torch
9
  import spaces
10
- from pdf_pipeline import prepare,chat,CANVASES,DEFAULT_INSTRUCTION
11
  from paper_sections import budget_body
12
  from poster_render import render
13
 
14
- ROOT=Path(__file__).resolve().parent
15
- ARTIFACTS=Path(tempfile.mkdtemp(prefix='rap_demo_'))
16
- WINDOW=16384
 
17
 
18
- def build(model,processor):
19
- def analyze(pdf,instruction,canvas,accent,columns,custom_spec,progress=gr.Progress()):
20
- if not pdf:raise gr.Error('Upload a research-paper PDF first.')
21
- progress(.05,desc='Extracting the first 12 pages with LayoutLMv3 (CPU)…')
22
- job=Path(tempfile.mkdtemp(prefix='paper_',dir=ARTIFACTS))
23
- try:
24
- p=prepare(pdf,job,instruction,canvas,accent,columns,custom_spec)
25
- progress(.85,desc='Fitting paper text and visual assets into the 16K context…')
26
- budget=22000
27
- while True:
28
- inputs=processor.apply_chat_template(chat(p),tokenize=True,add_generation_prompt=True,
29
- enable_thinking=False,return_dict=True,return_tensors='pt')
30
- n=inputs['input_ids'].shape[1]
31
- if n<=10000:break
32
- if budget<=3500:
33
- raise ValueError('The paper and visual assets exceed the demo input budget.')
34
- budget=max(3500,int(budget*.8))
35
- p['body'],_=budget_body(p['sections'],total=budget,abstract_missing=True)
36
- p['input_tokens']=n;p['continuation_ids']=[];p['raw']='';p['done']=False
37
- summary=f'**Ready:** {p["title"]}\n\n{len(p["assets"])} assets · {n:,} input tokens · layout specification included.'
38
- if p['stats'].get('total',0)<1000:
39
- summary+='\n\nWarning: only a small amount of paper text was extracted.'
40
- return p,summary,[(a['thumb'],f'{a["id"]} · page {a["page"]}') for a in p['assets']],p['spec'],None,None,None,''
41
- except (ValueError,RuntimeError) as e:raise gr.Error(str(e)) from None
 
 
 
 
 
42
 
43
- @spaces.GPU(size='xlarge',duration=120)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  def gpu_chunk(p):
45
- inputs=processor.apply_chat_template(chat(p),tokenize=True,add_generation_prompt=True,
46
- enable_thinking=False,return_dict=True,return_tensors='pt')
47
- prefix=p.get('continuation_ids',[])
48
  if prefix:
49
- inputs['input_ids']=torch.cat([inputs['input_ids'],torch.tensor([prefix],dtype=torch.long)],dim=1)
50
- inputs['attention_mask']=torch.ones_like(inputs['input_ids'])
51
- length=int(inputs['input_ids'].shape[1])
52
- remaining=WINDOW-length
53
- if remaining<=0:raise ValueError('The 16K context budget has been exhausted.')
54
- inputs=inputs.to('cuda')
55
- start=time.monotonic()
 
56
  with torch.inference_mode():
57
- output=model.generate(**inputs,max_new_tokens=min(8192,remaining),max_time=100,
58
- do_sample=False,use_cache=True,temperature=None,top_p=None,top_k=None,repetition_penalty=1.1)
59
- new_ids=output[0,length:].detach().cpu().tolist()
60
- all_ids=prefix+new_ids
61
- text=processor.decode(all_ids,skip_special_tokens=True)
62
- eos=model.generation_config.eos_token_id
63
- eos=eos if isinstance(eos,list) else [eos]
64
- complete=bool(re.search(r'</html\s*>',text,re.I))
65
- stopped=complete or bool(new_ids and new_ids[-1] in eos) or length+len(new_ids)>=WINDOW
66
- result={'ids':all_ids,'raw':text,'done':stopped,'complete':complete,'seconds':time.monotonic()-start,
67
- 'new_tokens':len(new_ids),'gpu':torch.cuda.get_device_name(),
68
- 'total_tokens':p['input_tokens']+len(all_ids)}
69
- del inputs,output
70
  return result
71
 
72
- def generate(p,progress=gr.Progress()):
73
- if not p:raise gr.Error('Click Analyze PDF first.')
74
- if p.get('done'):raise gr.Error('Generation has ended. Analyze again to start a new poster.')
75
- progress(.1,desc='Waiting for ZeroGPU xlarge, then generating HTML…')
76
- result=gpu_chunk(p)
77
- p=dict(p,continuation_ids=result['ids'],raw=result['raw'],done=result['done'])
78
- rate=result['new_tokens']/max(result['seconds'],.01)
79
- label=('Complete' if result['complete'] else 'Stopped — incomplete HTML; analyze again to retry'
80
- if p['done'] else 'Partial — click Generate / Continue to resume')
81
- status=(f'**{label}** · '
82
- f'{len(result["ids"]):,} generated tokens · {result["total_tokens"]:,} / 16,384 total · '
83
- f'{rate:.1f} tokens/s\n\nGPU: {result["gpu"]} · BF16 · checkpoint-240')
84
- progress(.9,desc='Resolving assets and rendering the poster…')
 
 
 
 
85
  try:
86
- png,html,archive,metadata=render(p['raw'],p)
87
- if metadata['unresolved_assets']:status+='\n\nWarning: some generated asset IDs were not available.'
88
- if not metadata['complete_html']:status+='\n\nThis preview is incomplete, not a finished poster.'
89
- return p,status,png,html,archive,p['raw']
90
- except ValueError as e:
91
- return p,status+'\n\n'+str(e),None,None,None,p['raw']
92
- except Exception as e:
93
- print('POSTER_RENDER_ERROR',type(e).__name__,flush=True)
94
- saved=Path(p['job'])/'output/poster.html'
95
- return p,status+'\n\nPreview rendering failed; the generated HTML is preserved.',None,\
96
- str(saved) if saved.exists() else None,None,p['raw']
97
 
98
- css='''#hero {padding:20px 24px;border-radius:18px;background:linear-gradient(115deg,#241541,#534180);color:white}
99
- #hero h1,#hero p {color:white} .gradio-container {max-width:1400px!important}'''
100
- with gr.Blocks(title='RAP · Paper to Poster',css=css,delete_cache=(3600,86400),
101
- theme=gr.themes.Soft(primary_hue='violet')) as demo:
102
- gr.Markdown('# RAP · Paper to Poster\nTurn a research paper into an editable academic poster.',elem_id='hero')
103
- gr.Markdown('**RL checkpoint-240 · BF16, no quantization · no thinking · 16K context**\n\n'
104
- 'Upload a text-based PDF (≤40 MB). We analyze its first 12 pages, extract figures/tables/equations, '
105
- 'then generate poster HTML. Sign in to Hugging Face before generating. '
106
- '**ZeroGPU xlarge uses 2× your personal GPU quota. A full poster usually needs several requests '
107
- 'and can exceed a free account’s daily allowance.** Partial previews are not finished posters. '
108
- 'The saved example below can be viewed without GPU quota.')
109
- state=gr.State()
110
- with gr.Row():
111
- with gr.Column(scale=4):
112
- pdf=gr.File(label='Research paper PDF',file_types=['.pdf'],type='filepath')
113
- instruction=gr.Textbox(label='Your design instruction',value=DEFAULT_INSTRUCTION,lines=5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  with gr.Row():
115
- canvas=gr.Dropdown(list(CANVASES),value='Landscape · 3:2',label='Canvas')
116
- columns=gr.Slider(1,4,value=3,step=1,label='Columns')
117
- accent=gr.ColorPicker(value='#584287',label='Accent color')
118
- with gr.Accordion('Advanced: custom layout specification',open=False):
119
- custom_spec=gr.Textbox(label='Optional — leave blank for an automatic layout plan',lines=5)
120
- analyze_btn=gr.Button('1 · Analyze PDF',variant='secondary')
121
- generate_btn=gr.Button('2 · Generate / Continue',variant='primary')
 
 
122
  with gr.Column(scale=7):
123
- status=gr.Markdown('Upload a PDF to begin.')
124
- poster=gr.Image(label='Rendered poster',type='filepath',interactive=False,height=600)
125
- with gr.Row():
126
- html_download=gr.File(label='Standalone HTML',interactive=False)
127
- bundle=gr.File(label='Poster + selected assets ZIP',interactive=False)
128
- with gr.Accordion('Extracted paper assets and layout plan',open=False):
129
- gallery=gr.Gallery(label='Assets offered to the model',columns=4,height=350)
130
- spec_view=gr.Textbox(label='Layout specification used for generation',lines=6,interactive=False)
131
- with gr.Accordion('Model-generated HTML',open=False):
132
- code=gr.Code(language='html',label='Model output (not executed in this page)',interactive=False)
133
- example=ROOT/'examples/gaussian_privacy'
134
- if (example/'poster.png').exists():
135
- with gr.Accordion('View a completed example — no GPU quota required',open=False):
136
- gr.Markdown('**Saved output, not a live generation.** This poster was generated on this '
137
- 'Space using RL checkpoint-240 in BF16 from '
138
- '[Gaussian Membership Inference Privacy](https://arxiv.org/abs/2306.07273). '
139
- 'Its figures are extracted from that paper. Please check scientific details before reuse.')
140
- gr.Image(value=str(example/'poster.png'),label='Completed model-generated example',
141
- interactive=False,height=600)
142
  with gr.Row():
143
- gr.File(value=str(example/'poster.html'),label='Example HTML',interactive=False)
144
- gr.File(value=str(example/'poster_bundle.zip'),label='Example poster and assets',interactive=False)
145
- gr.Markdown('Research demo: verify scientific statements, captions and layout before publication. '
146
- 'Uploaded papers are processed on the Space; do not upload confidential material. '
147
- 'Outputs are temporary and may disappear after a restart. '
148
- '[Source and third-party notices](https://huggingface.co/spaces/VincentHancoder/RAP/tree/main).')
149
- analyze_btn.click(analyze,[pdf,instruction,canvas,accent,columns,custom_spec],
150
- [state,status,gallery,spec_view,poster,html_download,bundle,code],api_name='analyze',concurrency_limit=1)
151
- generate_btn.click(generate,[state],[state,status,poster,html_download,bundle,code],
152
- api_name='generate',concurrency_limit=1)
153
- def reset_inputs():
154
- return None,'Inputs changed — click Analyze PDF again.',None,None,None,'',[],''
155
- for component in (pdf,instruction,canvas,accent,columns,custom_spec):
156
- component.change(reset_inputs,None,
157
- [state,status,poster,html_download,bundle,code,gallery,spec_view],
158
- queue=False,api_name=False)
159
- return demo,ARTIFACTS
 
 
 
 
 
1
+ """RAP demo UI: PDF -> assets -> model HTML -> rendered poster. Pipeline unchanged; UI redesigned 2026-10-15."""
2
  import json
3
  import re
4
  import tempfile
 
7
  import gradio as gr
8
  import torch
9
  import spaces
10
+ from pdf_pipeline import prepare, chat, CANVASES, DEFAULT_INSTRUCTION
11
  from paper_sections import budget_body
12
  from poster_render import render
13
 
14
+ ROOT = Path(__file__).resolve().parent
15
+ ARTIFACTS = Path(tempfile.mkdtemp(prefix='rap_demo_'))
16
+ WINDOW = 16384
17
+ MAX_CHUNKS = 8
18
 
19
+ CSS = '''
20
+ .gradio-container {max-width: 1360px !important; margin: 0 auto;}
21
+ #hero {padding: 26px 30px 22px; border-radius: 16px; margin-bottom: 6px;
22
+ background: linear-gradient(120deg, #1e1b4b 0%, #4c3d99 60%, #6d5bd0 100%); color: #fff;}
23
+ #hero h1 {margin: 0 0 6px; font-size: 30px; letter-spacing: .2px; color: #fff;}
24
+ #hero p {margin: 0; color: #e6e2ff; font-size: 15px;}
25
+ #hero a {color: #fff; text-decoration: underline;}
26
+ .badge {display: inline-block; margin: 10px 8px 0 0; padding: 3px 10px; border-radius: 999px;
27
+ background: rgba(255,255,255,.14); font-size: 12px; color: #fff;}
28
+ #status {min-height: 24px; font-size: 14px;}
29
+ #poster img {border-radius: 10px; box-shadow: 0 10px 30px rgba(20,16,60,.12);}
30
+ #examples .thumbnail-item {border-radius: 10px;}
31
+ footer {display: none !important;}
32
+ '''
33
+
34
+ def load_examples():
35
+ """examples/<name>/meta.json + paper.pdf + poster.png (+ poster.html, poster_bundle.zip)."""
36
+ out = []
37
+ for d in sorted((ROOT / 'examples').glob('*/')):
38
+ meta = d / 'meta.json'
39
+ if not meta.exists() or not (d / 'poster.png').exists() or not (d / 'paper.pdf').exists():
40
+ continue
41
+ m = json.loads(meta.read_text(encoding='utf-8'))
42
+ out.append({'dir': d, 'title': m.get('title', d.name), 'short': m.get('short', m.get('title', d.name))[:40], 'venue': m.get('venue', ''), 'instruction': m.get('instruction', DEFAULT_INSTRUCTION),
43
+ 'canvas': m.get('canvas', 'Landscape · 3:2'), 'columns': m.get('columns', 3), 'accent': m.get('accent', '#584287')})
44
+ return out
45
+
46
+ def fileout(path):
47
+ return gr.update(value=str(path), visible=True) if path else gr.update(value=None, visible=False)
48
 
49
+ def build(model, processor):
50
+ def analyze(pdf, instruction, canvas, accent, columns, custom_spec):
51
+ job = Path(tempfile.mkdtemp(prefix='paper_', dir=ARTIFACTS))
52
+ p = prepare(pdf, job, instruction, canvas, accent, columns, custom_spec)
53
+ budget = 22000
54
+ while True:
55
+ inputs = processor.apply_chat_template(chat(p), tokenize=True, add_generation_prompt=True,
56
+ enable_thinking=False, return_dict=True, return_tensors='pt')
57
+ n = inputs['input_ids'].shape[1]
58
+ if n <= 10000:
59
+ break
60
+ if budget <= 3500:
61
+ raise ValueError('The paper and its figures exceed the demo input budget.')
62
+ budget = max(3500, int(budget * .8))
63
+ p['body'], _ = budget_body(p['sections'], total=budget, abstract_missing=True)
64
+ p['input_tokens'] = n; p['continuation_ids'] = []; p['raw'] = ''; p['done'] = False
65
+ return p
66
+
67
+ @spaces.GPU(size='xlarge', duration=120)
68
  def gpu_chunk(p):
69
+ inputs = processor.apply_chat_template(chat(p), tokenize=True, add_generation_prompt=True,
70
+ enable_thinking=False, return_dict=True, return_tensors='pt')
71
+ prefix = p.get('continuation_ids', [])
72
  if prefix:
73
+ inputs['input_ids'] = torch.cat([inputs['input_ids'], torch.tensor([prefix], dtype=torch.long)], dim=1)
74
+ inputs['attention_mask'] = torch.ones_like(inputs['input_ids'])
75
+ length = int(inputs['input_ids'].shape[1])
76
+ remaining = WINDOW - length
77
+ if remaining <= 0:
78
+ raise ValueError('The 16K context budget has been exhausted.')
79
+ inputs = inputs.to('cuda')
80
+ start = time.monotonic()
81
  with torch.inference_mode():
82
+ output = model.generate(**inputs, max_new_tokens=min(8192, remaining), max_time=100, do_sample=False,
83
+ use_cache=True, temperature=None, top_p=None, top_k=None, repetition_penalty=1.1)
84
+ new_ids = output[0, length:].detach().cpu().tolist()
85
+ all_ids = prefix + new_ids
86
+ text = processor.decode(all_ids, skip_special_tokens=True)
87
+ eos = model.generation_config.eos_token_id
88
+ eos = eos if isinstance(eos, list) else [eos]
89
+ complete = bool(re.search(r'</html\s*>', text, re.I))
90
+ stopped = complete or bool(new_ids and new_ids[-1] in eos) or length + len(new_ids) >= WINDOW
91
+ result = {'ids': all_ids, 'raw': text, 'done': stopped, 'complete': complete, 'seconds': time.monotonic() - start,
92
+ 'new_tokens': len(new_ids), 'total_tokens': p['input_tokens'] + len(all_ids)}
93
+ del inputs, output
 
94
  return result
95
 
96
+ def generate_loop(p, gallery, progress):
97
+ """ZeroGPU chunks until the HTML is complete; yields (state, status, png, html, zip, gallery, spec, raw, continue-button)."""
98
+ tokens = ''
99
+ for k in range(1, MAX_CHUNKS + 1):
100
+ progress(min(.15 + .1 * k, .9), desc=f'Generating poster HTML (step {k})…')
101
+ try:
102
+ r = gpu_chunk(p)
103
+ except Exception as e: # noqa: BLE001 (quota exhausted, timeout, …)
104
+ yield (p, f"Paused after step {k - 1}: {str(e)[:200]}", None, None, None, gallery, p['spec'], p['raw'],
105
+ gr.update(visible=bool(p.get('continuation_ids'))))
106
+ return
107
+ p = dict(p, continuation_ids=r['ids'], raw=r['raw'], done=r['done'])
108
+ tokens = f"{len(r['ids']):,} tokens · {r['new_tokens'] / max(r['seconds'], .01):.0f} tok/s"
109
+ if r['complete'] or p['done']:
110
+ break
111
+ yield (p, f"Step {k} done · {tokens} · continuing…", None, None, None, gallery, p['spec'], p['raw'], gr.update(visible=False))
112
+ progress(.95, desc='Rendering…')
113
  try:
114
+ png, html, archive, meta = render(p['raw'], p)
115
+ except Exception as e: # noqa: BLE001
116
+ yield (p, f"The model stopped before a complete poster ({str(e)[:160]}).", None, None, None, gallery, p['spec'], p['raw'],
117
+ gr.update(visible=not p.get('done')))
118
+ return
119
+ note = '' if meta['complete_html'] else ' · output incomplete'
120
+ yield (dict(p, done=True), f"Done · {tokens}{note}", png, fileout(html), fileout(archive), gallery, p['spec'], p['raw'], gr.update(visible=False))
 
 
 
 
121
 
122
+ def run(pdf, instruction, canvas, accent, columns, custom_spec, progress=gr.Progress()):
123
+ """One click: parse the PDF, then generate until the poster is complete."""
124
+ if not pdf:
125
+ raise gr.Error('Upload a paper PDF first.')
126
+ progress(.05, desc='Reading the paper (LayoutLMv3 on CPU)…')
127
+ try:
128
+ p = analyze(pdf, instruction, canvas, accent, columns, custom_spec)
129
+ except (ValueError, RuntimeError) as e:
130
+ raise gr.Error(str(e)) from None
131
+ gallery = [(a['thumb'], f"{a['id']} · page {a['page']}") for a in p['assets']]
132
+ yield (p, f"Parsed **{p['title'][:90]}** · {len(p['assets'])} figures/tables found · generating…",
133
+ None, None, None, gallery, p['spec'], '', gr.update(visible=False))
134
+ yield from generate_loop(p, gallery, progress)
135
+
136
+ def cont(p, progress=gr.Progress()):
137
+ if not p or p.get('done'):
138
+ raise gr.Error('Nothing to continue. Click Generate poster.')
139
+ gallery = [(a['thumb'], f"{a['id']} · page {a['page']}") for a in p['assets']]
140
+ yield from generate_loop(p, gallery, progress)
141
+
142
+ examples = load_examples()
143
+ def pick_example(evt: gr.SelectData):
144
+ ex = examples[evt.index]; d = ex['dir']
145
+ html = d / 'poster.html'; archive = d / 'poster_bundle.zip'
146
+ return (str(d / 'paper.pdf'), ex['instruction'], ex['canvas'], ex['accent'], ex['columns'],
147
+ f"Example · {ex['title']}" + (f" ({ex['venue']})" if ex['venue'] else ''),
148
+ str(d / 'poster.png'), fileout(html if html.exists() else None), fileout(archive if archive.exists() else None))
149
+
150
+ with gr.Blocks(title='RAP · Paper to Poster', css=CSS, delete_cache=(3600, 86400),
151
+ theme=gr.themes.Soft(primary_hue='violet', neutral_hue='slate', radius_size='lg')) as demo:
152
+ gr.HTML('<div id="hero"><h1>RAP · Paper to Poster</h1>'
153
+ '<p>Upload a research paper and get an editable academic poster: a 27B vision-language model '
154
+ 'reads the paper, picks figures and tables, and writes the poster as standalone HTML.</p>'
155
+ '<span class="badge">Qwen3.8-27B · SFT + GRPO</span><span class="badge">BF16 · ZeroGPU</span>'
156
+ '<span class="badge">Output: HTML + PNG</span></div>')
157
+ with gr.Row(equal_height=False):
158
+ with gr.Column(scale=5, min_width=360):
159
+ pdf = gr.File(label='Paper PDF', file_types=['.pdf'], type='filepath', height=110)
160
+ instruction = gr.Textbox(label='Instruction', value=DEFAULT_INSTRUCTION, lines=3)
161
  with gr.Row():
162
+ canvas = gr.Dropdown(list(CANVASES), value='Landscape · 3:2', label='Canvas', scale=2)
163
+ columns = gr.Slider(1, 4, value=3, step=1, label='Columns', scale=2)
164
+ accent = gr.ColorPicker(value='#584287', label='Accent', scale=1)
165
+ with gr.Accordion('Custom layout specification (optional)', open=False):
166
+ custom_spec = gr.Textbox(show_label=False, placeholder='Leave empty to let the demo plan the layout.', lines=4)
167
+ run_btn = gr.Button('Generate poster', variant='primary', size='lg')
168
+ cont_btn = gr.Button('Continue generation', variant='secondary', visible=False)
169
+ gr.Markdown('<small>Generation runs in ZeroGPU steps of up to 120 s; a full poster usually takes 3–6 steps. '
170
+ 'Sign in to Hugging Face to use your GPU quota.</small>')
171
  with gr.Column(scale=7):
172
+ status = gr.Markdown('Upload a PDF or pick an example below.', elem_id='status')
173
+ poster = gr.Image(label='Poster', type='filepath', interactive=False, height=560, elem_id='poster', show_download_button=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  with gr.Row():
175
+ html_download = gr.File(label='Poster HTML', interactive=False, visible=False)
176
+ bundle = gr.File(label='HTML + figures (ZIP)', interactive=False, visible=False)
177
+ if examples:
178
+ gr.Markdown('### Examples')
179
+ ex_gallery = gr.Gallery(value=[(str(e['dir'] / 'poster.png'), e['short']) for e in examples], columns=5, rows=1,
180
+ height=230, object_fit='contain', show_label=False, elem_id='examples', allow_preview=False)
181
+ with gr.Accordion('Details: extracted figures, layout plan, raw HTML', open=False):
182
+ gallery = gr.Gallery(label='Figures and tables offered to the model', columns=6, height=220)
183
+ spec_view = gr.Textbox(label='Layout specification', lines=4, interactive=False)
184
+ code = gr.Code(language='html', label='Model output', interactive=False)
185
+ with gr.Accordion('Notes', open=False):
186
+ gr.Markdown('Research demo. Scanned PDFs are not supported; uploads are processed on this Space and are temporary. '
187
+ 'Check every statement, number and caption before using a poster. '
188
+ '[Source and third-party notices](https://huggingface.co/spaces/VincentHancoder/RAP/tree/main).')
189
+ state = gr.State()
190
+ outs = [state, status, poster, html_download, bundle, gallery, spec_view, code, cont_btn]
191
+ run_btn.click(run, [pdf, instruction, canvas, accent, columns, custom_spec], outs, api_name='generate', concurrency_limit=1)
192
+ cont_btn.click(cont, [state], outs, api_name='continue', concurrency_limit=1)
193
+ if examples:
194
+ ex_gallery.select(pick_example, None, [pdf, instruction, canvas, accent, columns, status, poster, html_download, bundle], api_name=False)
195
+ return demo, ARTIFACTS
examples/gaussian_privacy/poster.html DELETED
The diff for this file is too large to render. See raw diff
 
examples/iclr_2025_5006/meta.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "Matcha: Mitigating Graph Structure Shifts with Test-Time Adaptation",
3
+ "venue": "ICLR 2025",
4
+ "paper_id": "ICLR_2025_5006",
5
+ "short": "Matcha · ICLR 2025",
6
+ "instruction": "Create a clear academic poster with a strong title, a short motivation, the main method, key quantitative results, and takeaways. Prioritize the most informative figures. Use concise text, readable fonts, and generous spacing. Do not invent results.",
7
+ "canvas": "Landscape · 3:2",
8
+ "columns": 3,
9
+ "accent": "#584287"
10
+ }
examples/iclr_2025_5006/paper.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b602b23c819046ab875dca439c16e512d799c1c89f3340e5bf11e24c51723198
3
+ size 3939092
examples/{gaussian_privacy → iclr_2025_5006}/poster.png RENAMED
File without changes
examples/icml_2022_16583/meta.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "Adaptive Second Order Coresets for Data-efficient Machine Learning",
3
+ "venue": "ICML 2022",
4
+ "paper_id": "ICML_2022_16583",
5
+ "short": "AdaCore · ICML 2022",
6
+ "instruction": "Create a clear academic poster with a strong title, a short motivation, the main method, key quantitative results, and takeaways. Prioritize the most informative figures. Use concise text, readable fonts, and generous spacing. Do not invent results.",
7
+ "canvas": "Landscape · 3:2",
8
+ "columns": 3,
9
+ "accent": "#584287"
10
+ }
examples/icml_2022_16583/paper.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce8ae89247f386c4fa0f7bc72a70ab0eda1cf3cd0ba3e4371f8922c3355ea01d
3
+ size 1586267
examples/icml_2022_16583/poster.html ADDED
The diff for this file is too large to render. See raw diff
 
examples/{gaussian_privacy/poster_bundle.zip → icml_2022_16583/poster.png} RENAMED
File without changes
examples/icml_2022_18241/meta.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "Discovering Generalizable Spatial Goal Representations via Graph-based Active Reward Learning",
3
+ "venue": "ICML 2022",
4
+ "paper_id": "ICML_2022_18241",
5
+ "short": "GEM · ICML 2022",
6
+ "instruction": "Create a clear academic poster with a strong title, a short motivation, the main method, key quantitative results, and takeaways. Prioritize the most informative figures. Use concise text, readable fonts, and generous spacing. Do not invent results.",
7
+ "canvas": "Landscape · 3:2",
8
+ "columns": 3,
9
+ "accent": "#584287"
10
+ }
examples/icml_2022_18241/paper.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e3ca777da75c8b2f5e263786baec579cb877036fe5c0067d39aed04f8a921b60
3
+ size 1344934
examples/icml_2022_18241/poster.png ADDED

Git LFS Details

  • SHA256: ca64c94f1112643f970bbd9780aa20b0b2a2261621ed6120705038086a433a9c
  • Pointer size: 131 Bytes
  • Size of remote file: 279 kB
examples/neurips_2022_63389/meta.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "Optimized Global Perturbation Attacks For Brain Tumour ROI Extraction From Binary Classification Models",
3
+ "venue": "NeurIPS 2022",
4
+ "paper_id": "NeurIPS_2022_63389",
5
+ "short": "Global Perturbation · NeurIPS 2022",
6
+ "instruction": "Create a clear academic poster with a strong title, a short motivation, the main method, key quantitative results, and takeaways. Prioritize the most informative figures. Use concise text, readable fonts, and generous spacing. Do not invent results.",
7
+ "canvas": "Landscape · 3:2",
8
+ "columns": 3,
9
+ "accent": "#584287"
10
+ }
examples/neurips_2022_63389/paper.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:21c8594705d06c371d6c320bf1d720bba3fcb4d375930ff57a88ed148bc53b07
3
+ size 267114
examples/neurips_2022_63389/poster.html ADDED
The diff for this file is too large to render. See raw diff
 
examples/neurips_2022_63389/poster.png ADDED

Git LFS Details

  • SHA256: abfb20aace9f6d84cf277d058c7dc1e9ad1f52bd2ee0001363b06c4d1a1837b8
  • Pointer size: 131 Bytes
  • Size of remote file: 498 kB
examples/neurips_2024_4032/meta.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "DiffuLT: Diffusion for Long-tail Recognition Without External Knowledge",
3
+ "venue": "NeurIPS 2024",
4
+ "paper_id": "NeurIPS_2024_4032",
5
+ "short": "DiffuLT · NeurIPS 2024",
6
+ "instruction": "Create a clear academic poster with a strong title, a short motivation, the main method, key quantitative results, and takeaways. Prioritize the most informative figures. Use concise text, readable fonts, and generous spacing. Do not invent results.",
7
+ "canvas": "Landscape · 3:2",
8
+ "columns": 3,
9
+ "accent": "#584287"
10
+ }
examples/neurips_2024_4032/paper.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:25e258b685407444c4f181dc019427dc2ad6d5590eab72af7ebddc203f1e1da0
3
+ size 2809890
examples/neurips_2024_4032/poster.png ADDED

Git LFS Details

  • SHA256: 892625d25d37b38b92a9dc02623640bf18f7ca077135edfa9d90ec9b90bf58d5
  • Pointer size: 131 Bytes
  • Size of remote file: 404 kB