azlaan428
fix: update pipeline stage 2 label to reflect Europe PMC
49714cd
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ARIA — Biomedical Research Intelligence</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;900&family=JetBrains+Mono:wght@300;400;600&display=swap" rel="stylesheet">
<style>
:root {
--bg: #050505;
--surface: #0f172a;
--border: #1e293b;
--accent: #00f5ff;
--accent-dim: #00f5ff15;
--text: #c9d1d9;
--text-dim: #5a6a7a;
--text-bright: #e6edf3;
--red: #ff4d6d;
--yellow: #f0b429;
--mono: 'JetBrains Mono', monospace;
--sans: 'Inter', sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--sans);
font-size: 15px;
line-height: 1.6;
min-height: 100vh;
}
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(var(--border) 1px, transparent 1px),
linear-gradient(90deg, var(--border) 1px, transparent 1px);
background-size: 40px 40px;
opacity: 0.3;
pointer-events: none;
z-index: 0;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 40px 24px 80px;
position: relative;
z-index: 1;
}
header {
margin-bottom: 48px;
border-bottom: 1px solid var(--border);
padding-bottom: 24px;
}
.logo {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.2em;
color: var(--accent);
text-transform: uppercase;
margin-bottom: 8px;
}
h1 {
font-family: var(--mono);
font-size: 32px;
font-weight: 600;
color: var(--text-bright);
letter-spacing: -0.02em;
}
h1 span { color: var(--accent); }
.subtitle {
font-size: 13px;
color: var(--text-dim);
font-family: var(--mono);
margin-top: 6px;
}
.search-section { margin-bottom: 32px; }
.input-wrapper {
position: relative;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
transition: border-color 0.2s;
}
.input-wrapper:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent)40;
}
.input-label {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.15em;
color: var(--accent);
padding: 12px 16px 0;
text-transform: uppercase;
}
textarea {
width: 100%;
background: transparent;
border: none;
color: var(--text-bright);
font-family: var(--sans);
font-size: 15px;
line-height: 1.6;
padding: 8px 16px 12px;
resize: none;
outline: none;
}
textarea::placeholder { color: var(--text-dim); }
.search-actions {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
border-top: 1px solid var(--border);
}
.hint {
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
}
button#submitBtn {
background: var(--accent);
color: #000;
border: none;
border-radius: 3px;
padding: 8px 20px;
font-family: var(--mono);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.08em;
cursor: pointer;
transition: opacity 0.2s;
}
button#submitBtn:hover { opacity: 0.85; }
button#submitBtn:disabled { opacity: 0.4; cursor: not-allowed; }
.pipeline {
display: none;
margin-bottom: 32px;
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
}
.pipeline.visible { display: block; }
.pipeline-header {
background: var(--surface);
padding: 10px 16px;
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
letter-spacing: 0.1em;
text-transform: uppercase;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.progress-wrap {
width: 160px;
height: 4px;
background: var(--border);
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
width: 0%;
background: var(--accent);
border-radius: 2px;
transition: width 0.6s ease;
box-shadow: 0 0 8px var(--accent);
}
.progress-pct {
font-family: var(--mono);
font-size: 11px;
color: var(--accent);
min-width: 36px;
text-align: right;
}
.stage {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
font-family: var(--mono);
font-size: 12px;
color: var(--text-dim);
transition: color 0.3s;
}
.stage:last-child { border-bottom: none; }
.stage.active { color: var(--accent); }
.stage.done { color: var(--text); }
.stage-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--border);
flex-shrink: 0;
transition: background 0.3s;
}
.stage.active .stage-dot {
background: var(--accent);
box-shadow: 0 0 8px var(--accent);
animation: pulse 1s infinite;
}
.stage.done .stage-dot { background: var(--text-dim); }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.results { display: none; }
.results.visible { display: block; }
.meta-bar {
display: flex;
gap: 16px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.badge {
font-family: var(--mono);
font-size: 11px;
padding: 4px 10px;
border-radius: 2px;
border: 1px solid var(--border);
color: var(--text-dim);
}
.badge.green {
border-color: var(--accent);
color: var(--accent);
background: var(--accent-dim);
}
.synthesis { margin-bottom: 32px; }
.section {
margin-bottom: 2px;
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
}
.section-header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: var(--surface);
cursor: pointer;
user-select: none;
position: relative;
}
.section-header:hover { background: #111820; }
.section-tag {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--accent);
min-width: 180px;
}
.section-chevron {
margin-left: auto;
font-size: 10px;
color: var(--text-dim);
transition: transform 0.2s;
}
.section.open .section-chevron { transform: rotate(180deg); }
.section-body {
display: none;
padding: 16px;
border-top: 1px solid var(--border);
font-size: 14px;
line-height: 1.75;
color: var(--text);
}
.section.open .section-body { display: block; }
.citations-block {
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
}
.citations-header {
padding: 12px 16px;
background: var(--surface);
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
}
.citations-list {
display: none;
padding: 16px;
}
.citations-list.open { display: block; }
.citation-item {
font-family: var(--mono);
font-size: 12px;
color: var(--text-dim);
padding: 8px 0;
border-bottom: 1px solid var(--border);
line-height: 1.6;
display: flex;
align-items: flex-start;
gap: 4px;
cursor: pointer;
}
.citation-item:last-child { border-bottom: none; }
.citation-item:hover { color: var(--text); }
.citation-pmid { color: var(--accent); font-weight: 600; }
.error-box {
display: none;
padding: 16px;
border: 1px solid var(--red);
border-radius: 4px;
background: #ff4d6d10;
font-family: var(--mono);
font-size: 13px;
color: var(--red);
margin-bottom: 24px;
}
.error-box.visible { display: block; }
footer {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--border);
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
}
.confidence-badge {
font-family: var(--mono);
font-size: 10px;
padding: 2px 8px;
border-radius: 2px;
margin-left: auto;
margin-right: 8px;
font-weight: 600;
letter-spacing: 0.05em;
}
.conf-strong { background: #00e5a020; color: #00e5a0; border: 1px solid #00e5a0; }
.conf-moderate { background: #f0b42920; color: #f0b429; border: 1px solid #f0b429; }
.conf-weak { background: #ff4d6d20; color: #ff4d6d; border: 1px solid #ff4d6d; }
.conf-tooltip {
display: none;
position: absolute;
background: #0d1117;
border: 1px solid var(--border);
padding: 6px 10px;
font-family: var(--mono);
font-size: 11px;
color: var(--text);
border-radius: 3px;
max-width: 280px;
z-index: 10;
top: 100%;
right: 0;
margin-top: 4px;
}
.confidence-badge:hover .conf-tooltip { display: block; }
.abstract-body {
display: none;
margin-top: 8px;
padding: 10px 12px;
background: #0d1117;
border-left: 2px solid var(--accent);
font-family: var(--sans);
font-size: 12px;
line-height: 1.7;
color: var(--text);
border-radius: 0 3px 3px 0;
}
.citation-item.expanded .abstract-body { display: block; }
.citation-toggle { color: var(--accent); font-size: 10px; margin-left: 6px; }
.citation-text { flex: 1; }
.citation-checkbox {
width: 14px;
height: 14px;
margin-right: 8px;
accent-color: var(--accent);
cursor: pointer;
flex-shrink: 0;
}
.review-bar {
display: none;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
background: var(--accent-dim);
border: 1px solid var(--accent);
border-radius: 4px;
margin-top: 12px;
font-family: var(--mono);
font-size: 12px;
color: var(--accent);
}
.review-bar.visible { display: flex; }
.review-btn {
background: var(--accent);
color: #000;
border: none;
padding: 6px 16px;
border-radius: 3px;
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
cursor: pointer;
}
.review-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.review-output {
display: none;
margin-top: 16px;
border: 1px solid var(--accent);
border-radius: 4px;
overflow: hidden;
}
.review-output.visible { display: block; }
.review-output-header {
background: var(--accent-dim);
padding: 10px 16px;
font-family: var(--mono);
font-size: 11px;
color: var(--accent);
letter-spacing: 0.1em;
text-transform: uppercase;
}
.review-output-body {
padding: 16px;
font-size: 14px;
line-height: 1.8;
color: var(--text);
}
.prediction-block {
display: none;
margin-bottom: 32px;
border: 1px solid #f0b42940;
border-radius: 4px;
overflow: hidden;
}
.prediction-block.visible { display: block; }
.prediction-header {
background: #f0b42910;
padding: 12px 16px;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: #f0b429;
border-bottom: 1px solid #f0b42940;
display: flex;
align-items: center;
justify-content: space-between;
}
.prediction-tag {
font-family: var(--mono);
font-size: 9px;
padding: 2px 8px;
border-radius: 2px;
border: 1px solid #f0b429;
color: #f0b429;
}
.prediction-section {
padding: 16px;
border-bottom: 1px solid #f0b42920;
}
.prediction-section:last-child { border-bottom: none; }
.prediction-label {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
margin-bottom: 8px;
}
.prediction-label.constructive { color: var(--accent); }
.prediction-label.destructive { color: var(--red); }
.prediction-text { font-size: 14px; line-height: 1.75; color: var(--text); }
.history-toggle {
position: fixed;
top: 20px;
right: 20px;
background: var(--surface);
border: 1px solid var(--border);
color: var(--text-dim);
font-family: var(--mono);
font-size: 11px;
padding: 6px 12px;
border-radius: 3px;
cursor: pointer;
z-index: 100;
letter-spacing: 0.08em;
}
.history-toggle:hover { border-color: var(--accent); color: var(--accent); }
.history-panel {
position: fixed;
top: 0;
right: -320px;
width: 300px;
height: 100vh;
background: var(--surface);
border-left: 1px solid var(--border);
z-index: 200;
transition: right 0.3s ease;
display: flex;
flex-direction: column;
overflow: hidden;
}
.history-panel.open { right: 0; }
.history-panel-header {
padding: 16px;
border-bottom: 1px solid var(--border);
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--accent);
display: flex;
justify-content: space-between;
align-items: center;
}
.history-close { cursor: pointer; color: var(--text-dim); font-size: 14px; }
.history-close:hover { color: var(--text); }
.history-list { overflow-y: auto; flex: 1; padding: 8px; }
.history-item {
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 3px;
margin-bottom: 6px;
cursor: pointer;
transition: border-color 0.2s;
}
.history-item:hover { border-color: var(--accent); }
.history-item-query {
font-family: var(--sans);
font-size: 12px;
color: var(--text);
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.history-item-meta { font-family: var(--mono); font-size: 10px; color: var(--text-dim); }
.history-empty {
padding: 24px 16px;
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
text-align: center;
}
.table-block {
display: none;
margin-bottom: 32px;
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
}
.table-block.visible { display: block; }
.table-block-header {
background: var(--surface);
padding: 12px 16px;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
}
.table-wrap { overflow-x: auto; }
table.data-table { width: 100%; border-collapse: collapse; font-size: 12px; }
table.data-table th {
background: var(--surface);
padding: 10px 14px;
text-align: left;
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--accent);
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
table.data-table td {
padding: 10px 14px;
border-bottom: 1px solid var(--border);
color: var(--text);
line-height: 1.5;
vertical-align: top;
}
table.data-table tr:last-child td { border-bottom: none; }
table.data-table tr:hover td { background: #ffffff05; }
/* Follow-up block */
.followup-block {
display: none;
margin-bottom: 32px;
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
}
.followup-block.visible { display: block; }
.followup-header {
background: var(--surface);
padding: 10px 16px;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
}
.followup-input-row {
padding: 12px 16px;
display: flex;
gap: 8px;
}
.followup-input-row input {
flex: 1;
background: #0d1117;
border: 1px solid var(--border);
color: var(--text-bright);
font-family: var(--sans);
font-size: 13px;
padding: 8px 12px;
border-radius: 3px;
outline: none;
transition: border-color 0.2s;
}
.followup-input-row input:focus { border-color: var(--accent); }
.followup-ask-btn {
background: var(--accent);
color: #000;
border: none;
padding: 8px 16px;
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
border-radius: 3px;
cursor: pointer;
transition: opacity 0.2s;
}
.followup-ask-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.followup-answer {
display: none;
padding: 16px;
border-top: 1px solid var(--border);
font-size: 14px;
line-height: 1.75;
color: var(--text);
}
.followup-answer.visible { display: block; }
/* ── Query Suggestions block ── */
.suggestions-block {
display: none;
margin-bottom: 32px;
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
}
.suggestions-block.visible { display: block; }
.suggestions-header {
background: var(--surface);
padding: 10px 16px;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.suggestions-loading {
display: inline-block;
width: 8px; height: 8px;
border-radius: 50%;
border: 1px solid var(--border);
border-top-color: var(--accent);
animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.suggestions-list { padding: 12px 16px; display: flex; flex-direction: column; gap: 6px; }
.suggestion-btn {
background: transparent;
border: 1px solid var(--border);
border-radius: 3px;
padding: 10px 14px;
color: var(--text);
font-family: var(--sans);
font-size: 13px;
text-align: left;
cursor: pointer;
transition: border-color 0.2s, color 0.2s;
line-height: 1.5;
}
.suggestion-btn:hover { border-color: var(--accent); color: var(--accent); }
.suggestion-prefix {
font-family: var(--mono);
font-size: 9px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--accent);
display: block;
margin-bottom: 3px;
}
@keyframes hexTrace {
0% { stroke-dashoffset: 200; opacity: 0.3; }
50% { opacity: 1; }
100% { stroke-dashoffset: 0; opacity: 0.3; }
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scaleIn {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
@keyframes glowPulse {
0%, 100% { opacity: 0; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.1); }
}
.aria-logo-hex-trace {
stroke-dasharray: 200;
stroke-dashoffset: 200;
animation: hexTrace 3s ease-in-out infinite;
}
.aria-logo-a { animation: fadeUp 0.8s ease 0.2s both; }
.aria-logo-crossbar { transform-origin: center; animation: scaleIn 0.6s ease 0.8s both; }
.aria-logo-glow { animation: glowPulse 3s ease-in-out infinite; }
</style>
</head>
<body>
<button class="history-toggle" onclick="toggleHistory()">&#9776; History</button>
<div class="history-panel" id="historyPanel">
<div class="history-panel-header">
<span>Query History</span>
<span class="history-close" onclick="toggleHistory()">&#10005;</span>
</div>
<div class="history-list" id="historyList">
<div class="history-empty">No history yet</div>
</div>
</div>
<div class="container">
<header>
<div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
<div style="position:relative;width:40px;height:40px;flex-shrink:0;">
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 4L34 12V28L20 36L6 28V12L20 4Z" stroke="#00f5ff" stroke-width="1" stroke-opacity="0.25"/>
<path class="aria-logo-hex-trace" d="M20 4L34 12V28L20 36L6 28V12L20 4Z" stroke="#00f5ff" stroke-width="2" stroke-linecap="round"/>
<path class="aria-logo-a" d="M12 28L20 12L28 28" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path class="aria-logo-crossbar" d="M16 22H24" stroke="#00f5ff" stroke-width="2" stroke-linecap="round"/>
</svg>
<div class="aria-logo-glow" style="position:absolute;inset:0;background:#00f5ff;border-radius:50%;filter:blur(16px);opacity:0;pointer-events:none;"></div>
</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-family:var(--sans);font-weight:900;font-size:24px;letter-spacing:-0.03em;color:#f1f5f9;line-height:1;">ARIA</span>
<div style="display:flex;align-items:center;gap:6px;">
<span style="width:6px;height:1px;background:#00f5ff;display:inline-block;"></span>
<span style="font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:0.25em;color:#475569;font-weight:700;">Research</span>
</div>
</div>
</div>
<div style="font-family:var(--mono);font-size:10px;letter-spacing:0.15em;color:#00f5ff;text-transform:uppercase;margin-bottom:6px;">AMD Developer Hackathon 2026 &mdash; Glitch Squad</div>
<h1>Autonomous Research Intelligence Agent</h1>
<div class="subtitle">Multi-agent biomedical literature synthesis &middot; PubMed &middot; Qwen2.5-72B on AMD MI300X &middot; by Azlaan Mohammad</div>
</header>
<div class="search-section">
<div class="input-wrapper">
<div class="input-label">Clinical Question</div>
<textarea id="queryInput" rows="3" placeholder="e.g. What machine learning methods are most effective for epilepsy detection from EEG signals?"></textarea>
<div class="search-actions">
<span class="hint">Enter to submit &middot; Shift+Enter for new line</span>
<button id="submitBtn" onclick="submitQuery()">Run Pipeline</button>
</div>
</div>
</div>
<div class="pipeline" id="pipeline">
<div class="pipeline-header">
<span>Pipeline Status</span>
<div style="display:flex;align-items:center;gap:10px;">
<div class="progress-wrap"><div class="progress-fill" id="progressFill"></div></div>
<span class="progress-pct" id="progressPct">0%</span>
</div>
</div>
<div class="stage" id="stage1"><div class="stage-dot"></div>Query Architect &mdash; generating MeSH-optimised PubMed queries</div>
<div class="stage" id="stage2"><div class="stage-dot"></div>Literature Scout &mdash; fetching fetching from PubMed and Europe PMC</div>
<div class="stage" id="stage3"><div class="stage-dot"></div>PRISMA Filter &mdash; screening papers for relevance</div>
<div class="stage" id="stage4"><div class="stage-dot"></div>Evidence Synthesiser &mdash; building structured synthesis</div>
<div class="stage" id="stage5"><div class="stage-dot"></div>Citation Builder &mdash; formatting references</div>
</div>
<div class="error-box" id="errorBox"></div>
<div class="results" id="results">
<div class="meta-bar" id="metaBar"></div>
<div class="synthesis" id="synthesis"></div>
<div style="display:flex;justify-content:flex-end;margin-bottom:12px;">
<button id="exportBtn" onclick="exportPDF()" style="background:transparent;border:1px solid var(--accent);color:var(--accent);padding:7px 18px;font-family:var(--mono);font-size:11px;border-radius:3px;cursor:pointer;letter-spacing:0.08em;">Export PDF</button>
</div>
<!-- Follow-up Question -->
<div class="followup-block" id="followupBlock">
<div class="followup-header">Ask a Follow-up</div>
<div class="followup-input-row">
<input id="followupInput" type="text" placeholder="e.g. What are the side effects of these treatments?" onkeydown="if(event.key==='Enter') askFollowup()">
<button class="followup-ask-btn" id="followupBtn" onclick="askFollowup()">Ask</button>
</div>
<div class="followup-answer" id="followupAnswer"></div>
</div>
<!-- Query Suggestions -->
<div class="suggestions-block" id="suggestionsBlock">
<div class="suggestions-header">
<span id="suggestionsSpinner" class="suggestions-loading"></span>
Suggested Follow-up Queries
</div>
<div class="suggestions-list" id="suggestionsList"></div>
</div>
<div class="table-block" id="tableBlock">
<div class="table-block-header" id="tableTitle">Evidence Comparison Table</div>
<div class="table-wrap">
<table class="data-table" id="dataTable">
<thead id="tableHead"></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
</div>
<div class="prediction-block" id="predictionBlock">
<div class="prediction-header">
<span>Predictive Model</span>
<span class="prediction-tag">AI FORECAST</span>
</div>
<div class="prediction-section">
<div class="prediction-label constructive">Constructive Forecast</div>
<div class="prediction-text" id="constructiveForecast"></div>
</div>
<div class="prediction-section">
<div class="prediction-label destructive">Destructive Forecast</div>
<div class="prediction-text" id="destructiveForecast"></div>
</div>
</div>
<div class="citations-block">
<div class="citations-header" onclick="toggleCitations()">
<span>References</span><span id="citChevron">&#9660;</span>
</div>
<div class="citations-list" id="citationsList"></div>
</div>
</div>
<div class="review-bar" id="reviewBar">
<span id="reviewBarText">0 papers selected</span>
<button class="review-btn" id="reviewBtn" onclick="generateReview()" disabled>Generate Literature Review</button>
</div>
<div class="review-output" id="reviewOutput">
<div class="review-output-header">Generated Literature Review</div>
<div class="review-output-body" id="reviewText"></div>
</div>
<footer>
<span>Powered by LangGraph &middot; Qwen2.5-72B &middot; AMD MI300X &middot; PubMed NCBI</span>
<span>AI-generated synthesis &mdash; verify against primary sources</span>
<span style="color:var(--text-dim);font-family:var(--mono);font-size:10px;">Azlaan Mohammad &middot; 2026</span>
</footer>
</div>
<script>
const SECTIONS = [
{ key: "## Background", label: "Background" },
{ key: "## Key Findings", label: "Key Findings" },
{ key: "## Level of Evidence", label: "Level of Evidence" },
{ key: "## Conflicting Evidence", label: "Conflicting Evidence" },
{ key: "## Research Gaps", label: "Research Gaps" },
{ key: "## Clinical Implications", label: "Clinical Implications" },
];
function parseSynthesis(text) {
const result = {};
for (let i = 0; i < SECTIONS.length; i++) {
const start = text.indexOf(SECTIONS[i].key);
if (start === -1) continue;
const contentStart = start + SECTIONS[i].key.length;
const nextSection = i + 1 < SECTIONS.length ? text.indexOf(SECTIONS[i + 1].key) : -1;
result[SECTIONS[i].label] = nextSection === -1
? text.slice(contentStart).trim()
: text.slice(contentStart, nextSection).trim();
}
return result;
}
const STAGE_PCT = { 1: 10, 2: 35, 3: 70, 4: 90, 5: 100 };
function setStage(n) {
for (let i = 1; i <= 5; i++) {
const el = document.getElementById("stage" + i);
el.classList.remove("active", "done");
if (i < n) el.classList.add("done");
else if (i === n) el.classList.add("active");
}
const pct = STAGE_PCT[n] || 0;
document.getElementById("progressFill").style.width = pct + "%";
document.getElementById("progressPct").textContent = pct + "%";
}
function toggleSection(el) { el.classList.toggle("open"); }
function toggleCitations() {
document.getElementById("citationsList").classList.toggle("open");
document.getElementById("citChevron").textContent =
document.getElementById("citationsList").classList.contains("open") ? "▲" : "▼";
}
// ── Render Results ────────────────────────────────────────
async function renderResults(data) {
const meta = document.getElementById("metaBar");
meta.innerHTML = `<div class="badge green">${data.paper_count} papers retrieved</div><div class="badge">${data.queries ? data.queries.length : 0} PubMed queries</div><div class="badge">Qwen2.5-72B on AMD MI300X</div>`;
const sections = parseSynthesis(data.synthesis);
const synthEl = document.getElementById("synthesis");
synthEl.innerHTML = "";
{
SECTIONS.forEach(s => {
if (!sections[s.label]) return;
const div = document.createElement("div");
div.className = "section open";
div.innerHTML = `<div class="section-header" onclick="toggleSection(this.parentElement)"><span class="section-tag">${s.label}</span><span class="section-chevron">▲</span></div><div class="section-body">${sections[s.label].replace(/\n/g, "<br>")}</div>`;
synthEl.appendChild(div);
});
}
const citList = document.getElementById("citationsList");
citList.innerHTML = "";
citList.classList.add("open");
const papersMap = data.papers || {};
selectedPapers = {};
updateReviewBar();
data.citations.split("\n").forEach(line => {
if (!line.trim()) return;
const pmidMatch = line.match(/PMID:\s*(\d+)/);
const pmid = pmidMatch ? pmidMatch[1] : null;
const paper = pmid ? papersMap[pmid] : null;
const div = document.createElement("div");
div.className = "citation-item";
const citText = pmid ? line.replace(/PMID:\s*\d+/, `<span class="citation-pmid">PMID: ${pmid}</span>`) : line;
const inner = document.createElement("div");
inner.className = "citation-text";
inner.innerHTML = citText + (paper ? `<span class="citation-toggle">▶ abstract</span><div class="abstract-body">${paper.abstract ? paper.abstract.replace(/\n/g,"<br>") : "Abstract not available"}</div>` : "");
if (paper) {
inner.addEventListener("click", (e) => {
if (e.target.type === "checkbox") return;
inner.parentElement.classList.toggle("expanded");
const tog = inner.querySelector(".citation-toggle");
if (tog) tog.textContent = inner.parentElement.classList.contains("expanded") ? "▼ abstract" : "▶ abstract";
});
}
if (pmid && paper) {
const cb = document.createElement("input");
cb.type = "checkbox";
cb.className = "citation-checkbox";
cb.addEventListener("change", () => {
if (cb.checked) selectedPapers[pmid] = paper;
else delete selectedPapers[pmid];
updateReviewBar();
});
div.appendChild(cb);
}
div.appendChild(inner);
citList.appendChild(div);
});
document.getElementById("results").classList.add("visible");
document.getElementById("followupBlock").classList.add("visible");
document.getElementById("followupAnswer").classList.remove("visible");
document.getElementById("followupAnswer").innerHTML = "";
document.getElementById("followupInput").value = "";
}
function updateProgress(pct) {
document.getElementById("progressFill").style.width = pct + "%";
document.getElementById("progressPct").textContent = pct + "%";
}
async function submitQuery() {
const q = document.getElementById("queryInput").value.trim();
if (!q) return;
const btn = document.getElementById("submitBtn");
btn.disabled = true;
document.getElementById("results").classList.remove("visible");
document.getElementById("errorBox").classList.remove("visible");
document.getElementById("predictionBlock").classList.remove("visible");
document.getElementById("tableBlock").classList.remove("visible");
document.getElementById("suggestionsBlock").classList.remove("visible");
document.getElementById("suggestionsList").innerHTML = "";
document.getElementById("pipeline").classList.add("visible");
setStage(1);
updateProgress(5);
lastQuery = q;
const es = new EventSource("/stream?query=" + encodeURIComponent(q));
es.addEventListener("stage", e => { const d = JSON.parse(e.data); setStage(d.stage); updateProgress(d.pct); });
es.addEventListener("queries", e => { updateProgress(JSON.parse(e.data).pct); });
es.addEventListener("papers", e => { updateProgress(JSON.parse(e.data).pct); });
es.addEventListener("synthesis", e => { updateProgress(JSON.parse(e.data).pct); });
es.addEventListener("done", async e => {
const data = JSON.parse(e.data);
lastResult = data;
updateProgress(100);
setStage(5);
es.close();
await renderResults(data); // instant render
setTimeout(() => scoreResults(data.synthesis), 2000);
setTimeout(() => runPredictiveModel(lastQuery, data.synthesis), 20000);
setTimeout(() => buildTable(lastQuery, data.synthesis, data.papers || {}), 38000);
setTimeout(() => fetchQuerySuggestions(lastQuery, data.synthesis), 56000);
saveSession(data, q);
btn.disabled = false;
});
es.addEventListener("error", e => {
es.close();
let msg = "Pipeline error";
try { msg = JSON.parse(e.data).message; } catch(err) {}
document.getElementById("errorBox").textContent = "Error: " + msg;
document.getElementById("errorBox").classList.add("visible");
btn.disabled = false;
});
}
document.getElementById("queryInput").addEventListener("keydown", e => {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submitQuery(); }
});
let lastResult = null;
let lastQuery = '';
// ── Query Suggestions ─────────────────────────────────────
async function fetchQuerySuggestions(query, synthesis) {
const block = document.getElementById('suggestionsBlock');
const list = document.getElementById('suggestionsList');
const spinner = document.getElementById('suggestionsSpinner');
block.classList.add('visible');
spinner.style.display = 'inline-block';
list.innerHTML = '';
try {
const res = await fetch('/suggest-queries', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, synthesis })
});
const data = await res.json();
spinner.style.display = 'none';
if (data.error || !data.suggestions || data.suggestions.length === 0) {
list.innerHTML = '<span style="font-family:var(--mono);font-size:11px;color:var(--text-dim);">Could not generate suggestions.</span>';
return;
}
data.suggestions.forEach((s, i) => {
const btn = document.createElement('button');
btn.className = 'suggestion-btn';
btn.innerHTML = `<span class="suggestion-prefix">Suggestion ${i + 1}</span>${s}`;
btn.addEventListener('click', () => {
document.getElementById('queryInput').value = s;
submitQuery();
});
list.appendChild(btn);
});
} catch(e) {
spinner.style.display = 'none';
list.innerHTML = '<span style="font-family:var(--mono);font-size:11px;color:var(--text-dim);">Could not generate suggestions.</span>';
}
}
async function exportPDF() {
const btn = document.getElementById('exportBtn');
btn.textContent = 'Generating...';
btn.disabled = true;
try {
const res = await fetch('/export-pdf', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
synthesis: lastResult.synthesis,
citations: lastResult.citations,
query: lastQuery,
paper_count: lastResult.paper_count
})
});
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'ARIA_report.pdf'; a.click();
URL.revokeObjectURL(url);
} finally {
btn.textContent = 'Export PDF';
btn.disabled = false;
}
}
async function askFollowup() {
const q = document.getElementById('followupInput').value.trim();
if (!q || !lastResult) return;
const btn = document.getElementById('followupBtn');
const answerEl = document.getElementById('followupAnswer');
btn.disabled = true;
btn.textContent = 'Thinking...';
answerEl.classList.add('visible');
answerEl.innerHTML = '<span style="color:var(--text-dim);font-family:var(--mono);font-size:12px;">Analysing papers...</span>';
try {
const res = await fetch('/followup', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
question: q,
original_question: lastQuery,
synthesis: lastResult.synthesis,
papers: lastResult.papers || {}
})
});
const data = await res.json();
if (data.error) throw new Error(data.error);
answerEl.innerHTML = data.answer.replace(/\n/g, '<br>');
} catch(e) {
answerEl.innerHTML = '<span style="color:var(--red);">Error: ' + e.message + '</span>';
} finally {
btn.disabled = false;
btn.textContent = 'Ask';
}
}
async function scoreResults(synthesis) {
try {
const res = await fetch('/score', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({synthesis}) });
const data = await res.json();
if (data.error || !data.scores) return;
const scores = data.scores;
document.querySelectorAll('.section').forEach(sec => {
const tag = sec.querySelector('.section-tag');
if (!tag) return;
const s = scores[tag.textContent.trim()];
if (!s) return;
const cls = s.score >= 8 ? 'conf-strong' : s.score >= 5 ? 'conf-moderate' : 'conf-weak';
const text = s.score >= 8 ? 'Strong' : s.score >= 5 ? 'Moderate' : 'Weak';
const badge = document.createElement('div');
badge.className = 'confidence-badge ' + cls;
badge.innerHTML = text + ' ' + s.score + '/10<div class="conf-tooltip">' + s.rationale + '</div>';
sec.querySelector('.section-header').insertBefore(badge, sec.querySelector('.section-chevron'));
});
} catch(e) { console.warn('Scoring failed:', e); }
}
let selectedPapers = {};
function updateReviewBar() {
const count = Object.keys(selectedPapers).length;
document.getElementById('reviewBar').classList.toggle('visible', count > 0);
document.getElementById('reviewBarText').textContent = count + ' paper' + (count !== 1 ? 's' : '') + ' selected';
document.getElementById('reviewBtn').disabled = count < 2;
}
async function generateReview() {
const btn = document.getElementById('reviewBtn');
btn.disabled = true;
btn.textContent = 'Generating...';
try {
const res = await fetch('/selective-review', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ question: lastQuery, papers: selectedPapers })
});
const data = await res.json();
if (data.error) throw new Error(data.error);
document.getElementById('reviewText').textContent = data.review;
document.getElementById('reviewOutput').classList.add('visible');
document.getElementById('reviewOutput').scrollIntoView({behavior: 'smooth'});
} catch(e) {
alert('Error: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = 'Generate Literature Review';
updateReviewBar();
}
}
async function runPredictiveModel(question, synthesis) {
try {
const res = await fetch('/predict', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({question, synthesis}) });
const data = await res.json();
if (data.error || !data.prediction) return;
const text = data.prediction;
const cm = text.match(/## Constructive Forecast\n([\s\S]*?)(?=## Destructive Forecast|$)/);
const dm = text.match(/## Destructive Forecast\n([\s\S]*?)$/);
if (cm) document.getElementById('constructiveForecast').textContent = cm[1].trim();
if (dm) document.getElementById('destructiveForecast').textContent = dm[1].trim();
document.getElementById('predictionBlock').classList.add('visible');
} catch(e) { console.warn('Prediction failed:', e); }
}
function toggleHistory() {
document.getElementById('historyPanel').classList.toggle('open');
if (document.getElementById('historyPanel').classList.contains('open')) loadHistory();
}
async function loadHistory() {
try {
const res = await fetch('/sessions');
const data = await res.json();
const list = document.getElementById('historyList');
if (!data.sessions || data.sessions.length === 0) {
list.innerHTML = '<div class="history-empty">No history yet</div>';
return;
}
list.innerHTML = '';
data.sessions.forEach(s => {
const div = document.createElement('div');
div.className = 'history-item';
div.innerHTML = `<div class="history-item-query">${s.query}</div><div class="history-item-meta">${s.timestamp} &middot; ${s.paper_count} papers</div>`;
div.addEventListener('click', () => {
lastResult = s; lastQuery = s.query;
document.getElementById('queryInput').value = s.query;
document.getElementById('results').classList.remove('visible');
document.getElementById('predictionBlock').classList.remove('visible');
document.getElementById('reviewOutput').classList.remove('visible');
document.getElementById('errorBox').classList.remove('visible');
document.getElementById('suggestionsBlock').classList.remove('visible');
renderResults(s); // instant render for history
document.getElementById('historyPanel').classList.remove('open');
document.getElementById('results').scrollIntoView({behavior: 'smooth'});
});
list.appendChild(div);
});
} catch(e) { console.warn('History load failed:', e); }
}
async function saveSession(data, query) {
try {
await fetch('/sessions/save', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({...data, query}) });
} catch(e) { console.warn('Session save failed:', e); }
}
async function buildTable(question, synthesis, papers) {
try {
const res = await fetch('/extract-table', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({question, synthesis, papers}) });
const data = await res.json();
if (data.error || !data.table) return;
const t = data.table;
if (!t.rows || t.rows.length === 0) return;
document.getElementById('tableTitle').textContent = t.title || 'Evidence Comparison Table';
document.getElementById('tableHead').innerHTML = '<tr>' + (t.columns || []).map(c => `<th>${c}</th>`).join('') + '</tr>';
document.getElementById('tableBody').innerHTML = t.rows.map(row => '<tr>' + row.map(cell => `<td>${cell}</td>`).join('') + '</tr>').join('');
document.getElementById('tableBlock').classList.add('visible');
} catch(e) { console.warn('Table extraction failed:', e); }
}
</script>
</body>
</html>