Spaces:
Sleeping
Sleeping
| """ | |
| DVNC.AI β app.py | |
| Refactored for functional "Use as main insight" logic with academic rigor. | |
| """ | |
| # ββ Standard library ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| import html | |
| import json | |
| import math | |
| import os | |
| import random | |
| import re | |
| import urllib.parse | |
| import xml.etree.ElementTree as ET | |
| from pathlib import Path | |
| from typing import Dict, List, Optional | |
| from urllib.parse import quote | |
| # ββ Third-party ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| import gradio as gr | |
| import requests | |
| try: | |
| import fitz # PyMuPDF | |
| except Exception: | |
| fitz = None | |
| try: | |
| from bs4 import BeautifulSoup | |
| except Exception: | |
| BeautifulSoup = None | |
| # ββ Internal modules βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| from dvnc_ai_v2_hf.agent_route_cards import build_agent_route_cards_html | |
| from dvnc_ai_v2_hf.discovery_app_bridge import ( | |
| get_default_route_state, | |
| get_discovery_css, | |
| get_initial_discovery_timeline_html, | |
| ) | |
| from dvnc_ai_v2_hf.dvnc_ui_layout import get_dvnc_layout_css | |
| from dvnc_ai_v2_hf.self_learning_graph import ( | |
| DEFAULT_SOURCES, | |
| SEARCH_MODES, | |
| SOURCE_OPTIONS, | |
| build_learning_graph_html, | |
| build_journal_html, | |
| ingest_selected_papers, | |
| parse_uploaded_pdf, | |
| render_parse_result, | |
| run_paper_discovery, | |
| safe_text, | |
| ) | |
| # ββ Constants ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| MODELS = [ | |
| {"name": "DVNC Sovereign", "tag": "flagship", "desc": "Maximum depth orchestration for frontier discovery"}, | |
| {"name": "DVNC Atlas", "tag": "research", "desc": "Balanced reasoning, graph traversal, and synthesis"}, | |
| {"name": "DVNC Curie", "tag": "lab", "desc": "Experimental hypothesis generation for anomalous signals"}, | |
| ] | |
| AGENTS = [ | |
| "Query Interpreter", | |
| "Graph Divergence Mapper", | |
| "Evidence Harvester", | |
| "Analogy Engine", | |
| "Hypothesis Composer", | |
| "Adversarial Critic", | |
| "Experimental Program Designer", | |
| ] | |
| NODES = [ | |
| {"id": "seed", "label": "Seed Query", "group": "core", "x": 10, "y": 0, "z": 0}, | |
| {"id": "bio", "label": "Biomaterials", "group": "domain", "x": 24, "y": 12, "z": -8}, | |
| {"id": "card", "label": "Cardiac Repair", "group": "domain", "x": 38, "y": 3, "z": 14}, | |
| {"id": "nano", "label": "Nanostructure", "group": "bridge", "x": 24, "y": -18, "z": 16}, | |
| {"id": "selfasm", "label": "Self-Assembly", "group": "bridge", "x": 40, "y": -16, "z": -16}, | |
| {"id": "electro", "label": "Electro-signalling", "group": "mechanism", "x": 58, "y": 10, "z": -10}, | |
| {"id": "immune", "label": "Immune Modulation", "group": "mechanism", "x": 64, "y": -8, "z": 10}, | |
| {"id": "trial", "label": "Validation Path", "group": "outcome", "x": 80, "y": 0, "z": 0}, | |
| {"id": "alt1", "label": "Piezoelectric Scaffold","group": "candidate", "x": 56, "y": 26, "z": 14}, | |
| {"id": "alt2", "label": "Peptide Mesh", "group": "candidate", "x": 54, "y": -27, "z": -14}, | |
| ] | |
| EDGES = [ | |
| ("seed", "bio"), ("seed", "nano"), | |
| ("bio", "card"), ("nano", "selfasm"), | |
| ("selfasm", "electro"),("card", "immune"), | |
| ("electro", "trial"), ("immune", "trial"), | |
| ("card", "alt1"), ("selfasm","alt2"), | |
| ("alt1", "trial"), ("alt2", "trial"), | |
| ] | |
| DEFAULT_PATH = ["seed", "nano", "selfasm", "electro", "trial"] | |
| CANDIDATES = [ | |
| { | |
| "title": "Piezoelectric Scaffold Cascade", | |
| "front": "Use mechano-electric scaffolds to convert cardiac strain into micro-current signalling.", | |
| "back": "Discovery path: anomalous healing signal -> piezoelectric analog -> ion-channel entrainment -> tissue regeneration. Risk: power density and fibrosis coupling.", | |
| "score": 92, | |
| "novelty": "High", | |
| "agent": "Hypothesis Composer", | |
| }, | |
| { | |
| "title": "Peptide Self-Assembly Mesh", | |
| "front": "Deploy dynamic peptide meshes that self-assemble around damaged myocardium and guide repair.", | |
| "back": "Discovery path: self-assembly -> local immune choreography -> regenerative substrate formation. Risk: degradation timing and targeting specificity.", | |
| "score": 88, | |
| "novelty": "High", | |
| "agent": "Analogy Engine", | |
| }, | |
| { | |
| "title": "Immune-Tuned Conductive Hydrogel", | |
| "front": "Blend conductivity with macrophage-state modulation to reduce scarring and restore conduction.", | |
| "back": "Discovery path: inflammation mismatch -> conductive medium -> macrophage polarization -> synchronized healing. Risk: persistence and biocompatibility.", | |
| "score": 85, | |
| "novelty": "Medium-High", | |
| "agent": "Adversarial Critic", | |
| }, | |
| ] | |
| ACADEMIC_INSIGHTS = [ | |
| { | |
| "hypothesis": "Implementation of mechano-electric scaffolds to transduce cardiac strain into localized micro-current signalling for myocardial regeneration.", | |
| "metrics": {"Novelty": 92, "Mechanistic clarity": 85, "Experimental tractability": 78, "Cross-domain distance": 94}, | |
| "outline": "1. Synthesize candidate piezoelectric biomaterial scaffolds with tunable strain-electric coupling.\n2. Evaluate *in vitro* electromechanical transduction and subsequent ion-channel entrainment.\n3. Conduct *in vivo* comparative models to assess regenerative efficacy against gold-standard substrates.\n4. Rigorously validate to exclude pathological fibrosis and power-density toxicity.", | |
| "path": ["seed", "bio", "card", "alt1", "trial"] | |
| }, | |
| { | |
| "hypothesis": "Deployment of dynamic peptide networks that self-assemble post-infarction to orchestrate local immunological responses and guide substrate regeneration.", | |
| "metrics": {"Novelty": 88, "Mechanistic clarity": 82, "Experimental tractability": 86, "Cross-domain distance": 85}, | |
| "outline": "1. Formulate peptide sequences programmed for triggered *in situ* self-assembly within the myocardial infarct zone.\n2. Quantify macrophage polarization and local immune choreography post-deployment.\n3. Map the temporospatial degradation profile against *de novo* tissue formation.\n4. Falsify against off-target aggregation and delayed clearance risks.", | |
| "path": ["seed", "nano", "selfasm", "alt2", "trial"] | |
| }, | |
| { | |
| "hypothesis": "Integration of conductive hydrogels with immunomodulatory properties to simultaneously bridge electrical uncoupling and mitigate adverse fibrotic scarring.", | |
| "metrics": {"Novelty": 85, "Mechanistic clarity": 90, "Experimental tractability": 88, "Cross-domain distance": 79}, | |
| "outline": "1. Fabricate biocompatible hydrogels featuring precisely tuned electrical conductivity and immunomodulatory motifs.\n2. Monitor electrophysiological synchronization across the scaffold-tissue interface.\n3. Assess macrophage state transitions and suppression of adverse fibrotic remodelling.\n4. Validate long-term persistence, hemocompatibility, and mechanical integration.", | |
| "path": ["seed", "bio", "card", "immune", "trial"] | |
| } | |
| ] | |
| JOURNALS = [ | |
| {"name": "Nature", "url": "https://www.nature.com/search", "desc": "Flagship multidisciplinary research journal."}, | |
| {"name": "Science", "url": "https://www.science.org/search", "desc": "High-impact science journal and family."}, | |
| {"name": "Cell", "url": "https://www.cell.com/search", "desc": "Life sciences and translational biology."}, | |
| {"name": "The Lancet", "url": "https://www.thelancet.com/search", "desc": "Clinical and medical research."}, | |
| {"name": "IEEE Xplore","url": "https://ieeexplore.ieee.org/search/searchresult.jsp", "desc": "Engineering, AI, signal processing, and systems."}, | |
| ] | |
| SEMANTIC_SCHOLAR_API_KEY = os.getenv("SEMANTIC_SCHOLAR_API_KEY", "") | |
| GROBID_URL = os.getenv("GROBID_URL", "").strip() | |
| REQUEST_TIMEOUT = 25 | |
| # ββ Utility helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def safe_text(x, default: str = "") -> str: | |
| return html.escape(str(x if x is not None else default)) | |
| def norm_text(x: Optional[str]) -> str: | |
| return re.sub(r"\s+", " ", (x or "")).strip() | |
| def detect_query_type(query: str) -> str: | |
| q = (query or "").strip() | |
| if re.match(r"^10\.\d{4,9}/[-._;()/:A-Z0-9]+$", q, flags=re.I): | |
| return "doi" | |
| if q.startswith("http://") or q.startswith("https://"): | |
| return "link" | |
| return "topic" | |
| def ensure_list(x): | |
| return x if isinstance(x, list) else [] | |
| # ββ HTML builders βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def build_connectome_html(path_ids: List[str]) -> str: | |
| active = set(path_ids) | |
| node_map = {n["id"]: n for n in NODES} | |
| path_pairs = { | |
| pair | |
| for i in range(len(path_ids) - 1) | |
| for pair in [(path_ids[i], path_ids[i + 1]), (path_ids[i + 1], path_ids[i])] | |
| } | |
| base_lines, active_lines, circles, labels = [], [], [], [] | |
| for a, b in EDGES: | |
| na, nb = node_map[a], node_map[b] | |
| x1, y1 = na["x"] * 8 + 80, na["y"] * 6 + 280 | |
| x2, y2 = nb["x"] * 8 + 80, nb["y"] * 6 + 280 | |
| base_lines.append(f'<line class="edge" x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" />') | |
| if (a, b) in path_pairs: | |
| active_lines.append(f'<line class="edge active" x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" />') | |
| for n in NODES: | |
| cx, cy = n["x"] * 8 + 80, n["y"] * 6 + 280 | |
| is_active = n["id"] in active | |
| state = "chosen" if is_active else "idle" | |
| halo_cls = "halo active" if is_active else "halo" | |
| lbl_cls = "label active" if is_active else "label" | |
| radius = 18 if is_active else 13 | |
| halo_r = 30 if is_active else 0 | |
| circles.append( | |
| f'<g class="node-wrap">' | |
| f'<circle class="{halo_cls}" cx="{cx:.1f}" cy="{cy:.1f}" r="{halo_r}" />' | |
| f'<circle class="node {n["group"]} {state}" cx="{cx:.1f}" cy="{cy:.1f}" r="{radius}" />' | |
| f'</g>' | |
| ) | |
| labels.append(f'<text class="{lbl_cls}" x="{cx + 18:.1f}" y="{cy - 16:.1f}">{safe_text(n["label"])}</text>') | |
| return f""" | |
| <div class="panel brain-shell"> | |
| <div class="brain-header"> | |
| <div> | |
| <p class="eyebrow">Connectome</p> | |
| <h3>3D Connectome</h3> | |
| </div> | |
| <div class="brain-legend"> | |
| <span><i class="dot dot-live"></i> lit path</span> | |
| <span><i class="dot dot-chosen"></i> chosen node</span> | |
| <span><i class="dot dot-idle"></i> available node</span> | |
| </div> | |
| </div> | |
| <div class="brain-stage"> | |
| <svg viewBox="0 0 780 560" class="brain-svg" role="img" aria-label="DVNC 3D connectome visualisation"> | |
| {"".join(base_lines)} | |
| {"".join(active_lines)} | |
| {"".join(circles)} | |
| {"".join(labels)} | |
| </svg> | |
| </div> | |
| </div> | |
| """ | |
| def build_cards_html(cards: List[Dict]) -> str: | |
| items = [] | |
| for i, c in enumerate(cards): | |
| items.append(f""" | |
| <article class="candidate-card" tabindex="0"> | |
| <div class="candidate-card-inner"> | |
| <div class="candidate-face candidate-front"> | |
| <div class="candidate-top"> | |
| <span class="chip">{safe_text(c["agent"])}</span> | |
| <span class="score">{safe_text(c["score"])}</span> | |
| </div> | |
| <h4>{safe_text(c["title"])}</h4> | |
| <p>{safe_text(c["front"])}</p> | |
| <div class="meta-row"><span>Novelty</span><strong>{safe_text(c["novelty"])}</strong></div> | |
| <button class="mini" type="button" onclick="triggerRouteSwap('{i}')">Use as main insight</button> | |
| </div> | |
| <div class="candidate-face candidate-back"> | |
| <div class="candidate-top"> | |
| <span class="chip alt">Alternative path</span> | |
| <span class="score">{safe_text(c["score"])}</span> | |
| </div> | |
| <h4>{safe_text(c["title"])}</h4> | |
| <p>{safe_text(c["back"])}</p> | |
| <div class="meta-row"><span>Swap into route</span><strong>Enabled</strong></div> | |
| <button class="mini" type="button" onclick="triggerRouteSwap('{i}')">Use as main insight</button> | |
| </div> | |
| </div> | |
| </article>""") | |
| return '<div class="panel" style="padding:20px;"><div class="candidate-grid">' + "".join(items) + "</div></div>" | |
| def build_agent_timeline(reasoning: List[Dict]) -> str: | |
| rows = [] | |
| for r in reasoning: | |
| rows.append(f""" | |
| <details class="agent-step" {"open" if r["step"] == 1 else ""}> | |
| <summary class="agent-summary"> | |
| <div class="agent-index">{safe_text(r["step"])}</div> | |
| <div class="agent-head"> | |
| <h4>{safe_text(r["agent"])}</h4> | |
| <span>{safe_text(r["tag"])}</span> | |
| </div> | |
| </summary> | |
| <div class="agent-copy"> | |
| <p>{safe_text(r["summary"])}</p> | |
| </div> | |
| </details>""") | |
| return '<div class="panel" style="padding:18px;"><div class="timeline">' + "".join(rows) + "</div></div>" | |
| def build_chat_html(query: str, result: Dict) -> str: | |
| return f""" | |
| <div class="panel chat-panel"> | |
| <div class="chat-thread"> | |
| <div class="bubble bubble-user"> | |
| <span class="role">You</span> | |
| <p>{safe_text(query)}</p> | |
| </div> | |
| <div class="bubble bubble-ai"> | |
| <span class="role">DVNC Sovereign</span> | |
| <p>{safe_text(result["summary"])}</p> | |
| </div> | |
| <div class="bubble bubble-system"> | |
| <span class="role">Discovery Signal</span> | |
| <p><strong>Primary hypothesis:</strong> {safe_text(result["primary_hypothesis"])}</p> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def build_models_html(selected: str) -> str: | |
| items = [] | |
| for m in MODELS: | |
| active = "active" if m["name"] == selected else "" | |
| items.append(f""" | |
| <div class="model-pill {active}"> | |
| <span class="model-name">{safe_text(m["name"])}</span> | |
| <span class="model-tag">{safe_text(m["tag"])}</span> | |
| <small>{safe_text(m["desc"])}</small> | |
| </div>""") | |
| return '<div class="panel" style="padding:18px;"><div class="model-switcher">' + "".join(items) + "</div></div>" | |
| # ββ Discovery logic βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_discovery(query: str, model_name: str): | |
| """ | |
| Runs the 7-agent discovery pipeline. | |
| """ | |
| random.seed(len(query) + len(model_name)) | |
| if "curie" in query.lower() or "einstein" in query.lower(): | |
| primary = "Map the anomaly first, then force a distant analogy before composing the experimental programme." | |
| path = ["seed", "bio", "card", "immune", "trial"] | |
| else: | |
| primary = "Utilization of a self-assembling conductive scaffold to transduce mechanical strain into localized regenerative signalling pathways." | |
| path = DEFAULT_PATH | |
| summaries = [ | |
| "Normalises the user prompt into a graph-searchable seed and isolates the tension inside the question.", | |
| "Finds remote conceptual bridges instead of staying near the starting domain cluster.", | |
| "Pulls evidence packets and conflict signals required for grounded hypothesis formation.", | |
| "Generates cross-domain analogies with a bias toward mechanism transfer rather than keyword similarity.", | |
| "Composes the lead hypothesis and two structurally different variants.", | |
| "Attacks weak assumptions, hidden confounders, and feasibility gaps.", | |
| "Produces a staged validation plan with measurable falsification criteria.", | |
| ] | |
| tags = ["input", "graph", "evidence", "analogy", "compose", "critique", "experiment"] | |
| reasoning = [ | |
| {"step": i + 1, "agent": AGENTS[i], "tag": tags[i], "summary": summaries[i]} | |
| for i in range(7) | |
| ] | |
| result = { | |
| "summary": "A deeper route was chosen through the connectome, with live alternatives preserved as swappable cards so the reasoning path can be inspected rather than hidden.", | |
| "primary_hypothesis": primary, | |
| "reasoning": reasoning, | |
| "cards": CANDIDATES, | |
| "path": path, | |
| "metrics": { | |
| "Novelty": 93, | |
| "Mechanistic clarity": 89, | |
| "Experimental tractability": 82, | |
| "Cross-domain distance": 91, | |
| }, | |
| } | |
| chat_html = build_chat_html(query, result) | |
| connectome_html = build_connectome_html(path) | |
| timeline_html = build_agent_route_cards_html(reasoning) | |
| metrics_md = "\n".join(f"- {k}: {v}/100" for k, v in result["metrics"].items()) | |
| hypothesis_md = ( | |
| "# Discovery Output\n\n" | |
| f"**Model:** {model_name}\n\n" | |
| f"**Primary hypothesis:** {result['primary_hypothesis']}\n\n" | |
| "## Scoring\n" | |
| f"{metrics_md}\n\n" | |
| "## Experimental outline\n" | |
| "1. Construct the candidate material or protocol.\n" | |
| "2. Test mechanistic signal expression under controlled conditions.\n" | |
| "3. Compare against baseline and nearest-neighbour alternatives.\n" | |
| "4. Falsify using the adversarial risk criteria surfaced in the reasoning path.\n" | |
| ) | |
| cards_html = build_cards_html(CANDIDATES) | |
| route_state = get_default_route_state() | |
| return chat_html, connectome_html, timeline_html, cards_html, hypothesis_md, build_models_html(model_name), route_state | |
| def apply_route_swap(query: str, model_name: str, route_swap_payload: str, route_state): | |
| """ | |
| Called when a user clicks 'Use as main insight' on a candidate card. | |
| Sanitizes the output, adopts academic rigor, updates the connectome and discovery output. | |
| """ | |
| try: | |
| idx = int(route_swap_payload) | |
| except ValueError: | |
| idx = 0 | |
| if not (0 <= idx < len(ACADEMIC_INSIGHTS)): | |
| idx = 0 | |
| academic = ACADEMIC_INSIGHTS[idx] | |
| # Update Connectome | |
| connectome_html = build_connectome_html(academic["path"]) | |
| # Update Chat Feedback | |
| result = { | |
| "summary": "Main insight formally adopted. The connectome pathway and validation protocol have been realigned to the selected candidate methodology.", | |
| "primary_hypothesis": academic["hypothesis"] | |
| } | |
| chat_html = build_chat_html(query, result) | |
| # Format Oxford-tier markdown output | |
| metrics_md = "\n".join(f"- {k}: {v}/100" for k, v in academic["metrics"].items()) | |
| hypothesis_md = ( | |
| "# Discovery Output\n\n" | |
| f"**Model:** {model_name}\n\n" | |
| f"**Primary hypothesis:** {academic['hypothesis']}\n\n" | |
| "## Scoring\n" | |
| f"{metrics_md}\n\n" | |
| "## Experimental outline\n" | |
| f"{academic['outline']}\n" | |
| ) | |
| return chat_html, connectome_html, gr.update(), hypothesis_md, route_state | |
| # ββ Example loaders βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_example() -> str: | |
| return "How could a self-assembling conductive biomaterial improve cardiac tissue regeneration by converting mechanical strain into repair signalling?" | |
| def load_paper_topic() -> str: | |
| return "self-assembling conductive biomaterials for cardiac repair" | |
| # ββ CSS / HEAD ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| BASE_CSS = r""" | |
| :root { | |
| --bg: #ffffff; --panel: #ffffff; --line: rgba(0,0,0,.12); | |
| --text: #111111; --muted: #5b5b5b; --soft: rgba(0,0,0,.62); | |
| --gold: #ff6600; --teal: #17b8a6; --blue: #628dff; | |
| --chosen: #ff7a1a; --idle: #b8d8ff; --idle-stroke: #5e8fe6; | |
| --query-node: #ffd8b3; --paper-node: #d7f6f2; --upload-node: #e7defe; | |
| --shadow: 0 16px 40px rgba(0,0,0,.12); | |
| } | |
| html,body,.gradio-container { background:#ffffff !important; font-family:Inter,ui-sans-serif,system-ui,sans-serif; } | |
| .gradio-container { max-width:1640px !important; padding:20px !important; } | |
| #dvnc-shell { border:1px solid var(--line); border-radius:28px; overflow:hidden; background:#ffffff; box-shadow:var(--shadow); padding:20px 22px 22px; } | |
| .hero-bar { display:flex; justify-content:space-between; align-items:center; gap:16px; padding-bottom:12px; border-bottom:1px solid rgba(0,0,0,.06); margin-bottom:16px; } | |
| .brand { display:flex; align-items:center; gap:14px; } | |
| .logo { width:42px; height:42px; border-radius:14px; display:grid; place-items:center; color:var(--gold); background:linear-gradient(135deg,rgba(255,122,26,.12),rgba(23,184,166,.10)); border:1px solid rgba(0,0,0,.08); } | |
| .logo svg { width:24px; height:24px; } | |
| .brand h1 { font-size:1.05rem; margin:0; font-weight:700; letter-spacing:.12em; text-transform:uppercase; } | |
| .brand p { margin:3px 0 0; color:var(--muted); font-size:.84rem; } | |
| .status { display:flex; gap:10px; align-items:center; color:var(--soft); font-size:.85rem; } | |
| .status-dot { width:10px; height:10px; border-radius:50%; background:var(--teal); box-shadow:0 0 0 6px rgba(23,184,166,.10),0 0 14px rgba(23,184,166,.25); } | |
| .panel { background:#ffffff; border:1px solid var(--line); border-radius:22px; box-shadow:inset 0 1px 0 rgba(255,255,255,.8); } | |
| .querybox textarea,.querybox input { background:transparent !important; color:var(--text) !important; } | |
| .querybox,.querybox>div { background:#ffffff !important; border-radius:18px !important; border-color:var(--line) !important; } | |
| .chat-panel { padding:18px; min-height:280px; } | |
| .chat-thread { display:flex; flex-direction:column; gap:14px; } | |
| .bubble { max-width:88%; padding:16px 18px; border-radius:22px; border:1px solid var(--line); } | |
| .bubble p { margin:8px 0 0; line-height:1.6; font-size:.96rem; color:var(--text); } | |
| .bubble .role { font-size:.72rem; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); } | |
| .bubble-user { align-self:flex-end; background:linear-gradient(135deg,rgba(98,141,255,.16),rgba(98,141,255,.08)); } | |
| .bubble-ai { align-self:flex-start; background:#ffffff; } | |
| .bubble-system { align-self:flex-start; background:linear-gradient(135deg,rgba(255,122,26,.10),rgba(255,122,26,.04)); } | |
| .model-switcher { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; } | |
| .model-pill { padding:14px; border:1px solid var(--line); border-radius:18px; display:flex; flex-direction:column; gap:4px; min-height:98px; background:#ffffff; } | |
| .model-pill.active { border-color:rgba(255,122,26,.40); background:linear-gradient(135deg,rgba(255,122,26,.10),rgba(255,255,255,.96)); } | |
| .model-name { font-weight:650; color:var(--text); } | |
| .model-tag { font-size:.76rem; text-transform:uppercase; letter-spacing:.12em; color:var(--gold); } | |
| .model-pill small { color:var(--muted); line-height:1.45; } | |
| .brain-shell { padding:18px; } | |
| .brain-header { display:flex; justify-content:space-between; align-items:flex-end; gap:16px; margin-bottom:10px; } | |
| .eyebrow { font-size:.72rem; letter-spacing:.16em; text-transform:uppercase; color:var(--gold); margin:0 0 4px; } | |
| .brain-header h3 { margin:0; font-size:1.12rem; color:var(--text); } | |
| .brain-legend { display:flex; gap:14px; color:var(--muted); font-size:.8rem; flex-wrap:wrap; } | |
| .dot { width:10px; height:10px; display:inline-block; border-radius:50%; margin-right:6px; } | |
| .dot-live { background:var(--chosen); box-shadow:0 0 10px rgba(255,122,26,.35); } | |
| .dot-chosen { background:var(--chosen); } | |
| .dot-idle { background:var(--idle); border:1px solid var(--idle-stroke); } | |
| .dot-query { background:var(--query-node); border:1px solid #de9e58; } | |
| .dot-paper { background:var(--paper-node); border:1px solid #4fb3a5; } | |
| .dot-upload { background:var(--upload-node); border:1px solid #8f73d9; } | |
| .brain-stage { position:relative; min-height:420px; overflow:hidden; background:linear-gradient(180deg,rgba(250,250,250,1),rgba(255,255,255,1)); border:1px solid rgba(0,0,0,.05); border-radius:20px; } | |
| .brain-svg { width:100%; height:520px; display:block; } | |
| .edge { stroke:rgba(0,0,0,.12); stroke-width:2.4; } | |
| .edge.active { stroke:var(--chosen); stroke-width:4.2; stroke-linecap:round; filter:drop-shadow(0 0 6px rgba(255,122,26,.45)); stroke-dasharray:8 12; animation:pulseEdge 1.5s linear infinite; } | |
| .node { stroke-width:2.2; transition:all .25s ease; } | |
| .node.idle { fill:var(--idle); stroke:var(--idle-stroke); } | |
| .node.chosen { fill:var(--chosen); stroke:#ffb16d; } | |
| .halo { fill:none; } | |
| .halo.active { stroke:rgba(255,122,26,.18); stroke-width:12; } | |
| .label { fill:#2c2c2c; font-size:13px; font-weight:500; letter-spacing:.01em; } | |
| .label.active { fill:#111111; font-weight:700; } | |
| .learn-edge { stroke:rgba(0,0,0,.18); stroke-width:2.2; stroke-linecap:round; } | |
| .learn-node { stroke-width:2.2; } | |
| .learn-node.query { fill:var(--query-node); stroke:#de9e58; } | |
| .learn-node.paper { fill:var(--paper-node); stroke:#36a091; } | |
| .learn-node.upload { fill:var(--upload-node); stroke:#7e63cb; } | |
| .learn-label { fill:#1e1e1e; font-size:12px; font-weight:600; } | |
| .learning-empty { display:grid; place-items:center; } | |
| .empty-graph-copy { text-align:center; max-width:440px; padding:40px 20px; } | |
| .empty-graph-copy h4 { margin:0 0 10px; font-size:1.05rem; } | |
| .empty-graph-copy p { margin:0; color:var(--muted); line-height:1.6; } | |
| .timeline { display:flex; flex-direction:column; gap:10px; } | |
| .agent-step { border:1px solid var(--line); border-radius:18px; background:#ffffff; overflow:hidden; } | |
| .agent-summary { list-style:none; display:grid; grid-template-columns:42px 1fr; gap:12px; align-items:center; padding:12px; cursor:pointer; } | |
| .agent-summary::-webkit-details-marker { display:none; } | |
| .agent-index { width:42px; height:42px; border-radius:14px; display:grid; place-items:center; font-weight:700; color:var(--gold); background:rgba(255,122,26,.08); border:1px solid rgba(255,122,26,.18); } | |
| .agent-head { display:flex; justify-content:space-between; gap:12px; align-items:center; } | |
| .agent-head h4 { margin:0; font-size:.98rem; color:var(--text); } | |
| .agent-head span { font-size:.72rem; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); } | |
| .agent-copy { padding:0 14px 16px 66px; } | |
| .agent-copy p { margin:0; color:#2d2d2d; font-size:.93rem; line-height:1.6; } | |
| .candidate-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:18px; } | |
| .candidate-card { background:none; perspective:1400px; min-height:330px; } | |
| .candidate-card-inner { position:relative; width:100%; min-height:330px; transition:transform .8s cubic-bezier(.2,.7,.1,1); transform-style:preserve-3d; } | |
| .candidate-card:hover .candidate-card-inner,.candidate-card:focus .candidate-card-inner,.candidate-card:focus-within .candidate-card-inner { transform:rotateY(180deg); } | |
| .candidate-face { position:absolute; inset:0; padding:20px; border-radius:22px; border:1px solid var(--line); background:#ffffff; color:var(--text); backface-visibility:hidden; box-shadow:0 12px 24px rgba(0,0,0,.06); display:flex; flex-direction:column; gap:14px; } | |
| .candidate-back { transform:rotateY(180deg); } | |
| .candidate-top { display:flex; justify-content:space-between; align-items:center; gap:8px; } | |
| .chip { font-size:.72rem; text-transform:uppercase; letter-spacing:.12em; color:#0b6f66; padding:7px 10px; border-radius:999px; background:rgba(23,184,166,.08); border:1px solid rgba(23,184,166,.18); } | |
| .chip.alt { color:var(--gold); background:rgba(255,122,26,.08); border-color:rgba(255,122,26,.18); } | |
| .score { font-weight:700; color:var(--gold); } | |
| .candidate-face h4 { margin:0; font-size:1.08rem; line-height:1.35; } | |
| .candidate-face p { margin:0; color:#1e1e1e; line-height:1.65; font-size:.96rem; overflow-wrap:anywhere; } | |
| .meta-row { margin-top:auto; display:flex; justify-content:space-between; color:var(--muted); font-size:.88rem; gap:14px; } | |
| .mini { cursor:pointer; margin-top:8px; align-self:flex-start; color:var(--text); padding:10px 12px; border-radius:14px; border:1px solid var(--line); background:#ffffff; transition:all 0.2s; } | |
| .mini:hover { background: #f5f5f5; border-color: var(--chosen); } | |
| .papers-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; } | |
| .paper-card { border:1px solid var(--line); border-radius:18px; padding:16px; background:#ffffff; } | |
| .paper-topline { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:10px; } | |
| .paper-badge { font-size:.72rem; padding:6px 10px; border-radius:999px; background:rgba(98,141,255,.08); color:#3456b5; border:1px solid rgba(98,141,255,.18); } | |
| .paper-badge.alt { background:rgba(0,0,0,.04); color:#444; border-color:rgba(0,0,0,.08); } | |
| .doi-badge { background:rgba(255,122,26,.08); color:#8a4105; border-color:rgba(255,122,26,.18); } | |
| .paper-card h4 { margin:0 0 10px; line-height:1.35; font-size:1rem; } | |
| .paper-card p { margin:0 0 12px; line-height:1.6; color:#222; } | |
| .paper-links { display:flex; gap:12px; flex-wrap:wrap; } | |
| .paper-meta-stack { display:flex; flex-direction:column; gap:6px; color:#444; margin-bottom:12px; font-size:.9rem; } | |
| .paper-links a,.journal-card,.upload-note a { color:#0b63ce; text-decoration:none; } | |
| .journal-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; } | |
| .journal-card { border:1px solid var(--line); border-radius:18px; padding:16px; display:flex; justify-content:space-between; gap:14px; align-items:center; background:#ffffff; } | |
| .journal-card h4 { margin:0 0 6px; } | |
| .journal-card p { margin:0; color:var(--muted); line-height:1.5; } | |
| .upload-note { border:1px dashed rgba(0,0,0,.16); border-radius:18px; padding:16px; background:rgba(0,0,0,.015); color:#1f1f1f; line-height:1.6; } | |
| .prosebox { padding:18px; white-space:pre-wrap; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; line-height:1.55; color:#1b1b1b; } | |
| .gr-button-primary { background:linear-gradient(135deg,rgba(255,122,26,.92),rgba(240,108,22,.92)) !important; color:#ffffff !important; border:none !important; } | |
| .gr-button-secondary { background:#ffffff !important; color:var(--text) !important; border:1px solid var(--line) !important; } | |
| .ref-list { margin:0; padding-left:18px; } | |
| .ref-list li { margin-bottom:8px; line-height:1.5; } | |
| .parse-grid { display:grid; grid-template-columns:1.2fr 1fr; gap:14px; } | |
| .parse-card { border:1px solid var(--line); border-radius:18px; padding:16px; background:#ffffff; } | |
| .selection-panel { padding:18px; } | |
| footer { display:none !important; } | |
| @keyframes pulseEdge { to { stroke-dashoffset:-40; } } | |
| @media (max-width:1180px) { | |
| .model-switcher,.candidate-grid,.papers-grid,.journal-grid,.parse-grid { grid-template-columns:1fr; } | |
| .brain-svg { height:460px; } | |
| } | |
| """ | |
| CSS = BASE_CSS + "\n" + get_dvnc_layout_css() | |
| HEAD = """ | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> | |
| <script> | |
| function triggerRouteSwap(idx) { | |
| const container = document.getElementById('route_swap_payload'); | |
| if(!container) return; | |
| const input = container.querySelector('textarea') || container.querySelector('input'); | |
| if(input) { | |
| input.value = idx.toString(); | |
| input.dispatchEvent(new Event('input', { bubbles: true })); | |
| setTimeout(() => { | |
| const btn = document.getElementById('route_swap_apply'); | |
| if(btn) btn.click(); | |
| }, 150); | |
| } | |
| } | |
| </script> | |
| """ | |
| # ββ Gradio layout βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks(css=CSS, head=HEAD, theme=gr.themes.Base(), fill_height=True) as demo: | |
| # ββ Shared state ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| papers_state = gr.State([]) | |
| parsed_pdf_state = gr.State({}) | |
| ingest_payload_state = gr.State({}) | |
| route_state = gr.State(get_default_route_state()) | |
| # ββ Header ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| gr.HTML(""" | |
| <div id="dvnc-shell"> | |
| <div class="hero-bar"> | |
| <div class="brand"> | |
| <div class="logo" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"> | |
| <path d="M5 17L12 4l7 13"/> | |
| <path d="M8.5 12.5h7"/> | |
| <circle cx="12" cy="12" r="1.8" fill="currentColor" stroke="none"/> | |
| </svg> | |
| </div> | |
| <div> | |
| <h1>DVNC.AI</h1> | |
| <p>Sovereign discovery instrument Β· connectome-native reasoning</p> | |
| </div> | |
| </div> | |
| <div class="status"><span class="status-dot"></span><span>Live orchestration</span></div> | |
| </div> | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| # ββ Tab 1 Β· Discovery Engine ββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Discovery Engine"): | |
| model_html = gr.HTML(build_models_html("DVNC Sovereign")) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| model = gr.Dropdown( | |
| choices=[m["name"] for m in MODELS], | |
| value="DVNC Sovereign", | |
| label="Model tier", | |
| ) | |
| query = gr.Textbox( | |
| label="Discovery query", | |
| elem_classes=["querybox"], | |
| placeholder="Enter a scientific question, anomaly, or breakthrough directionβ¦", | |
| lines=4, | |
| ) | |
| with gr.Row(): | |
| run_btn = gr.Button("Run discovery", variant="primary") | |
| example_btn = gr.Button("Load example", variant="secondary") | |
| chat = gr.HTML(""" | |
| <div class="panel chat-panel"> | |
| <div class="chat-thread"> | |
| <div class="bubble bubble-ai"> | |
| <span class="role">DVNC</span> | |
| <p>Enter a query to activate the 7-agent discovery stack and illuminate the chosen path through the 3D connectome.</p> | |
| </div> | |
| </div> | |
| </div> | |
| """) | |
| with gr.Column(scale=3): | |
| connectome = gr.HTML(build_connectome_html(DEFAULT_PATH)) | |
| cards = gr.HTML("") | |
| output = gr.Markdown("# Discovery Output\n\nAwaiting query.") | |
| timeline = gr.HTML(get_initial_discovery_timeline_html()) | |
| route_swap_payload = gr.Textbox(value="", visible=False, elem_id="route_swap_payload") | |
| route_swap_apply = gr.Button("Apply route swap", visible=False, elem_id="route_swap_apply") | |
| # ββ Tab 2 Β· Self-Learning Graph βββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Self-Learning Graph"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| paper_query = gr.Textbox( | |
| label="Research topic / title / DOI / link", | |
| elem_classes=["querybox"], | |
| placeholder="e.g. self-assembling conductive biomaterials for cardiac repair", | |
| lines=3, | |
| ) | |
| search_mode = gr.Dropdown( | |
| choices=SEARCH_MODES, | |
| value="topic", | |
| label="Search mode", | |
| ) | |
| source_selector = gr.CheckboxGroup( | |
| choices=SOURCE_OPTIONS, | |
| value=DEFAULT_SOURCES, | |
| label="Sources", | |
| ) | |
| pdf_upload = gr.File(label="Upload PDF papers", file_types=[".pdf"], file_count="single") | |
| with gr.Row(): | |
| learn_btn = gr.Button("Discover papers", variant="primary") | |
| load_topic_btn = gr.Button("Load example topic", variant="secondary") | |
| upload_status = gr.Markdown("No PDF uploaded yet.") | |
| discovery_status = gr.Markdown("### No discovery results yet.") | |
| journal_panel = gr.HTML(build_journal_html("biomaterials cardiac repair")) | |
| gr.HTML('<div class="panel selection-panel"><h3 style="margin:0 0 12px;">Select papers to ingest</h3></div>') | |
| selection_box = gr.CheckboxGroup(choices=[], value=[], label="Candidate papers") | |
| parser_order = gr.CheckboxGroup( | |
| choices=["grobid", "docling", "pymupdf"], | |
| value=["grobid", "docling", "pymupdf"], | |
| label="Parser routing order", | |
| ) | |
| with gr.Row(): | |
| parse_btn = gr.Button("Parse uploaded PDF", variant="secondary") | |
| ingest_btn = gr.Button("Ingest selected into graph", variant="primary") | |
| with gr.Column(scale=3): | |
| learning_graph = gr.HTML(build_learning_graph_html([], [])) | |
| papers_panel = gr.HTML('<div class="panel papers-panel" style="padding:18px"><p>Search by topic, title, DOI, or link, then select papers before graph ingestion.</p></div>') | |
| parse_summary = gr.Markdown("### PDF parse status\n\nAwaiting upload.") | |
| parse_panel = gr.HTML('<div class="panel" style="padding:18px"><p>No parsed document yet.</p></div>') | |
| ingest_summary = gr.Markdown("### Graph ingest status\n\nAwaiting paper selection.") | |
| ingest_payload = gr.JSON(label="Graph ingest payload", value={"status": "empty", "nodes": [], "edges": []}) | |
| # ββ Event wiring ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| example_btn.click(fn=load_example, outputs=query) | |
| run_btn.click( | |
| fn=run_discovery, | |
| inputs=[query, model], | |
| outputs=[chat, connectome, timeline, cards, output, model_html, route_state], | |
| ) | |
| route_swap_apply.click( | |
| fn=apply_route_swap, | |
| inputs=[query, model, route_swap_payload, route_state], | |
| outputs=[chat, connectome, timeline, output, route_state], | |
| ) | |
| load_topic_btn.click(fn=load_paper_topic, outputs=paper_query) | |
| learn_btn.click( | |
| fn=run_paper_discovery, | |
| inputs=[paper_query, search_mode, source_selector, pdf_upload], | |
| outputs=[learning_graph, papers_panel, journal_panel, upload_status, selection_box, papers_state, discovery_status], | |
| ) | |
| parse_btn.click( | |
| fn=parse_uploaded_pdf, | |
| inputs=[pdf_upload, parser_order], | |
| outputs=[parse_summary, parsed_pdf_state], | |
| ).then( | |
| fn=render_parse_result, | |
| inputs=[parsed_pdf_state], | |
| outputs=[parse_panel], | |
| ) | |
| ingest_btn.click( | |
| fn=ingest_selected_papers, | |
| inputs=[paper_query, selection_box, papers_state, pdf_upload, parsed_pdf_state], | |
| outputs=[learning_graph, ingest_summary, ingest_payload], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |