# ── Patch 3: fix Gradio 4.44.0 + Starlette API mismatch ── import starlette.templating as _st _orig_TemplateResponse = _st.Jinja2Templates.TemplateResponse def _safe_TemplateResponse(self, *args, **kwargs): # If the arguments are shifted, re-align them: if len(args) >= 2 and isinstance(args[0], str) and isinstance(args[1], dict): name_str = args[0] context_dict = args[1] request_obj = context_dict.get("request") args = (request_obj, name_str, context_dict) + args[2:] return _orig_TemplateResponse(self, *args, **kwargs) _st.Jinja2Templates.TemplateResponse = _safe_TemplateResponse # ── Patch 1: restore HfFolder for gradio 4.44.0 ── import unittest.mock as _mock import sys as _sys _hf_hub = __import__("huggingface_hub") if not hasattr(_hf_hub, "HfFolder"): class _FakeHfFolder: @staticmethod def get_token(): return None @staticmethod def save_token(token): pass @staticmethod def delete_token(): pass _hf_hub.HfFolder = _FakeHfFolder _sys.modules["huggingface_hub"].HfFolder = _FakeHfFolder # ── Patch 2: fix gradio_client schema bug ── import gradio_client.utils as _gcu _orig = _gcu._json_schema_to_python_type def _safe(schema, defs=None): if not isinstance(schema, dict): return "Any" return _orig(schema, defs) _gcu._json_schema_to_python_type = _safe # ── Imports ── import json import time import threading import datetime import gradio as gr from langchain_core.messages import HumanMessage, AIMessage, ToolMessage # ───────────────────────────────────────────────────────────────────── # CSS — 600+ lines of premium dark-theme styling # ───────────────────────────────────────────────────────────────────── # ───────────────────────────────────────────────────────────────────── # CSS — 600+ lines of premium dark-theme styling (Safari Compatible) # ───────────────────────────────────────────────────────────────────── CUSTOM_CSS = """ /* ── Google Fonts ── */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Sora:wght@300;400;600;700&display=swap'); /* ── Global Reset ── */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --bg-void: #04050a; --bg-deep: #080c14; --bg-surface: #0d1220; --bg-raised: #111827; --bg-glass: rgba(13,18,32,0.72); --bg-card: rgba(17,24,39,0.85); --border-subtle: rgba(99,120,190,0.12); --border-glow: rgba(99,179,237,0.28); --border-active: rgba(99,179,237,0.6); --blue-400: #60a5fa; --blue-500: #3b82f6; --blue-600: #2563eb; --blue-glow: rgba(59,130,246,0.35); --cyan-400: #22d3ee; --cyan-glow: rgba(34,211,238,0.25); --green-400: #4ade80; --green-500: #22c55e; --green-glow: rgba(74,222,128,0.25); --orange-400: #fb923c; --orange-glow: rgba(251,146,60,0.25); --red-400: #f87171; --red-glow: rgba(248,113,113,0.25); --purple-400: #c084fc; --purple-glow: rgba(192,132,252,0.2); --text-primary: #f0f4ff; --text-secondary: #94a3b8; --text-muted: #4b5563; --text-accent: #93c5fd; --font-display: 'Sora', sans-serif; --font-body: 'Inter', sans-serif; --font-mono: 'JetBrains Mono', monospace; --radius-sm: 6px; --radius-md: 10px; --radius-lg: 16px; --radius-xl: 22px; --shadow-card: 0 4px 24px rgba(0,0,0,0.45), 0 1px 4px rgba(0,0,0,0.3); --shadow-glow-blue: 0 0 20px rgba(59,130,246,0.3), 0 0 60px rgba(59,130,246,0.1); --shadow-glow-green: 0 0 20px rgba(74,222,128,0.25), 0 0 50px rgba(74,222,128,0.08); --shadow-glow-orange: 0 0 20px rgba(251,146,60,0.25); --transition-fast: 0.15s ease; --transition-smooth: 0.3s cubic-bezier(0.4,0,0.2,1); --transition-spring: 0.5s cubic-bezier(0.34,1.56,0.64,1); } /* ── App Shell ── */ .gradio-container { background: var(--bg-void) !important; font-family: var(--font-body) !important; min-height: 100vh !important; max-width: 100% !important; padding: 0 !important; } /* hide gradio chrome */ footer { display: none !important; } .gr-form { background: transparent !important; border: none !important; } .gr-box { background: transparent !important; border: none !important; } /* ── Ambient Background ── */ #nb-root { background: radial-gradient(ellipse 80% 50% at 20% 10%, rgba(37,99,235,0.08) 0%, transparent 60%), radial-gradient(ellipse 60% 40% at 80% 80%, rgba(124,58,237,0.06) 0%, transparent 55%), radial-gradient(ellipse 40% 30% at 60% 30%, rgba(6,182,212,0.04) 0%, transparent 50%), var(--bg-void); padding: 0; -webkit-font-smoothing: antialiased; /* Safari font smoothing */ } /* ─────────────────── HEADER ─────────────────── */ #nb-header { padding: 40px 48px 32px; border-bottom: 1px solid var(--border-subtle); display: flex; align-items: center; justify-content: space-between; gap: 32px; position: relative; overflow: hidden; } #nb-header::before { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(37,99,235,0.06) 0%, transparent 60%); pointer-events: none; } .nb-logo-group { display: flex; flex-direction: column; gap: 6px; } .nb-wordmark { font-family: var(--font-display) !important; font-size: 26px !important; font-weight: 700 !important; letter-spacing: -0.03em !important; color: var(--text-primary) !important; background: linear-gradient(135deg, #f0f4ff 0%, #93c5fd 50%, #60a5fa 100%); -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; display: inline-block; /* Essential for Safari background-clip */ line-height: 1.2 !important; margin: 0 !important; padding: 0 !important; } .nb-tagline { font-family: var(--font-mono) !important; font-size: 11px !important; font-weight: 400 !important; color: var(--blue-400) !important; letter-spacing: 0.12em !important; text-transform: uppercase !important; opacity: 0.8; margin: 0 !important; padding: 0 !important; } .nb-header-right { display: flex; align-items: center; gap: 24px; } .nb-status-chip { display: flex; align-items: center; gap: 8px; padding: 6px 14px; border-radius: 99px; border: 1px solid var(--border-subtle); background: var(--bg-card); font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary); letter-spacing: 0.06em; } .nb-status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green-400); box-shadow: 0 0 6px var(--green-glow); animation: nb-pulse-dot 2s ease-in-out infinite; } @keyframes nb-pulse-dot { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(0.85); } } /* ─────────────────── UPLOAD ROW ─────────────────── */ #nb-upload-row { padding: 24px 48px; display: flex; align-items: center; gap: 20px; border-bottom: 1px solid var(--border-subtle); background: rgba(13,18,32,0.4); } #nb-upload-row .gr-file-upload, #nb-upload-row input[type="file"] { display: none !important; } /* Upload button (gr.File renders as a button) */ #nb-upload-btn button { -webkit-appearance: none !important; /* Safari Reset */ appearance: none !important; background: var(--bg-card) !important; border: 1px dashed rgba(99,120,190,0.3) !important; color: var(--text-secondary) !important; font-family: var(--font-body) !important; font-size: 13px !important; border-radius: var(--radius-md) !important; padding: 10px 20px !important; cursor: pointer !important; transition: all var(--transition-smooth) !important; width: 100% !important; } #nb-upload-btn button:hover { border-color: var(--border-glow) !important; color: var(--text-primary) !important; background: rgba(59,130,246,0.08) !important; } /* Begin Investigation button */ #nb-begin-btn button { -webkit-appearance: none !important; /* Safari Reset */ appearance: none !important; background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 50%, #3b82f6 100%) !important; border: 1px solid rgba(99,179,237,0.4) !important; color: #fff !important; font-family: var(--font-display) !important; font-size: 14px !important; font-weight: 600 !important; border-radius: var(--radius-md) !important; padding: 11px 28px !important; cursor: pointer !important; transition: all var(--transition-smooth) !important; box-shadow: 0 4px 20px rgba(37,99,235,0.35), 0 0 0 0 rgba(59,130,246,0) !important; letter-spacing: 0.01em !important; } #nb-begin-btn button:hover { transform: translateY(-1px) !important; box-shadow: 0 6px 28px rgba(37,99,235,0.5), 0 0 40px rgba(59,130,246,0.2) !important; background: linear-gradient(135deg, #1e40af 0%, #2563eb 50%, #60a5fa 100%) !important; } #nb-begin-btn button:active { transform: translateY(0) !important; } .nb-patient-badge { display: flex; align-items: center; gap: 10px; padding: 8px 16px; border-radius: var(--radius-md); border: 1px solid var(--border-subtle); background: var(--bg-card); font-family: var(--font-mono); font-size: 12px; color: var(--text-secondary); } .nb-patient-badge .nb-badge-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--cyan-400); box-shadow: 0 0 8px var(--cyan-glow); } /* ─────────────────── MAIN LAYOUT ─────────────────── */ #nb-main { display: grid; grid-template-columns: 220px 1fr 340px; gap: 0; height: calc(100vh - 180px); overflow: hidden; } #nb-main > * { border-right: 1px solid var(--border-subtle); overflow-y: auto; overflow-x: hidden; min-width: 0; /* Crucial Safari fix for CSS grid blowout */ min-height: 0; /* Crucial Safari fix */ } #nb-main > *:last-child { border-right: none; } /* ── Scrollbar ── */ ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(99,120,190,0.25); border-radius: 99px; } ::-webkit-scrollbar-thumb:hover { background: rgba(99,120,190,0.45); } /* ─────────────────── LEFT PANEL — TIMELINE ─────────────────── */ #nb-left { padding: 24px 0; background: rgba(8,12,20,0.6); } .nb-panel-title { font-family: var(--font-mono) !important; font-size: 10px !important; font-weight: 500 !important; color: var(--text-muted) !important; letter-spacing: 0.16em !important; text-transform: uppercase !important; padding: 0 20px 16px !important; margin: 0 !important; border-bottom: 1px solid var(--border-subtle) !important; margin-bottom: 8px !important; } /* Loop counter */ .nb-loop-counter { margin: 12px 16px; padding: 12px 14px; border-radius: var(--radius-md); background: rgba(37,99,235,0.08); border: 1px solid rgba(59,130,246,0.15); } .nb-loop-label { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 4px; } .nb-loop-value { font-family: var(--font-display); font-size: 18px; font-weight: 700; color: var(--blue-400); line-height: 1; } .nb-loop-sub { font-family: var(--font-mono); font-size: 10px; color: var(--text-secondary); margin-top: 4px; } /* ── Timeline nodes ── */ .nb-timeline { padding: 16px 0; position: relative; } .nb-timeline-node { display: flex; align-items: center; gap: 12px; padding: 10px 20px; cursor: default; position: relative; transition: background var(--transition-fast); } .nb-timeline-node:hover { background: rgba(59,130,246,0.04); } .nb-node-line { position: absolute; left: 30px; top: 50%; width: 2px; height: calc(100% + 0px); background: var(--border-subtle); transform: translateX(-50%); z-index: 0; } .nb-node-dot { width: 22px; height: 22px; border-radius: 50%; border: 2px solid currentColor; background: var(--bg-deep); display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; z-index: 1; transition: all var(--transition-smooth); font-size: 9px; } .nb-node-label { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); transition: color var(--transition-smooth); font-weight: 500; } /* Node states */ .nb-timeline-node[data-state="future"] .nb-node-dot { color: var(--text-muted); border-color: var(--text-muted); opacity: 0.4; } .nb-timeline-node[data-state="active"] .nb-node-dot { color: var(--blue-400); border-color: var(--blue-400); background: rgba(59,130,246,0.12); box-shadow: 0 0 14px var(--blue-glow), 0 0 0 4px rgba(59,130,246,0.12); animation: nb-glow-pulse 1.5s ease-in-out infinite; } .nb-timeline-node[data-state="active"] .nb-node-label { color: var(--blue-400); } .nb-timeline-node[data-state="complete"] .nb-node-dot { color: var(--green-400); border-color: var(--green-400); background: rgba(74,222,128,0.1); box-shadow: 0 0 10px var(--green-glow); } .nb-timeline-node[data-state="complete"] .nb-node-label { color: var(--green-400); } .nb-timeline-node[data-state="contradiction"] .nb-node-dot { color: var(--orange-400); border-color: var(--orange-400); background: rgba(251,146,60,0.1); box-shadow: 0 0 10px var(--orange-glow); } .nb-timeline-node[data-state="contradiction"] .nb-node-label { color: var(--orange-400); } .nb-timeline-node[data-state="error"] .nb-node-dot { color: var(--red-400); border-color: var(--red-400); background: rgba(248,113,113,0.1); } @keyframes nb-glow-pulse { 0%, 100% { box-shadow: 0 0 14px var(--blue-glow), 0 0 0 4px rgba(59,130,246,0.12); } 50% { box-shadow: 0 0 22px rgba(59,130,246,0.6), 0 0 0 8px rgba(59,130,246,0.06); } } /* ── Event feed ── */ .nb-event-feed { margin: 16px 0 0; padding-top: 16px; border-top: 1px solid var(--border-subtle); } .nb-event-feed-title { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); letter-spacing: 0.14em; text-transform: uppercase; padding: 0 20px 12px; } .nb-event-item { display: flex; align-items: flex-start; gap: 10px; padding: 7px 20px; animation: nb-slide-in 0.4s cubic-bezier(0.4,0,0.2,1); } .nb-event-time { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); flex-shrink: 0; margin-top: 1px; } .nb-event-text { font-family: var(--font-body); font-size: 11px; color: var(--text-secondary); line-height: 1.4; } @keyframes nb-slide-in { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } } /* ─────────────────── CENTER PANEL ─────────────────── */ #nb-center { padding: 24px 32px; display: flex; flex-direction: column; gap: 0; background: transparent; } /* Reasoning messages */ .nb-msg-block { display: flex; gap: 14px; padding: 20px 0; border-bottom: 1px solid var(--border-subtle); animation: nb-fade-up 0.5s cubic-bezier(0.4,0,0.2,1); } .nb-msg-block:last-child { border-bottom: none; } @keyframes nb-fade-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } } .nb-msg-avatar { width: 36px; height: 36px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; margin-top: 2px; } .nb-msg-avatar.observe { background: rgba(99,179,237,0.12); border: 1px solid rgba(99,179,237,0.25); } .nb-msg-avatar.hypothesis{ background: rgba(168,85,247,0.12); border: 1px solid rgba(168,85,247,0.25); } .nb-msg-avatar.search { background: rgba(34,211,238,0.1); border: 1px solid rgba(34,211,238,0.2); } .nb-msg-avatar.interpret { background: rgba(74,222,128,0.1); border: 1px solid rgba(74,222,128,0.2); } .nb-msg-avatar.revise { background: rgba(251,146,60,0.1); border: 1px solid rgba(251,146,60,0.2); } .nb-msg-avatar.converge { background: rgba(59,130,246,0.12); border: 1px solid rgba(59,130,246,0.3); } .nb-msg-avatar.final { background: rgba(74,222,128,0.12); border: 1px solid rgba(74,222,128,0.3); } .nb-msg-content { flex: 1; min-width: 0; } .nb-msg-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; } .nb-msg-role { font-family: var(--font-mono); font-size: 11px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; } .nb-msg-role.observe { color: var(--cyan-400); } .nb-msg-role.hypothesis { color: var(--purple-400); } .nb-msg-role.search { color: var(--cyan-400); } .nb-msg-role.interpret { color: var(--green-400); } .nb-msg-role.revise { color: var(--orange-400); } .nb-msg-role.converge { color: var(--blue-400); } .nb-msg-role.final { color: var(--green-400); } .nb-msg-timestamp { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); margin-left: auto; } .nb-msg-body { font-family: var(--font-body); font-size: 14px; line-height: 1.7; color: var(--text-secondary); } /* Data grids inside messages */ .nb-data-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin: 14px 0; } .nb-data-cell { padding: 12px 14px; border-radius: var(--radius-md); background: rgba(13,18,32,0.8); border: 1px solid var(--border-subtle); } .nb-data-cell-label { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 5px; } .nb-data-cell-value { font-family: var(--font-mono); font-size: 15px; font-weight: 600; color: var(--text-primary); } .nb-data-cell-value.positive { color: var(--orange-400); } .nb-data-cell-value.neutral { color: var(--cyan-400); } /* Confidence bar */ .nb-confidence-row { display: flex; align-items: center; gap: 12px; margin: 12px 0; } .nb-confidence-label { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); min-width: 80px; text-transform: uppercase; letter-spacing: 0.07em; } .nb-confidence-bar { flex: 1; height: 5px; background: rgba(255,255,255,0.06); border-radius: 99px; overflow: hidden; position: relative; } .nb-confidence-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, var(--blue-500), var(--cyan-400)); box-shadow: 0 0 8px var(--cyan-glow); transition: width 0.8s cubic-bezier(0.4,0,0.2,1); } .nb-confidence-pct { font-family: var(--font-mono); font-size: 13px; font-weight: 600; color: var(--blue-400); min-width: 38px; text-align: right; } /* Searching animation */ .nb-searching-tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; } .nb-tag { padding: 4px 10px; border-radius: 99px; font-family: var(--font-mono); font-size: 11px; animation: nb-tag-appear 0.4s ease both; } .nb-tag.query { background: rgba(34,211,238,0.08); border: 1px solid rgba(34,211,238,0.25); color: var(--cyan-400); } .nb-tag.year { background: rgba(99,120,190,0.08); border: 1px solid rgba(99,120,190,0.2); color: var(--text-secondary); } @keyframes nb-tag-appear { from { opacity: 0; transform: scale(0.85); } to { opacity: 1; transform: scale(1); } } /* Streaming cursor */ .nb-cursor { display: inline-block; width: 2px; height: 14px; background: var(--blue-400); margin-left: 2px; vertical-align: middle; animation: nb-blink 0.85s step-end infinite; } @keyframes nb-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } /* ── Final Result card ── */ .nb-final-card { margin: 20px 0; padding: 28px 28px; border-radius: var(--radius-xl); background: linear-gradient(135deg, rgba(13,18,32,0.95) 0%, rgba(17,24,39,0.9) 100%); border: 1px solid rgba(74,222,128,0.3); box-shadow: var(--shadow-card), 0 0 40px rgba(74,222,128,0.08); } .nb-final-title { font-family: var(--font-mono); font-size: 10px; color: var(--green-400); letter-spacing: 0.18em; text-transform: uppercase; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; } .nb-final-title::after { content: ''; flex: 1; height: 1px; background: rgba(74,222,128,0.2); } .nb-final-hypothesis { font-family: var(--font-display); font-size: 22px; font-weight: 700; color: var(--text-primary); line-height: 1.3; margin-bottom: 20px; } .nb-stats-row { display: flex; gap: 12px; flex-wrap: wrap; margin: 16px 0; } .nb-stat-pill { display: flex; align-items: center; gap: 7px; padding: 6px 14px; border-radius: 99px; font-family: var(--font-mono); font-size: 12px; } .nb-stat-pill.green { background: rgba(74,222,128,0.08); border: 1px solid rgba(74,222,128,0.25); color: var(--green-400); } .nb-stat-pill.blue { background: rgba(59,130,246,0.08); border: 1px solid rgba(59,130,246,0.25); color: var(--blue-400); } .nb-stat-pill.orange { background: rgba(251,146,60,0.08); border: 1px solid rgba(251,146,60,0.25); color: var(--orange-400); } /* ─────────────────── RIGHT PANEL ─────────────────── */ #nb-right { padding: 20px; display: flex; flex-direction: column; gap: 0; background: rgba(4,5,10,0.5); } /* Tool cards */ .nb-tool-card { border-radius: var(--radius-lg); border: 1px solid var(--border-subtle); background: var(--bg-card); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); /* Safari Support */ margin-bottom: 14px; overflow: hidden; animation: nb-card-in 0.5s cubic-bezier(0.34,1.2,0.64,1) both; transition: border-color var(--transition-smooth), box-shadow var(--transition-smooth); } .nb-tool-card:hover { border-color: var(--border-glow); box-shadow: var(--shadow-card); } @keyframes nb-card-in { from { opacity: 0; transform: translateY(20px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } } .nb-card-header { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--border-subtle); } .nb-card-icon { font-size: 15px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-sm); } .nb-card-icon.pubmed { background: rgba(99,179,237,0.1); } .nb-card-icon.trials { background: rgba(74,222,128,0.1); } .nb-card-icon.omim { background: rgba(192,132,252,0.1); } .nb-card-icon.rag { background: rgba(251,191,36,0.08); } .nb-card-icon.biorxiv { background: rgba(34,211,238,0.1); } .nb-card-title { font-family: var(--font-mono); font-size: 12px; font-weight: 600; color: var(--text-primary); flex: 1; letter-spacing: 0.04em; } .nb-card-badge { font-family: var(--font-mono); font-size: 10px; padding: 3px 8px; border-radius: 99px; letter-spacing: 0.06em; } .nb-card-badge.searching { background: rgba(59,130,246,0.12); border: 1px solid rgba(59,130,246,0.3); color: var(--blue-400); animation: nb-shimmer 1.5s ease-in-out infinite; } .nb-card-badge.complete { background: rgba(74,222,128,0.1); border: 1px solid rgba(74,222,128,0.25); color: var(--green-400); } .nb-card-badge.error { background: rgba(251,146,60,0.1); border: 1px solid rgba(251,146,60,0.25); color: var(--orange-400); } @keyframes nb-shimmer { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } .nb-card-body { padding: 14px 16px; } .nb-card-query { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); margin-bottom: 10px; letter-spacing: 0.04em; } .nb-card-query span { color: var(--text-secondary); font-weight: 500; } .nb-card-results { display: flex; flex-direction: column; gap: 6px; } .nb-card-result-item { display: flex; align-items: center; gap: 8px; font-family: var(--font-body); font-size: 12px; color: var(--text-secondary); line-height: 1.4; } .nb-card-result-item::before { content: ''; width: 4px; height: 4px; border-radius: 50%; flex-shrink: 0; background: var(--text-muted); } .nb-card-result-item.supporting::before { background: var(--green-400); } .nb-card-result-item.contradicting::before { background: var(--orange-400); } .nb-card-progress { height: 2px; background: rgba(255,255,255,0.05); border-radius: 99px; overflow: hidden; margin: 10px 0; } .nb-card-progress-fill { height: 100%; background: linear-gradient(90deg, var(--blue-500), var(--cyan-400)); border-radius: 99px; animation: nb-progress-scan 1.8s ease-in-out infinite; } @keyframes nb-progress-scan { 0% { width: 0%; margin-left: 0; } 50% { width: 70%; margin-left: 15%; } 100% { width: 0%; margin-left: 100%; } } /* ── Revision card ── */ .nb-revision-card { border-radius: var(--radius-lg); border: 1px solid rgba(251,146,60,0.3); background: linear-gradient(135deg, rgba(17,24,39,0.95) 0%, rgba(20,15,10,0.9) 100%); box-shadow: 0 0 30px rgba(251,146,60,0.08); margin-bottom: 14px; overflow: hidden; animation: nb-card-in 0.6s cubic-bezier(0.34,1.2,0.64,1) both; } .nb-revision-header { display: flex; align-items: center; gap: 8px; padding: 12px 16px; background: rgba(251,146,60,0.06); border-bottom: 1px solid rgba(251,146,60,0.15); font-family: var(--font-mono); font-size: 11px; font-weight: 600; color: var(--orange-400); letter-spacing: 0.08em; text-transform: uppercase; } .nb-revision-body { padding: 16px; display: flex; flex-direction: column; gap: 12px; } .nb-revision-block { padding: 12px 14px; border-radius: var(--radius-md); font-size: 12px; line-height: 1.5; } .nb-revision-block.before { background: rgba(99,120,190,0.06); border: 1px solid rgba(99,120,190,0.15); color: var(--text-secondary); } .nb-revision-block.evidence { background: rgba(251,146,60,0.06); border: 1px solid rgba(251,146,60,0.2); color: var(--orange-400); } .nb-revision-block.after { background: rgba(74,222,128,0.06); border: 1px solid rgba(74,222,128,0.2); color: var(--green-400); } .nb-revision-block-title { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; margin-bottom: 6px; opacity: 0.7; } .nb-revision-arrow { text-align: center; color: var(--text-muted); font-size: 14px; animation: nb-bounce-arrow 1s ease-in-out 3; } @keyframes nb-bounce-arrow { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(3px); } } /* ── Warning / error cards ── */ .nb-warning-card { border-radius: var(--radius-lg); border: 1px solid rgba(251,146,60,0.35); background: rgba(20,12,4,0.9); padding: 18px; margin-bottom: 14px; animation: nb-card-in 0.5s ease both; } .nb-warning-title { font-family: var(--font-mono); font-size: 12px; font-weight: 600; color: var(--orange-400); display: flex; align-items: center; gap: 8px; margin-bottom: 10px; } .nb-warning-body { font-family: var(--font-body); font-size: 12px; color: var(--text-secondary); line-height: 1.6; } .nb-checklist { display: flex; flex-direction: column; gap: 4px; margin: 8px 0; } .nb-check-item { display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary); } .nb-check-item.done { color: var(--green-400); } .nb-check-item.pending { color: var(--text-muted); } /* ── Agent graph ── */ .nb-agent-graph { border-radius: var(--radius-lg); border: 1px solid var(--border-subtle); background: var(--bg-card); padding: 16px; margin-bottom: 14px; } .nb-graph-title { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); letter-spacing: 0.14em; text-transform: uppercase; margin-bottom: 14px; } .nb-graph-nodes { display: flex; flex-direction: column; align-items: flex-start; gap: 0; } .nb-graph-node { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-family: var(--font-mono); font-size: 11px; position: relative; } .nb-graph-node-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; position: relative; z-index: 1; transition: all var(--transition-smooth); } .nb-graph-node-dot.blue { background: var(--blue-400); box-shadow: 0 0 8px var(--blue-glow); } .nb-graph-node-dot.green { background: var(--green-400); box-shadow: 0 0 8px var(--green-glow); } .nb-graph-node-dot.orange { background: var(--orange-400); box-shadow: 0 0 8px var(--orange-glow); } .nb-graph-node-dot.grey { background: var(--text-muted); opacity: 0.4; } .nb-graph-node-dot.glow { animation: nb-node-glow 1.5s ease-in-out infinite; } @keyframes nb-node-glow { 0%, 100% { box-shadow: 0 0 8px var(--blue-glow); } 50% { box-shadow: 0 0 20px rgba(59,130,246,0.7); } } .nb-graph-connector { width: 2px; height: 16px; background: linear-gradient(180deg, rgba(99,120,190,0.3), transparent); margin-left: 4px; } /* ─────────────────── WELCOME STATE ─────────────────── */ .nb-welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; gap: 16px; text-align: center; padding: 60px 40px; } .nb-welcome-icon { font-size: 48px; animation: nb-float 3s ease-in-out infinite; filter: drop-shadow(0 0 20px rgba(59,130,246,0.4)); } @keyframes nb-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } } .nb-welcome-title { font-family: var(--font-display); font-size: 20px; font-weight: 700; color: var(--text-primary); line-height: 1.3; } .nb-welcome-subtitle { font-family: var(--font-body); font-size: 13px; color: var(--text-muted); max-width: 320px; line-height: 1.6; } /* ─────────────────── Gradio overrides ─────────────────── */ .gr-padded { padding: 0 !important; } .gap-2 { gap: 0 !important; } /* textbox-based hidden output */ .nb-hidden { display: none !important; } /* Gradio column wrappers */ .gradio-row { gap: 0 !important; margin: 0 !important; } /* File upload area */ .upload-container label { -webkit-appearance: none !important; /* Safari Reset */ appearance: none !important; background: var(--bg-card) !important; border: 1px dashed rgba(99,120,190,0.25) !important; border-radius: var(--radius-md) !important; color: var(--text-secondary) !important; font-family: var(--font-mono) !important; font-size: 12px !important; padding: 10px 20px !important; min-height: unset !important; cursor: pointer !important; transition: all var(--transition-smooth) !important; } .upload-container label:hover { border-color: var(--border-glow) !important; background: rgba(59,130,246,0.05) !important; color: var(--text-primary) !important; } /* Textarea / output */ textarea, input { -webkit-appearance: none !important; /* Safari Reset */ appearance: none !important; background: transparent !important; border: none !important; color: var(--text-secondary) !important; font-family: var(--font-mono) !important; font-size: 12px !important; resize: none !important; } /* Nuke all label spans Gradio injects */ .gr-block > label > span, .gr-form > label > span { display: none !important; } /* Download buttons */ .nb-download-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; } .nb-dl-btn { -webkit-appearance: none !important; /* Safari Reset */ appearance: none !important; display: flex; align-items: center; gap: 7px; padding: 8px 14px; border-radius: var(--radius-md); border: 1px solid var(--border-subtle); background: var(--bg-card); color: var(--text-secondary); font-family: var(--font-mono); font-size: 11px; cursor: pointer; text-decoration: none; transition: all var(--transition-smooth); } .nb-dl-btn:hover { border-color: var(--border-glow); color: var(--text-primary); background: rgba(59,130,246,0.06); } """ # ───────────────────────────────────────────────────────────────────── # HTML building blocks # ───────────────────────────────────────────────────────────────────── def ts(): return datetime.datetime.now().strftime("%H:%M:%S") def make_timeline_html(active_node: str = "", completed: list = None, contradictions: list = None): if completed is None: completed = [] if contradictions is None: contradictions = [] NODES = [ ("OBSERVE", "👁"), ("FORM HYPOTHESIS", "🧠"), ("PLAN SEARCH", "📋"), ("PUBMED", "🔬"), ("INTERPRET", "📊"), ("REVISION", "🔄"), ("CLINICALTRIALS", "🏥"), ("CONVERGE", "⚡"), ("RESEARCH BRIEF", "📄"), ] html = f"""
Agent halted: routing flag false.
", right() return instructions = "\n".join(payload["routing"]["agent_instructions"]) if payload.get("consensus") and payload["consensus"].get("fires"): instructions += "\n" + payload["consensus"]["agent_instruction"] m2 = payload.get("m2") or {} m5 = payload.get("m5") or {} deltas = m2.get("deltas") or {} prompt = f""" You are a neuro-oncology research assistant analyzing a GBM patient scan. PATIENT DATA: - Progression class (tentative): {m3.get("progression_class")} - M3 confidence: {m3.get("confidence")} (band: {m3.get("confidence_band")}) - Delta pattern flag: {m3.get("delta_pattern_flag")} - Biophysical deltas: delta_mu_d={deltas.get("delta_mu_d")}, delta_mu_r={deltas.get("delta_mu_r")}, delta_gamma={deltas.get("delta_gamma")}, over {deltas.get("delta_t_days")} days - cfDNA result: {m5.get("clinical_subtype")} (confidence {m5.get("detection_confidence")}) - MGMT status: {treatment.get("known_mgmt_status")} - IDH status: {treatment.get("known_idh_status")} - Regimen: {treatment.get("current_regimen")}, {treatment.get("days_since_rt_end")} days post-RT, {treatment.get("tmz_cycles_completed")} TMZ cycles completed AGENT INSTRUCTIONS FROM NEUROSIGHT: {instructions} TASK: Step 1 — Write your initial hypothesis based on the patient data above, before doing any research. Step 2 — Use the search tools to find evidence for or against it. You decide what to search and how many times. Step 3 — State your final hypothesis (revised if needed), confidence level, one alternative you considered and ruled out, and all sources. """ initial_state = { "messages": [HumanMessage(content=prompt)], "task_id": payload.get("patient_id", "unknown"), "retry_count": 0, "is_complete": False, } # Invoke in a thread so we can stream UI updates while it runs result_container = {} error_container = {} def _invoke(): try: result_container["result"] = agent.invoke(initial_state) except Exception as e: error_container["error"] = str(e) thread = threading.Thread(target=_invoke, daemon=True) thread.start() # Show animated states while backend runs tool_stages = [ ("PUBMED", "PubMed", "🔬", "pubmed"), ("INTERPRET", "bioRxiv", "📡", "biorxiv"), ("CLINICALTRIALS","ClinicalTrials", "🏥", "trials"), ("REVISION", "Internal RAG", "📚", "rag"), ] for node_key, tool_label, icon, icon_cls in tool_stages: if not thread.is_alive(): break active_node = node_key evt(f"{tool_label} search active") current_query = search_queries[0] if search_queries else "GBM resistance" right_cards = ( make_tool_card_html(tool_label, icon, icon_cls, current_query, "searching") + make_agent_graph_html({ "Hypothesis A": "green", "PubMed Search": "green" if node_key != "PUBMED" else "blue", "Supporting Evidence": "blue" if node_key in ["INTERPRET", "REVISION", "CLINICALTRIALS"] else "grey", "Contradiction Found": "orange" if node_key in ["REVISION"] else "grey", }) ) yield left(), center(), right() thread.join(timeout=6) # Wait for completion thread.join(timeout=120) # ── Error handling ── if "error" in error_container: err = error_container["error"] evt("⚠ Backend error encountered") warning = f"""{err[:200]}