| 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"<span class='muted'>{empty}</span>" |
|
|
| html = "" |
| for x in items[:6]: |
| clean = clean_display_text(x, max_len=70) |
| html += f"<span class='chip' title='{str(x)}'>{clean}</span>" |
| 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""" |
| <div class="summary-card"> |
| <div class="card-head"> |
| <div><span class="icon">📄</span><b>Quick summary</b></div> |
| <span class="status-pill">{status}</span> |
| </div> |
| |
| <div class="summary-row"> |
| <div class="label">Title</div> |
| <div class="value">{card.get("title", "Untitled paper")}</div> |
| </div> |
| |
| <div class="summary-row"> |
| <div class="label">Research question</div> |
| <div class="value">{card.get("research_question", "Not found")}</div> |
| </div> |
| |
| <div class="summary-row"> |
| <div class="label">Datasets</div> |
| <div class="value">{chip_list(card.get("datasets_or_data_sources"))}</div> |
| </div> |
| |
| <div class="summary-row"> |
| <div class="label">Models</div> |
| <div class="value">{chip_list(card.get("models_or_methods"))}</div> |
| </div> |
| |
| <div class="summary-row"> |
| <div class="label">Key findings</div> |
| <div class="value highlight">{bullet_list(card.get("key_findings")).replace("- ", "• ")}</div> |
| </div> |
| </div> |
| """ |
|
|
|
|
| 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""" |
| <div class="lab-board"> |
| <div class="lab-board-header"> |
| <span class="lab-emoji">🧪</span> |
| <span>Lab readiness</span> |
| </div> |
| |
| <div class="lab-grid-2"> |
| <div class="lab-tile lab-green"> |
| <span>Starter type</span> |
| <b>{short_card_text(starter_type)}</b> |
| </div> |
| |
| <div class="lab-tile lab-dark"> |
| <span>Tools</span> |
| <b>{short_card_text(tools, max_len=58)}</b> |
| </div> |
| |
| <div class="lab-tile lab-purple"> |
| <span>Experiments</span> |
| <b>{len(experiments)} detected</b> |
| </div> |
| |
| <div class="lab-tile lab-red"> |
| <span>Risks</span> |
| <b>{len(risks)} issues</b> |
| </div> |
| </div> |
| </div> |
| """ |
|
|
| 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]) |
|
|
| |
| 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( |
| """ |
| <div class="app-shell"> |
| <section class="hero"> |
| <div class="hero-content"> |
| <div class="logo-row"> |
| <span class="logo-mark">🧪</span> |
| <span class="logo-text">Paper2Lab</span> |
| </div> |
| |
| <div class="kicker">Research Assistant</div> |
| |
| <h1> |
| Turn research papers into |
| <span>actionable lab plans</span> |
| </h1> |
| |
| <p> |
| Upload a PDF and get a structured paper card, lab starter kit, |
| evidence grounding, reproducibility assessment and exportable reports. |
| </p> |
| |
| <div class="hero-badges"> |
| <div class="hero-badge">✦ AI-powered extraction</div> |
| <div class="hero-badge">□ Evidence grounded</div> |
| <div class="hero-badge">☘ Reproducibility ready</div> |
| </div> |
| </div> |
| </section> |
| </div> |
| """ |
| ) |
|
|
| with gr.Row(elem_classes=["app-shell", "top-layout"]): |
| with gr.Column(scale=4, min_width=300, elem_classes=["control-stack"]): |
| gr.HTML("""<div class="panel-title">📤 1. Upload your paper</div>""") |
| gr.HTML("""<div class="upload-caption">📄 Drop your PDF here</div>""") |
|
|
| pdf_input = gr.File( |
| label=None, |
| show_label=False, |
| file_types=[".pdf"], |
| type="filepath", |
| elem_id="pdf_upload", |
| elem_classes=["clean-upload"], |
| ) |
| |
| gr.HTML("""<div class="panel-title">⚡ 2. Select analysis engine</div>""") |
| gr.HTML("""<div class="engine-caption">⚙️ Analysis engine</div>""") |
|
|
| 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("""<div class="tip-box">💡 Use a clear-text PDF for best results.</div>""") |
|
|
| with gr.Column(scale=7, min_width=520, elem_classes=["result-stack"]): |
| quick_summary = gr.HTML( |
| """ |
| <div class="summary-card"> |
| <div class="card-head"> |
| <div><span class="icon">📄</span><b>Quick summary</b></div> |
| <span class="status-pill">Ready</span> |
| </div> |
| <div class="summary-row"> |
| <div class="label">Status</div> |
| <div class="value">Upload a PDF and click <b>Analyze Paper</b>.</div> |
| </div> |
| </div> |
| """ |
| ) |
|
|
| lab_cards_html = gr.HTML( |
| """ |
| <div class="lab-board"> |
| <div class="lab-board-header"> |
| <span class="lab-emoji">🧪</span> |
| <span>Lab readiness</span> |
| </div> |
| |
| <div class="lab-grid-2"> |
| <div class="lab-tile lab-green"> |
| <span>Starter type</span> |
| <b>Waiting</b> |
| </div> |
| |
| <div class="lab-tile lab-dark"> |
| <span>Tools</span> |
| <b>Waiting</b> |
| </div> |
| |
| <div class="lab-tile lab-purple"> |
| <span>Experiments</span> |
| <b>Waiting</b> |
| </div> |
| |
| <div class="lab-tile lab-red"> |
| <span>Risks</span> |
| <b>Waiting</b> |
| </div> |
| </div> |
| </div> |
| """ |
| ) |
|
|
| 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, |
| ) |
|
|
| 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() |