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"""
๐Ÿงช Lab readiness
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( """
๐Ÿ“„Quick summary
Ready
Status
Upload a PDF and click Analyze Paper.
""" ) lab_cards_html = gr.HTML( """
๐Ÿงช Lab readiness
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()