import json
import tempfile
import html
import os
import sys
from pathlib import Path
ROOT_DIR = Path(__file__).resolve().parent
SRC_DIR = ROOT_DIR / "src"
sys.path.insert(0, str(ROOT_DIR))
sys.path.insert(0, str(SRC_DIR))
print("DEBUG ROOT_DIR:", ROOT_DIR)
print("DEBUG SRC_DIR:", SRC_DIR)
print("DEBUG SRC_EXISTS:", SRC_DIR.exists())
print("DEBUG PIPELINE_EXISTS:", (SRC_DIR / "paper2lab" / "inference" / "pipeline.py").exists())
print("DEBUG sys.path[:5]:", sys.path[:5])
import gradio as gr
try:
from paper2lab.inference.pipeline import PaperPipeline
print("DEBUG PaperPipeline import: OK")
except Exception as e:
print("DEBUG PaperPipeline import failed:", repr(e))
PaperPipeline = None
try:
from paper2lab.rag.qa import answer_from_pipeline_result
print("DEBUG RAG import: OK")
except Exception as e:
print("DEBUG RAG import failed:", repr(e))
answer_from_pipeline_result = None
APP_NAME = "Paper2Lab"
def pretty_json(data):
return json.dumps(data or {}, indent=2, ensure_ascii=False)
def as_list(value):
if not value:
return []
if isinstance(value, list):
return value
return [value]
def bullet_list(items, empty="_Not found._"):
items = as_list(items)
if not items:
return empty
return "\n".join(f"- {x}" for x in items)
def clean_display_text(text, max_len=95):
text = str(text or "").replace("\n", " ").strip()
while " " in text:
text = text.replace(" ", " ")
if len(text) > max_len:
return text[:max_len].rstrip() + "..."
return text
def chip_list(items, empty="Not found"):
items = as_list(items)
if not items:
return f"{empty}"
html = ""
for x in items[:6]:
clean = clean_display_text(x, max_len=70)
html += f"{clean}"
return html
def get_card(result):
return result.get("paper_card_final") or result.get("paper_card") or result
def quality_warnings(card):
warnings = []
rq = card.get("research_question", "")
if rq and ("BERTis" in rq or len(rq.split()) < 5):
warnings.append("Research question may contain PDF spacing noise.")
datasets = as_list(card.get("datasets_or_data_sources"))
if any(len(str(x)) > 220 for x in datasets):
warnings.append("Dataset extraction contains long noisy candidates.")
for field in ["contributions", "key_findings", "limitations"]:
if not card.get(field):
warnings.append(f"{field} is empty.")
return warnings
def build_quick_summary_html(result):
card = get_card(result)
warnings = quality_warnings(card)
status = "Ready"
if result:
status = f"{max(0, 6 - len(warnings))} / 6 fields complete"
return f"""
๐Quick summary
{status}
Title
{card.get("title", "Untitled paper")}
Research question
{card.get("research_question", "Not found")}
Datasets
{chip_list(card.get("datasets_or_data_sources"))}
Models
{chip_list(card.get("models_or_methods"))}
Key findings
{bullet_list(card.get("key_findings")).replace("- ", "โข ")}
"""
def build_paper_summary_md(result):
card = get_card(result)
warnings = quality_warnings(card)
warning_md = ""
if warnings:
warning_md = "## โ ๏ธ Extraction Quality Warnings\n" + "\n".join(f"- {w}" for w in warnings)
return f"""
# Structured Paper Summary
**Title:** {card.get("title", "Untitled paper")}
**Field:** {card.get("field", "Unknown")}
{warning_md}
## Research Question
{card.get("research_question", "_Not found._")}
## Contributions
{bullet_list(card.get("contributions"))}
## Methodology
{bullet_list(card.get("methodology"))}
## Datasets / Data Sources
{bullet_list(card.get("datasets_or_data_sources"))}
## Models / Methods
{bullet_list(card.get("models_or_methods"))}
## Metrics / Measurements
{bullet_list(card.get("metrics_or_measurements"))}
## Key Findings
{bullet_list(card.get("key_findings"))}
## Limitations
{bullet_list(card.get("limitations"))}
"""
def build_lab_md(result):
card = get_card(result)
kit = card.get("lab_starter_kit") or result.get("lab_starter_kit") or {}
return f"""
# Lab Starter Kit
**Starter type:** `{kit.get("starter_type", card.get("paper_type", "unknown"))}`
## Project Structure
{bullet_list(kit.get("project_structure"))}
## Requirements
{bullet_list(kit.get("requirements_txt"))}
## Dataset Plan
{bullet_list(kit.get("dataset_plan") or kit.get("required_data"))}
## Suggested Experiments
{bullet_list(kit.get("suggested_experiments") or kit.get("experiment_checklist"))}
## Evaluation Plan
{bullet_list(kit.get("evaluation_plan"))}
## Reproducibility Risks
{bullet_list(kit.get("reproducibility_risks") or card.get("missing_reproducibility_info"))}
"""
def build_evidence_md(result):
figs = result.get("figures_and_tables", [])
card = get_card(result)
lines = ["# Evidence Viewer"]
if figs:
lines.append("## Figures and Tables")
for item in figs[:12]:
lines.append(
f"- **{item.get('label', 'Item')}** โ page {item.get('page_number', '?')}: "
f"{item.get('summary', 'No summary')}"
)
lines.append("\n## Extracted Evidence Fields")
for field in [
"methodology",
"datasets_or_data_sources",
"models_or_methods",
"metrics_or_measurements",
"key_findings",
]:
lines.append(f"\n### {field}")
lines.append(bullet_list(card.get(field)))
return "\n".join(lines)
def build_advanced_md(result):
card = get_card(result)
metadata = card.get("metadata", {}) if isinstance(card, dict) else {}
if not isinstance(metadata, dict):
metadata = {}
quality = metadata.get("quality", {})
if not isinstance(quality, dict):
quality = {}
repro = card.get("reproducibility_score", {}) if isinstance(card, dict) else {}
if not isinstance(repro, dict):
repro = {}
selection = result.get("auto_selection", {}).get("selection_report", {})
if not isinstance(selection, dict):
selection = {}
md = []
md.append("# โ๏ธ Advanced Analysis")
md.append("")
md.append("## Extraction Quality")
md.append("")
md.append("| Metric | Value |")
md.append("|---|---|")
md.append(f"| Quality Score | {quality.get('quality_score', 'N/A')} |")
md.append(f"| Sections Found | {quality.get('num_sections', 'N/A')} |")
md.append(f"| References Found | {quality.get('num_references', metadata.get('references_count', 'N/A'))} |")
md.append(f"| Tables Found | {quality.get('num_tables', 'N/A')} |")
md.append(f"| Captions Found | {quality.get('num_captions', 'N/A')} |")
md.append("")
md.append("---")
md.append("")
md.append(f"**Reproducibility Level:** {repro.get('level', 'unknown')}")
md.append("")
md.append(f"**Score:** {repro.get('score', 'N/A')}")
md.append("")
md.append("### Detected Items")
md.append(bullet_list(repro.get("detected_items")))
md.append("")
md.append("### Missing Items")
md.append(bullet_list(repro.get("missing_items")))
md.append("")
md.append("---")
md.append("")
md.append("## Selection Report")
if selection.get("fields"):
md.append("Field-level local vs Nemotron selection was completed.")
md.append("")
md.append("```json")
md.append(pretty_json(selection))
md.append("```")
else:
md.append(
"No field-level comparison available for this paper. "
"The final card was generated from the best available extraction pipeline."
)
return "\n".join(md)
def safe_html(value):
return html.escape(str(value or ""), quote=True)
def short_card_text(value, max_len=48):
text = clean_display_text(value, max_len=max_len)
return safe_html(text)
def build_lab_cards_html(result):
card = get_card(result)
kit = card.get("lab_starter_kit") or result.get("lab_starter_kit") or {}
reqs = as_list(kit.get("requirements_txt"))
risks = as_list(
kit.get("reproducibility_risks")
or card.get("missing_reproducibility_info")
)
experiments = as_list(
kit.get("suggested_experiments")
or kit.get("experiment_checklist")
)
starter_type = kit.get("starter_type", card.get("paper_type", "unknown"))
tools = ", ".join(clean_display_text(x, max_len=28) for x in reqs[:3]) if reqs else "Not found"
return f"""
Starter type
{short_card_text(starter_type)}
Tools
{short_card_text(tools, max_len=58)}
Experiments
{len(experiments)} detected
Risks
{len(risks)} issues
"""
def analyze_paper(pdf_file, refinement_mode):
if pdf_file is None:
raise gr.Error("Please upload a PDF first.")
if PaperPipeline is None:
raise gr.Error("PaperPipeline import failed. Run from the project root or install your package.")
pdf_path = pdf_file if isinstance(pdf_file, str) else pdf_file.name
pipeline = PaperPipeline(refinement_mode=refinement_mode)
result = pipeline.run(pdf_path)
print("PDF PATH:", pdf_path)
print("MODE:", refinement_mode)
print("FINAL DATASETS:", result.get("paper_card_final", {}).get("datasets_or_data_sources"))
print("GET_CARD DATASETS:", get_card(result).get("datasets_or_data_sources"))
card = get_card(result)
if not card.get("datasets_or_data_sources"):
roadmap = card.get("reproduction_roadmap") or {}
kit = card.get("lab_starter_kit") or {}
fallback_datasets = []
if isinstance(roadmap, dict):
fallback_datasets += roadmap.get("datasets") or []
if isinstance(kit, dict):
fallback_datasets += kit.get("dataset_plan") or []
if fallback_datasets:
card["datasets_or_data_sources"] = list(dict.fromkeys(fallback_datasets))
result["paper_card_final"] = card
paper_md = build_paper_summary_md(result)
lab_md = build_lab_md(result)
evidence_md = build_evidence_md(result)
advanced_md = build_advanced_md(result)
quick_html = build_quick_summary_html(result)
lab_cards = build_lab_cards_html(result)
tmp = Path(tempfile.mkdtemp())
json_path = tmp / "paper2lab_output.json"
md_path = tmp / "paper2lab_report.md"
json_path.write_text(pretty_json(result), encoding="utf-8")
md_path.write_text(paper_md + "\n\n---\n\n" + lab_md, encoding="utf-8")
return (
result,
quick_html,
paper_md,
lab_md,
evidence_md,
advanced_md,
lab_cards,
card,
str(json_path),
str(md_path),
)
def ask_paper_question(result, question):
if not result:
return "โ ๏ธ Analyze a paper first.", {}
if not question or not str(question).strip():
return "โ ๏ธ Please enter a question.", {}
question_text = str(question).strip()
q = question_text.lower()
card = get_card(result)
def md_items(title, items):
items = as_list(items)
items = [x for x in items if str(x).strip()]
if not items:
return f"**{title}:**\n\n_Not found in the structured paper card._"
return f"**{title}:**\n\n" + "\n".join(f"- {x}" for x in items[:8])
# Fast structured answers for demo-critical questions
if any(k in q for k in ["dataset", "data source", "corpus", "benchmark"]):
return md_items("Datasets / data sources", card.get("datasets_or_data_sources")), {
"source": "structured_card",
"field": "datasets_or_data_sources",
}
if any(k in q for k in ["model", "architecture", "proposed"]):
rq = card.get("research_question", "")
if rq:
return (
f"**Proposed model:**\n\n- {rq}",
{
"source": "structured_card",
"field": "research_question",
},
)
if any(k in q for k in ["finding", "result", "conclusion", "contribution"]):
return md_items("Key findings", card.get("key_findings") or card.get("contributions")), {
"source": "structured_card",
"field": "key_findings",
}
if any(k in q for k in ["metric", "score", "performance", "evaluation"]):
return md_items("Metrics / measurements", card.get("metrics_or_measurements")), {
"source": "structured_card",
"field": "metrics_or_measurements",
}
if any(k in q for k in ["limitation", "risk", "missing"]):
return md_items("Limitations / reproducibility risks", card.get("limitations")), {
"source": "structured_card",
"field": "limitations",
}
if any(k in q for k in ["reproduce", "reproduction", "roadmap", "steps"]):
roadmap = card.get("reproduction_roadmap") or {}
steps = []
if isinstance(roadmap, dict):
steps = roadmap.get("experimental_steps") or roadmap.get("missing_for_reproduction") or []
return md_items("Reproduction roadmap", steps), {
"source": "structured_card",
"field": "reproduction_roadmap",
}
if answer_from_pipeline_result is None:
return (
"โ ๏ธ RAG module import failed. Check that `paper2lab.rag.qa` is available "
"and dependencies are installed.",
{},
)
try:
qa = answer_from_pipeline_result(
pipeline_result=result,
question=question_text,
top_k=5,
embedder_backend="local",
)
answer = qa.get("answer", "No answer found.")
evidence = qa.get("evidence", [])
evidence_md = "\n\n## Evidence\n"
for i, ev in enumerate(evidence, 1):
page = ev.get("page_start") or ev.get("page_number") or "?"
title = ev.get("title", "Evidence")
text = ev.get("text", "")
evidence_md += f"\n**{i}. {title} โ page {page}**\n\n> {text[:700]}\n"
return answer + evidence_md, qa
except Exception as exc:
return f"โ RAG error: {exc}", {}
CSS = """
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Paper2Lab UI โ fixed layout
Fixes:
- no duplicate upload holder/native file input
- wider hero with Paper2Lab title
- clean 2-column app layout
- lab card placed under Quick Summary
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
body,
.gradio-container {
background: #eef2f7 !important;
color: #111827 !important;
}
* {
box-sizing: border-box;
}
footer {
display: none !important;
}
.gradio-container {
max-width: none !important;
padding-top: 28px !important;
}
/* Main page width */
.app-shell {
width: min(1220px, 94vw);
margin: 0 auto;
}
/* Remove unwanted Gradio block styling only inside our custom layout */
.top-layout .block,
.top-layout .gr-group,
.top-layout .gr-box,
.top-layout .gr-panel,
.result-stack .block,
.result-stack .gr-group,
.result-stack .gr-box,
.result-stack .gr-panel {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
margin: 0 !important;
overflow: visible !important;
}
/* โโโโโโโโโโโโโโโโโ HERO โโโโโโโโโโโโโโโโโ */
.hero {
position: relative;
overflow: hidden;
min-height: 245px;
border-radius: 24px;
padding: 34px 48px;
margin: 0 auto 28px;
background: linear-gradient(135deg, #0f172a 0%, #18253f 48%, #252659 100%);
border: 1px solid rgba(255,255,255,.08);
box-shadow: 0 22px 60px rgba(15,23,42,.18);
}
.hero::before {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(circle at 15% 25%, rgba(34,211,238,.14), transparent 30%),
radial-gradient(circle at 78% 26%, rgba(124,58,237,.22), transparent 35%);
pointer-events: none;
}
.hero-content {
position: relative;
z-index: 2;
max-width: 920px;
}
.logo-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 14px;
}
.logo-mark {
width: 32px;
height: 32px;
display: inline-grid;
place-items: center;
border-radius: 10px;
background: rgba(103,232,249,.14);
border: 1px solid rgba(103,232,249,.22);
font-size: 18px;
}
.logo-text {
color: #ffffff !important;
font-size: 18px;
font-weight: 950;
letter-spacing: -0.035em;
}
.kicker {
color: #67e8f9 !important;
text-transform: uppercase;
letter-spacing: .16em;
font-size: 11px;
font-weight: 900;
margin-bottom: 8px;
}
.hero h1 {
max-width: 960px;
color: #ffffff !important;
font-size: 38px;
line-height: 1.05;
letter-spacing: -0.052em;
margin: 0 0 14px;
}
.hero h1 span {
color: #a78bfa !important;
}
.hero p {
max-width: 760px;
color: rgba(255,255,255,.86) !important;
font-size: 15px;
line-height: 1.62;
margin: 0 0 22px;
}
.hero-badges {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hero-badge {
padding: 8px 13px;
border-radius: 10px;
background: rgba(255,255,255,.12);
color: #ffffff !important;
font-weight: 850;
font-size: 12px;
border: 1px solid rgba(255,255,255,.18);
}
/* โโโโโโโโโโโโโโโโโ TWO COLUMN LAYOUT โโโโโโโโโโโโโโโโโ */
.top-layout {
display: grid !important;
grid-template-columns: 340px minmax(0, 1fr) !important;
gap: 28px !important;
align-items: start !important;
}
.control-stack {
background: #ffffff !important;
border: 1px solid #e2e8f0 !important;
border-radius: 24px !important;
padding: 24px !important;
box-shadow: 0 14px 38px rgba(15,23,42,.06) !important;
display: flex !important;
flex-direction: column !important;
gap: 18px !important;
}
.result-stack {
display: flex !important;
flex-direction: column !important;
gap: 26px !important;
background: transparent !important;
border: none !important;
padding: 0 !important;
}
/* โโโโโโโโโโโโโโโโโ LEFT PANEL โโโโโโโโโโโโโโโโโ */
.panel-title {
font-size: 16px;
font-weight: 900;
color: #111827 !important;
margin: 0;
padding-bottom: 10px;
border-bottom: 1px solid #f1f5f9;
}
/* Button */
.gradio-container button.primary,
.gradio-container button[class*="primary"] {
width: 100% !important;
min-height: 50px !important;
border: none !important;
border-radius: 15px !important;
background: linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%) !important;
color: #ffffff !important;
font-weight: 950 !important;
font-size: 15px !important;
box-shadow: 0 12px 26px rgba(124,58,237,.28) !important;
}
.gradio-container button.primary *,
.gradio-container button[class*="primary"] * {
color: #ffffff !important;
}
.tip-box {
padding: 12px 14px;
border-radius: 14px;
background: #fafbff;
border: 1px solid #e5e7eb;
color: #4b5563 !important;
font-size: 12px;
font-weight: 750;
}
/* โโโโโโโโโโโโโโโโโ QUICK SUMMARY โโโโโโโโโโโโโโโโโ */
.summary-card {
background: #ffffff !important;
border: 1px solid #e2e8f0 !important;
border-radius: 22px !important;
overflow: hidden !important;
box-shadow: 0 14px 38px rgba(15,23,42,.06) !important;
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 17px 22px;
border-bottom: 1px solid #f1f5f9;
}
.card-head b {
color: #111827 !important;
font-size: 16px;
}
.icon {
display: inline-grid;
place-items: center;
width: 28px;
height: 28px;
margin-right: 9px;
border-radius: 9px;
background: #ede9fe;
font-size: 15px;
}
.status-pill {
background: #dcfce7;
color: #047857 !important;
padding: 6px 13px;
border-radius: 999px;
font-weight: 900;
font-size: 12px;
}
.summary-row {
display: grid;
grid-template-columns: 140px minmax(0, 1fr);
gap: 16px;
padding: 15px 22px;
border-bottom: 1px solid #f8fafc;
font-size: 14px;
}
.label {
color: #6b7280 !important;
font-weight: 850;
}
.value {
color: #111827 !important;
line-height: 1.55;
overflow-wrap: anywhere;
}
.highlight {
background: #f5f3ff;
border-left: 3px solid #7c3aed;
border-radius: 0 10px 10px 0;
padding: 10px 12px;
color: #312e81 !important;
}
.chip {
display: inline-block;
margin: 3px 5px 3px 0;
padding: 6px 10px;
border-radius: 999px;
background: #ede9fe;
color: #4c1d95 !important;
border: 1px solid #c4b5fd;
font-weight: 800;
font-size: 12px;
}
.muted {
color: #9ca3af !important;
}
/* โโโโโโโโโโโโโโโโโ LAB READINESS โโโโโโโโโโโโโโโโโ */
.lab-board {
background: #ffffff !important;
border: 1px solid #e2e8f0 !important;
border-radius: 22px !important;
padding: 18px !important;
box-shadow: 0 14px 38px rgba(15,23,42,.06) !important;
}
.lab-board-header {
display: flex;
align-items: center;
gap: 12px;
background: #f8f9fc;
border: 1px solid #ebebf0;
border-radius: 14px;
padding: 14px 18px;
margin-bottom: 16px;
color: #0f172a !important;
font-size: 18px;
font-weight: 950;
letter-spacing: -0.03em;
}
.lab-board-header * {
color: #0f172a !important;
}
.lab-emoji {
font-size: 22px;
line-height: 1;
flex: 0 0 auto;
}
.lab-grid-2 {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.lab-tile {
min-height: 108px;
padding: 18px 20px;
border-radius: 16px;
border: 1px solid transparent;
overflow: hidden;
}
.lab-tile span {
display: block;
margin-bottom: 10px;
font-size: 12px;
line-height: 1.2;
font-weight: 900;
letter-spacing: .04em;
text-transform: uppercase;
}
.lab-tile b {
display: block;
font-size: 23px;
line-height: 1.15;
font-weight: 950;
letter-spacing: -0.035em;
overflow-wrap: break-word;
}
.lab-green {
background: #ecfdf5 !important;
color: #065f46 !important;
border-color: #a7f3d0 !important;
}
.lab-dark {
background:
radial-gradient(circle at 84% 18%, rgba(124,58,237,.28), transparent 36%),
#1e1b4b !important;
color: #ffffff !important;
border-color: #3730a3 !important;
}
.lab-purple {
background: #f5f3ff !important;
color: #4c1d95 !important;
border-color: #ddd6fe !important;
}
.lab-red {
background: #fff1f2 !important;
color: #9f1239 !important;
border-color: #fecdd3 !important;
}
.lab-green *,
.lab-dark *,
.lab-purple *,
.lab-red * {
color: inherit !important;
}
/* โโโโโโโโโโโโโโโโโ TABS โโโโโโโโโโโโโโโโโ */
.tabs-shell {
margin-top: 28px;
}
.gradio-container .tab-nav {
gap: 6px !important;
border-bottom: 1px solid #cbd5e1 !important;
}
.gradio-container .tab-nav button {
font-size: 14px !important;
font-weight: 750 !important;
color: #6b7280 !important;
padding: 11px 18px !important;
background: transparent !important;
border: none !important;
border-bottom: 3px solid transparent !important;
white-space: nowrap !important;
}
.gradio-container .tab-nav button.selected {
color: #6d28d9 !important;
border-bottom-color: #7c3aed !important;
}
.gradio-container .tabs > .block,
.gradio-container .tabitem {
background: #ffffff !important;
border: 1px solid #e2e8f0 !important;
border-radius: 18px !important;
box-shadow: 0 8px 24px rgba(15,23,42,.04) !important;
overflow: hidden !important;
}
/* General text - exclude hero */
.gradio-container .markdown:not(.hero):not(.hero *),
.gradio-container p:not(.hero p),
.gradio-container li,
.gradio-container td,
.gradio-container th,
.gradio-container label,
.gradio-container textarea,
.gradio-container input {
color: #111827 !important;
}
/* โโโโโโโโโโโโโโโโโ RESPONSIVE โโโโโโโโโโโโโโโโโ */
@media (max-width: 900px) {
.app-shell {
width: min(94vw, 760px);
}
.hero {
min-height: auto;
padding: 26px 22px;
}
.hero h1 {
font-size: 28px;
}
.hero p {
font-size: 14px;
}
.top-layout {
grid-template-columns: 1fr !important;
}
.summary-row {
grid-template-columns: 1fr;
gap: 6px;
}
.lab-grid-2 {
grid-template-columns: 1fr;
}
}
/* "Analyze Paper" text inside Quick Summary */
.summary-card .value,
.summary-card .value *,
.summary-row .value,
.summary-row .value * {
color: #111827 !important;
opacity: 1 !important;
visibility: visible !important;
}
/* Quick summary text */
.summary-card .value,
.summary-card .value *,
.summary-row .value,
.summary-row .value * {
color: #111827 !important;
opacity: 1 !important;
-webkit-text-fill-color: #111827 !important;
}
/* Tabs text */
.gradio-container button[role="tab"],
.gradio-container button[role="tab"] *,
.gradio-container .tab-nav button,
.gradio-container .tab-nav button * {
color: #111827 !important;
opacity: 1 !important;
visibility: visible !important;
-webkit-text-fill-color: #111827 !important;
}
.gradio-container button[role="tab"][aria-selected="true"],
.gradio-container button[role="tab"][aria-selected="true"] *,
.gradio-container .tab-nav button.selected,
.gradio-container .tab-nav button.selected * {
color: #7c3aed !important;
-webkit-text-fill-color: #7c3aed !important;
font-weight: 800 !important;
}
/* ===== FINAL LEFT PANEL UPLOAD FIX ===== */
.upload-caption {
display: inline-flex;
width: fit-content;
align-items: center;
gap: 8px;
padding: 11px 20px;
border-radius: 12px;
background: linear-gradient(135deg, #7c3aed, #6d28d9);
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 15px;
font-weight: 900;
box-shadow: 0 12px 24px rgba(124,58,237,.22);
margin-bottom: 0 !important;
}
.upload-caption * {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
}
#pdf_upload {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
/* Main drop area only */
#pdf_upload .wrap {
width: 100% !important;
min-height: 215px !important;
background: #ffffff !important;
border: 2px dashed #c4b5fd !important;
border-radius: 24px !important;
display: grid !important;
place-items: center !important;
text-align: center !important;
position: relative !important;
overflow: hidden !important;
}
/* Hide only Gradio placeholder text before upload */
#pdf_upload .wrap > div:first-child,
#pdf_upload .wrap > span,
#pdf_upload .wrap > p {
display: none !important;
}
/* Custom placeholder */
#pdf_upload .wrap::after {
content: "โ๏ธ\\A Drag & drop your PDF file here\\A or click to browse";
white-space: pre-line;
color: #334155 !important;
-webkit-text-fill-color: #334155 !important;
font-size: 16px !important;
line-height: 1.8 !important;
font-weight: 700 !important;
text-align: center !important;
}
/* After file upload: remove fake placeholder */
#pdf_upload:has([data-testid="file"]) .wrap::after,
#pdf_upload:has(.file-preview) .wrap::after {
content: "" !important;
}
/* Uploaded file card readability */
#pdf_upload [data-testid="file"],
#pdf_upload .file-preview,
#pdf_upload .file-preview *,
#pdf_upload a,
#pdf_upload span,
#pdf_upload p {
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
opacity: 1 !important;
}
/* Hide built-in label only */
#pdf_upload .label-wrap,
#pdf_upload label,
#pdf_upload [data-testid="block-label"],
#pdf_upload input[type="file"] {
display: none !important;
}
/* Engine wrapper */
#engine_radio,
#engine_radio *,
#engine_radio fieldset,
#engine_radio .wrap {
background: #FFFFFF !important;
border: none !important;
box-shadow: none !important;
}
.engine-caption {
display: inline-flex;
width: fit-content;
align-items: center;
padding: 8px 14px;
border-radius: 10px;
background: #7c3aed;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 16px;
font-weight: 900;
margin-bottom: 10px;
}
#engine_radio label,
#engine_radio label span {
background: transparent !important;
border: none !important;
box-shadow: none !important;
color: #8b5cf6 !important;
-webkit-text-fill-color: #8b5cf6 !important;
font-size: 14px !important;
font-weight: 900 !important;
}
/* Selected option purple */
#engine_radio label:has(input:checked),
#engine_radio label:has(input:checked) * {
color: #5b21b6 !important;
-webkit-text-fill-color: #5b21b6 !important;
}
/* Radio circles */
#engine_radio input[type="radio"] {
display: inline-block !important;
appearance: auto !important;
-webkit-appearance: radio !important;
opacity: 1 !important;
visibility: visible !important;
position: static !important;
width: 14px !important;
height: 14px !important;
margin-right: 8px !important;
accent-color: #5b21b6 !important;
}
#engine_radio .wrap {
display: flex !important;
flex-direction: row !important;
gap: 40px !important;
align-items: center !important;
}
/* Hide Gradio's built-in title completely */
#engine_radio legend,
#engine_radio .label-wrap,
#engine_radio [data-testid="block-label"] {
display: none !important;
}
/* EMERGENCY READABILITY FIX */
.gradio-container h1:not(.hero h1),
.gradio-container h2:not(.hero h2),
.gradio-container h3:not(.hero h3),
.gradio-container h4:not(.hero h4),
.gradio-container h5:not(.hero h5),
.gradio-container h6:not(.hero h6) {
color: #111827 !important;
opacity: 1 !important;
visibility: visible !important;
font-weight: 800 !important;
}
/* Safe readability fix: Markdown only, not JSON/code internals */
.gradio-container .markdown,
.gradio-container .markdown p,
.gradio-container .markdown li,
.gradio-container .markdown h1,
.gradio-container .markdown h2,
.gradio-container .markdown h3,
.gradio-container .markdown strong {
color: #111827 !important;
opacity: 1 !important;
}
/* Keep code/JSON readable */
.gradio-container pre,
.gradio-container code,
.gradio-container pre *,
.gradio-container code *,
.gradio-container .cm-editor,
.gradio-container .cm-editor * {
color: inherit !important;
-webkit-text-fill-color: inherit !important;
}
/* ================= FINAL TAB READABILITY PATCH ================= */
/* Normal markdown text, including _Not found._ italic text */
.gradio-container .markdown,
.gradio-container .markdown *,
.gradio-container .prose,
.gradio-container .prose * {
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
opacity: 1 !important;
}
/* Markdown italic fallback like _Not found._ */
.gradio-container em,
.gradio-container i {
color: #475569 !important;
-webkit-text-fill-color: #475569 !important;
opacity: 1 !important;
}
/* Inline code like `survey_or_review` */
.gradio-container .markdown code,
.gradio-container p code,
.gradio-container li code {
background: #f1f5f9 !important;
color: #5b21b6 !important;
-webkit-text-fill-color: #5b21b6 !important;
padding: 2px 6px !important;
border-radius: 6px !important;
font-weight: 800 !important;
}
/* Force tab panels to stay white */
.gradio-container .tabitem,
.gradio-container .tabitem > div,
.gradio-container .tabitem .block,
.gradio-container .tabitem .wrap {
background: #ffffff !important;
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
}
/* Advanced accordion fix */
.gradio-container .accordion,
.gradio-container .accordion *,
.gradio-container details,
.gradio-container details * {
background: #ffffff !important;
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
}
/* Tables in Advanced Analysis */
.gradio-container table,
.gradio-container table *,
.gradio-container th,
.gradio-container td {
background: #ffffff !important;
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
border-color: #cbd5e1 !important;
}
/* Code blocks inside markdown */
.gradio-container pre,
.gradio-container pre *,
.gradio-container code,
.gradio-container code * {
background: #f8fafc !important;
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
}
/* Export JSON/code viewer: make it readable */
.gradio-container .cm-editor,
.gradio-container .cm-editor *,
.gradio-container .cm-scroller,
.gradio-container .cm-content,
.gradio-container .cm-line {
background: #f8fafc !important;
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
}
/* Export file boxes */
.gradio-container [data-testid="file"],
.gradio-container [data-testid="file"] *,
.gradio-container .file-preview,
.gradio-container .file-preview * {
background: #f8fafc !important;
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
}
/* ================= LAB READINESS RESTORE ================= */
/* Restore dark Tools tile readability */
.lab-board .lab-dark,
.lab-board .lab-dark *,
.lab-board .lab-dark span,
.lab-board .lab-dark b {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
opacity: 1 !important;
}
/* Optional: softer label inside dark card */
.lab-board .lab-dark span {
color: #c4b5fd !important;
-webkit-text-fill-color: #c4b5fd !important;
}
/* Keep other lab tiles clean */
.lab-board .lab-green,
.lab-board .lab-green * {
color: #065f46 !important;
-webkit-text-fill-color: #065f46 !important;
}
.lab-board .lab-purple,
.lab-board .lab-purple * {
color: #4c1d95 !important;
-webkit-text-fill-color: #4c1d95 !important;
}
.lab-board .lab-red,
.lab-board .lab-red * {
color: #9f1239 !important;
-webkit-text-fill-color: #9f1239 !important;
}
/* ================= HERO COLOR RESTORE ================= */
.hero,
.hero * {
color: inherit;
}
.hero .logo-text {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
}
.hero .kicker {
color: #67e8f9 !important;
-webkit-text-fill-color: #67e8f9 !important;
}
.hero h1 {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
}
.hero h1 span {
color: #a78bfa !important;
-webkit-text-fill-color: #a78bfa !important;
}
.hero p {
color: rgba(255,255,255,.88) !important;
-webkit-text-fill-color: rgba(255,255,255,.88) !important;
}
.hero .hero-badge,
.hero .hero-badge * {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
}
.hero .logo-mark {
color: inherit !important;
-webkit-text-fill-color: inherit !important;
}
/* FINAL HERO READABILITY OVERRIDE */
.hero,
.hero .hero-content,
.hero .hero-copy {
opacity: 1 !important;
filter: none !important;
mix-blend-mode: normal !important;
}
.hero h1,
.hero h1 *,
.hero p,
.hero .kicker,
.hero .logo-text {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
opacity: 1 !important;
filter: none !important;
mix-blend-mode: normal !important;
}
.hero .kicker {
color: #67e8f9 !important;
-webkit-text-fill-color: #67e8f9 !important;
}
.hero h1 span {
color: #c4b5fd !important;
-webkit-text-fill-color: #c4b5fd !important;
}
.hero p {
color: rgba(255,255,255,.92) !important;
-webkit-text-fill-color: rgba(255,255,255,.92) !important;
}
/* ================= ASK THE PAPER / RAG FIX ================= */
.rag-panel,
.rag-panel * {
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
}
.rag-question,
.rag-question *,
.rag-question textarea,
.rag-question input {
background: #ffffff !important;
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
border-color: #cbd5e1 !important;
}
.rag-question textarea::placeholder,
.rag-question input::placeholder {
color: #64748b !important;
-webkit-text-fill-color: #64748b !important;
opacity: 1 !important;
}
.rag-answer {
background: #ffffff !important;
border: 1px solid #e2e8f0 !important;
border-radius: 18px !important;
padding: 22px !important;
box-shadow: 0 8px 24px rgba(15,23,42,.04) !important;
line-height: 1.65 !important;
}
.rag-answer,
.rag-answer * {
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
}
.rag-answer h1,
.rag-answer h2,
.rag-answer h3 {
margin-top: 18px !important;
margin-bottom: 10px !important;
font-weight: 900 !important;
}
.rag-answer ul {
padding-left: 22px !important;
}
.rag-answer li {
margin-bottom: 7px !important;
}
.rag-answer blockquote {
margin: 12px 0 !important;
padding: 12px 16px !important;
border-left: 4px solid #7c3aed !important;
background: #f8fafc !important;
border-radius: 10px !important;
color: #334155 !important;
-webkit-text-fill-color: #334155 !important;
}
.rag-debug,
.rag-debug * {
background: #ffffff !important;
color: #111827 !important;
-webkit-text-fill-color: #111827 !important;
}
/* HF SPACE HERO FINAL OVERRIDE */
.gradio-container .hero,
.gradio-container .hero * {
opacity: 1 !important;
filter: none !important;
mix-blend-mode: normal !important;
}
.gradio-container .hero h1,
.gradio-container .hero h1 *,
.gradio-container .hero p,
.gradio-container .hero .logo-text {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
}
.gradio-container .hero .kicker {
color: #67e8f9 !important;
-webkit-text-fill-color: #67e8f9 !important;
}
.gradio-container .hero h1 span {
color: #c4b5fd !important;
-webkit-text-fill-color: #c4b5fd !important;
}
.gradio-container .hero p {
color: rgba(255, 255, 255, 0.92) !important;
-webkit-text-fill-color: rgba(255, 255, 255, 0.92) !important;
}
/* ABSOLUTE FINAL HERO FIX FOR HF */
.gradio-container .hero {
color: #ffffff !important;
}
.gradio-container .hero h1,
.gradio-container .hero h1 *,
.gradio-container .hero p,
.gradio-container .hero p *,
.gradio-container .hero .logo-text,
.gradio-container .hero .hero-badge,
.gradio-container .hero .hero-badge * {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
opacity: 1 !important;
filter: none !important;
}
.gradio-container .hero .kicker {
color: #67e8f9 !important;
-webkit-text-fill-color: #67e8f9 !important;
}
.gradio-container .hero h1 span {
color: #c4b5fd !important;
-webkit-text-fill-color: #c4b5fd !important;
}
"""
theme = gr.themes.Soft(
primary_hue="violet",
secondary_hue="blue",
neutral_hue="slate",
)
with gr.Blocks(title=APP_NAME, theme=theme, css=CSS) as demo:
state = gr.State({})
gr.HTML(
"""
๐งช
Paper2Lab
Research Assistant
Turn research papers into
actionable lab plans
Upload a PDF and get a structured paper card, lab starter kit,
evidence grounding, reproducibility assessment and exportable reports.
โฆ AI-powered extraction
โก Evidence grounded
โ Reproducibility ready
"""
)
with gr.Row(elem_classes=["app-shell", "top-layout"]):
with gr.Column(scale=4, min_width=300, elem_classes=["control-stack"]):
gr.HTML("""๐ค 1. Upload your paper
""")
gr.HTML("""๐ Drop your PDF here
""")
pdf_input = gr.File(
label=None,
show_label=False,
file_types=[".pdf"],
type="filepath",
elem_id="pdf_upload",
elem_classes=["clean-upload"],
)
gr.HTML("""โก 2. Select analysis engine
""")
gr.HTML("""โ๏ธ Analysis engine
""")
refinement_mode = gr.Radio(
label=None,
container=False,
choices=["local", "nemotron"],
value="nemotron",
elem_id="engine_radio",
)
run_btn = gr.Button(
"โจ Analyze Paper",
variant="primary",
size="lg",
elem_classes=["clean-button"],
)
gr.HTML("""๐ก Use a clear-text PDF for best results.
""")
with gr.Column(scale=7, min_width=520, elem_classes=["result-stack"]):
quick_summary = gr.HTML(
"""
Status
Upload a PDF and click Analyze Paper.
"""
)
lab_cards_html = gr.HTML(
"""
Starter type
Waiting
Tools
Waiting
Experiments
Waiting
Risks
Waiting
"""
)
with gr.Row(elem_classes=["app-shell", "tabs-shell"]):
with gr.Column():
with gr.Tabs():
with gr.Tab("๐ Paper Summary"):
paper_md = gr.Markdown()
with gr.Tab("๐งช Lab Starter Kit"):
lab_md = gr.Markdown()
with gr.Tab("๐ Evidence Viewer"):
evidence_md = gr.Markdown()
with gr.Tab("๐ฌ Ask the Paper"):
with gr.Column(elem_classes=["rag-panel"]):
rag_question = gr.Textbox(
label="Ask a question about the uploaded paper",
placeholder="Example: What datasets were used?",
lines=2,
elem_classes=["rag-question"],
)
rag_btn = gr.Button("Ask", variant="primary")
rag_answer = gr.Markdown(elem_classes=["rag-answer"])
rag_json = gr.JSON(
label="RAG debug output",
elem_classes=["rag-debug"],
visible=False, # better for demo
)
with gr.Tab("โ๏ธ Advanced Analysis"):
with gr.Accordion("Model comparison, selection report, metadata", open=False):
advanced_md = gr.Markdown()
with gr.Tab("โฌ๏ธ Export"):
final_json = gr.JSON(label="Final paper card")
json_file = gr.File(label="Download JSON")
md_file = gr.File(label="Download Markdown report")
run_btn.click(
fn=analyze_paper,
inputs=[pdf_input, refinement_mode],
outputs=[
state,
quick_summary,
paper_md,
lab_md,
evidence_md,
advanced_md,
lab_cards_html,
final_json,
json_file,
md_file,
],
show_progress=True,
)
rag_btn.click(
fn=ask_paper_question,
inputs=[state, rag_question],
outputs=[rag_answer, rag_json],
show_progress=True,
)
if __name__ == "__main__":
demo.queue()
demo.launch()