rustvital-amd / static /index.html
brainworm2024's picture
Pass API key via query string, fix cardiac scenario
fbc77a0
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RustVital‑AMD | Zero‑Trust Medical AI</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.pii-highlight { background-color: #fee2e2; padding: 0 2px; border-radius: 3px; font-weight: bold; }
.agent-card { transition: all 0.3s ease; }
.agent-active { box-shadow: 0 0 12px rgba(139, 92, 246, 0.5); }
#gpu-gauge { transition: width 0.5s ease; }
#rehydrated-output { white-space: pre-wrap; }
</style>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col items-center p-4">
<div class="max-w-4xl w-full">
<div id="device-banner" class="bg-purple-100 text-purple-800 px-4 py-2 rounded-lg mb-2 text-sm text-center font-medium"></div>
<script>
fetch('/status')
.then(r=>r.json())
.then(s=>{
document.getElementById('device-banner').textContent =
`Running on ${s.device} – Model: ${s.model}`;
});
</script>
<div class="bg-white rounded-2xl shadow-xl p-6 mb-6">
<div class="flex items-center gap-3 mb-4">
<span class="text-4xl">🏥</span>
<h1 class="text-2xl font-bold text-gray-800">RustVital‑AMD</h1>
</div>
<p class="text-gray-500 mb-4">Zero‑trust medical triage with on‑chain audit</p>
<!-- Scenario Selector -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">Scenario</label>
<select id="scenario-select" onchange="loadScenario()" class="w-full border rounded-lg p-2">
<option value="custom">Custom Note</option>
<option value="cardiac">🚨 Cardiac Emergency (Dr. Chen’s Night Shift)</option>
<option value="pediatric">👶 Pediatric Trauma + DICOM</option>
</select>
</div>
<div class="flex flex-col md:flex-row gap-4">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 mb-1">Original Note</label>
<textarea id="patient-note" rows="5"
class="w-full border border-gray-300 rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
placeholder="Enter patient note...">Patient John Smith, 45 yo, chest pain</textarea>
</div>
<div id="redacted-preview" class="flex-1 hidden">
<label class="block text-sm font-medium text-gray-700 mb-1">Redacted (PII removed)</label>
<div id="redacted-text" class="bg-gray-100 p-3 rounded-lg text-sm font-mono"></div>
</div>
</div>
<!-- GPU Meter -->
<div class="mt-4 hidden" id="gpu-section">
<div class="flex items-center justify-between text-sm">
<span>GPU Utilization</span>
<span id="gpu-pct">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3 mt-1">
<div id="gpu-gauge" class="bg-purple-600 h-3 rounded-full" style="width: 0%"></div>
</div>
</div>
<button onclick="runTriage()" id="start-btn"
class="mt-4 w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold py-3 rounded-lg transition">
Start Triage
</button>
</div>
<!-- Agent Progress Cards -->
<div id="agent-progress" class="hidden mb-6 space-y-2">
<div class="agent-card bg-white p-3 rounded-xl shadow" id="card-shield">🛡️ Shield <span class="text-sm text-gray-500" id="shield-status">Waiting...</span></div>
<div class="agent-card bg-white p-3 rounded-xl shadow" id="card-triage">🧠 Triage <span class="text-sm text-gray-500" id="triage-status">Waiting...</span></div>
<div class="agent-card bg-white p-3 rounded-xl shadow" id="card-audit">⛓️ Audit <span class="text-sm text-gray-500" id="audit-status">Waiting...</span></div>
</div>
<!-- Rehydrated output (clinical view) -->
<div id="rehydrated-container" class="hidden bg-white rounded-2xl shadow-xl p-6 mb-6">
<h3 class="font-semibold text-gray-700 mb-2">Clinician’s View (Rehydrated)</h3>
<div id="rehydrated-output" class="bg-green-50 p-3 rounded-lg text-lg"></div>
</div>
<div id="result" class="space-y-4"></div>
</div>
<script>
const scenarios = {
cardiac: "Patient John Morrison, 67 yo, MRN 847291A, SSN 123-45-6789. Presents with acute substernal chest pain radiating to left arm. History of HTN, DM2. ECG shows ST elevation in leads II, III, aVF. Troponin pending.",
pediatric: "Patient Jane Doe, 7yo female, MRN 293-B, Parents: Michael & Sarah Doe, Phone 555-123-4567. Fell from tree, complaining of left upper quadrant pain. CT shows grade III splenic laceration. Vitals: HR 120, BP 90/60."
};
let piiMap = [];
function loadScenario() {
const sel = document.getElementById('scenario-select').value;
if (scenarios[sel]) {
document.getElementById('patient-note').value = scenarios[sel];
}
}
function highlightPlaceholders(text) {
return text.replace(/\[([A-Z_]+)_(\d+)\]/g, '<span class="pii-highlight">[$1_$2]</span>');
}
function rehydrateText(text) {
let result = text;
piiMap.forEach(p => {
const regex = new RegExp(p.placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
result = result.replace(regex, p.original);
});
return result;
}
async function runTriage() {
const note = document.getElementById('patient-note').value;
document.getElementById('agent-progress').classList.remove('hidden');
document.getElementById('gpu-section').classList.remove('hidden');
document.getElementById('rehydrated-container').classList.remove('hidden');
document.getElementById('start-btn').disabled = true;
document.getElementById('result').innerHTML = '';
document.getElementById('rehydrated-output').innerText = '';
piiMap = [];
const eventSource = new EventSource(`/triage/stream?patient_note=${encodeURIComponent(note)}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.agent === 'Shield' && data.status === 'completed') {
document.getElementById('card-shield').classList.add('agent-active');
document.getElementById('shield-status').textContent = `${data.pii_map.length} PII entities redacted`;
piiMap = data.pii_map;
} else if (data.agent === 'Triage' && data.status === 'started') {
document.getElementById('gpu-gauge').style.width = data.gpu_util + '%';
document.getElementById('gpu-pct').textContent = data.gpu_util + '%';
} else if (data.agent === 'Triage' && data.token) {
// rehydrate token and display
let displayToken = data.token;
piiMap.forEach(p => {
displayToken = displayToken.replace(new RegExp(p.placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), p.original);
});
document.getElementById('rehydrated-output').innerText += displayToken;
// update GPU gauge (to simulate high usage)
document.getElementById('gpu-gauge').style.width = '78%';
document.getElementById('gpu-pct').textContent = '78%';
} else if (data.agent === 'Triage' && data.status === 'completed') {
document.getElementById('card-triage').classList.add('agent-active');
document.getElementById('triage-status').textContent = 'Inference complete';
} else if (data.agent === 'Audit' && data.status === 'completed') {
document.getElementById('card-audit').classList.add('agent-active');
document.getElementById('audit-status').textContent = 'On-chain tx confirmed';
eventSource.close();
fetchFinalResult(note);
}
};
eventSource.onerror = () => {
eventSource.close();
fetchFinalResult(note);
};
}
async function fetchFinalResult(note) {
const resp = await fetch('/triage', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({patient_note: note, consent_hash: 'abc123'})
});
const data = await resp.json();
document.getElementById('redacted-preview').classList.remove('hidden');
document.getElementById('redacted-text').innerHTML = highlightPlaceholders(data.redacted_prompt);
document.getElementById('result').innerHTML = `
<div class="bg-white rounded-2xl shadow-xl p-6 space-y-4">
<div class="text-sm text-purple-700 bg-purple-50 px-3 py-1 rounded-full inline-block">
${data.device_info} · Model: Qwen2.5-${data.model_used}
</div>
<div><h3 class="font-semibold">Triage Result</h3>
<div class="bg-purple-50 p-3 rounded-lg text-lg font-medium">${data.triage_result}</div>
</div>
<div><h3 class="font-semibold">PII Redaction Map</h3>
<ul class="list-disc list-inside text-sm">${data.pii_map.map(p => `<li>🔴 <strong>${p.original}</strong> → <code>${p.placeholder}</code></li>`).join('')}</ul>
</div>
<div><h3 class="font-semibold">Redaction Proof (Schnorr Signature)</h3>
<code class="text-xs bg-gray-100 p-2 rounded block">${data.redaction_proof}</code>
</div>
<div><h3 class="font-semibold">On‑Chain Audit</h3>
<p>CID: <code>${data.cid}</code></p>
<p>Transaction: <a href="https://sepolia.basescan.org/tx/${data.transaction_hash}" target="_blank" class="text-purple-600 underline">${data.transaction_hash}</a></p>
</div>
</div>
`;
}
</script>
</body>
</html>