Medbot / static /index.html
Moncey10's picture
Add robust error handling and self-debugging support
250e5f3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MedBot — Hospital Admin AI</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Syne:wght@400;600;800&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0a0d12;
--surface: #111620;
--surface2: #161c28;
--border: #1e2a3a;
--accent: #00d4ff;
--accent2: #0099cc;
--green: #00e87a;
--red: #ff4d6a;
--yellow: #ffcc00;
--text: #e8edf5;
--muted: #4a5568;
--user-bubble: #0f2944;
--bot-bubble: #111620;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Syne', sans-serif;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Grid background */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(0,212,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0,212,255,0.03) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
z-index: 0;
}
/* Header */
header {
position: relative;
z-index: 10;
padding: 16px 28px;
background: var(--surface);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
}
.logo-icon {
width: 38px;
height: 38px;
background: linear-gradient(135deg, var(--accent), #0055aa);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.logo-text {
display: flex;
flex-direction: column;
}
.logo-title {
font-size: 18px;
font-weight: 800;
color: var(--accent);
letter-spacing: -0.5px;
line-height: 1;
}
.logo-sub {
font-size: 11px;
color: var(--muted);
font-family: 'JetBrains Mono', monospace;
letter-spacing: 1px;
}
.status-badge {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--green);
font-family: 'JetBrains Mono', monospace;
background: rgba(0,232,122,0.08);
padding: 6px 14px;
border-radius: 20px;
border: 1px solid rgba(0,232,122,0.2);
}
.status-dot {
width: 7px;
height: 7px;
background: var(--green);
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* Quick actions */
.quick-actions {
position: relative;
z-index: 10;
padding: 12px 28px;
background: var(--surface2);
border-bottom: 1px solid var(--border);
display: flex;
gap: 8px;
overflow-x: auto;
scrollbar-width: none;
}
.quick-actions::-webkit-scrollbar { display: none; }
.chip {
flex-shrink: 0;
padding: 6px 14px;
border: 1px solid var(--border);
border-radius: 20px;
font-size: 12px;
color: var(--muted);
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
font-family: 'JetBrains Mono', monospace;
background: transparent;
}
.chip:hover {
border-color: var(--accent);
color: var(--accent);
background: rgba(0,212,255,0.05);
}
/* Chat area */
.chat-container {
position: relative;
z-index: 5;
flex: 1;
overflow-y: auto;
padding: 24px 28px;
display: flex;
flex-direction: column;
gap: 16px;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
.chat-container::-webkit-scrollbar { width: 4px; }
.chat-container::-webkit-scrollbar-track { background: transparent; }
.chat-container::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
/* Welcome */
.welcome {
text-align: center;
padding: 40px 20px;
opacity: 0.6;
}
.welcome-icon {
font-size: 48px;
margin-bottom: 16px;
display: block;
}
.welcome h2 {
font-size: 22px;
font-weight: 800;
color: var(--text);
margin-bottom: 8px;
}
.welcome p {
font-size: 13px;
color: var(--muted);
font-family: 'JetBrains Mono', monospace;
line-height: 1.8;
}
/* Messages */
.message {
display: flex;
gap: 12px;
animation: fadeUp 0.3s ease;
max-width: 820px;
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.message.user {
align-self: flex-end;
flex-direction: row-reverse;
}
.avatar {
width: 34px;
height: 34px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
}
.avatar.bot {
background: linear-gradient(135deg, var(--accent), #0055aa);
}
.avatar.user {
background: linear-gradient(135deg, #553399, #2244aa);
}
.bubble {
padding: 12px 16px;
border-radius: 12px;
font-size: 14px;
line-height: 1.7;
max-width: 680px;
}
.bubble.bot {
background: var(--bot-bubble);
border: 1px solid var(--border);
border-top-left-radius: 2px;
color: var(--text);
}
.bubble.user {
background: var(--user-bubble);
border: 1px solid rgba(0,212,255,0.15);
border-top-right-radius: 2px;
color: var(--text);
}
.bubble pre {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
white-space: pre-wrap;
word-break: break-word;
}
.timestamp {
font-size: 10px;
color: var(--muted);
font-family: 'JetBrains Mono', monospace;
margin-top: 4px;
padding: 0 4px;
}
.message.user .timestamp { text-align: right; }
/* Typing indicator */
.typing {
display: flex;
gap: 12px;
align-items: flex-start;
animation: fadeUp 0.3s ease;
}
.typing-bubble {
background: var(--bot-bubble);
border: 1px solid var(--border);
border-top-left-radius: 2px;
border-radius: 12px;
padding: 14px 18px;
display: flex;
gap: 5px;
align-items: center;
}
.dot {
width: 6px;
height: 6px;
background: var(--accent);
border-radius: 50%;
animation: bounce 1.2s infinite;
}
.dot:nth-child(2) { animation-delay: 0.2s; }
.dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
/* Input area */
.input-area {
position: relative;
z-index: 10;
padding: 16px 28px 20px;
background: var(--surface);
border-top: 1px solid var(--border);
}
.input-wrapper {
display: flex;
gap: 12px;
align-items: flex-end;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 14px;
padding: 12px 14px;
transition: border-color 0.2s;
}
.input-wrapper:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(0,212,255,0.08);
}
#msg {
flex: 1;
background: transparent;
border: none;
outline: none;
color: var(--text);
font-family: 'Syne', sans-serif;
font-size: 14px;
resize: none;
min-height: 22px;
max-height: 100px;
line-height: 1.5;
}
#msg::placeholder { color: var(--muted); }
.send-btn {
width: 38px;
height: 38px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
border: none;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
.send-btn:hover {
transform: scale(1.05);
box-shadow: 0 0 16px rgba(0,212,255,0.4);
}
.send-btn:active { transform: scale(0.97); }
.send-btn svg {
width: 16px;
height: 16px;
fill: #000;
}
.input-hint {
text-align: center;
font-size: 11px;
color: var(--muted);
font-family: 'JetBrains Mono', monospace;
margin-top: 10px;
}
</style>
</head>
<body>
<header>
<div class="logo">
<div class="logo-icon">🏥</div>
<div class="logo-text">
<span class="logo-title">MedBot</span>
<span class="logo-sub">HOSPITAL ADMIN AI · v1.0</span>
</div>
</div>
<div class="status-badge">
<div class="status-dot"></div>
SYSTEM ONLINE
</div>
</header>
<div class="quick-actions">
<button class="chip" onclick="quickAsk('Show all admitted patients')">🛏 Admitted Patients</button>
<button class="chip" onclick="quickAsk('Show disease statistics')">📊 Disease Stats</button>
<button class="chip" onclick="quickAsk('Show appointments on 2025-05-20')">📅 Today\'s Appointments</button>
<button class="chip" onclick="quickAsk('Show Dr. Mehta\'s patients')">👨‍⚕️ Dr. Mehta\'s Patients</button>
<button class="chip" onclick="quickAsk('Show Dr. Singh\'s patients')">👨‍⚕️ Dr. Singh\'s Patients</button>
<button class="chip" onclick="quickAsk('Show Dr. Rao\'s patients')">👨‍⚕️ Dr. Rao\'s Patients</button>
</div>
<div class="chat-container" id="chat">
<div class="welcome" id="welcome">
<span class="welcome-icon">🤖</span>
<h2>Hospital Admin Assistant</h2>
<p>
Ask me anything about patients, doctors, appointments.<br>
I can also add patients, discharge them, or update appointments.
</p>
</div>
</div>
<div class="input-area">
<div class="input-wrapper">
<textarea id="msg" placeholder="Ask about patients, appointments, doctors..." rows="1"></textarea>
<button class="send-btn" onclick="sendMsg()">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
</svg>
</button>
</div>
<div class="input-hint">Enter to send · Shift+Enter for new line</div>
</div>
<script>
const chat = document.getElementById('chat');
const input = document.getElementById('msg');
// Auto resize textarea
input.addEventListener('input', () => {
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, 100) + 'px';
});
// Enter to send
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMsg();
}
});
function getTime() {
return new Date().toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit' });
}
function appendMessage(role, text) {
const welcome = document.getElementById('welcome');
if (welcome) welcome.remove();
const msg = document.createElement('div');
msg.className = `message ${role}`;
const avatar = document.createElement('div');
avatar.className = `avatar ${role}`;
avatar.textContent = role === 'bot' ? '🤖' : '👤';
const right = document.createElement('div');
const bubble = document.createElement('div');
bubble.className = `bubble ${role}`;
bubble.innerHTML = `<pre>${escapeHtml(text)}</pre>`;
const ts = document.createElement('div');
ts.className = 'timestamp';
ts.textContent = getTime();
right.appendChild(bubble);
right.appendChild(ts);
msg.appendChild(avatar);
msg.appendChild(right);
chat.appendChild(msg);
chat.scrollTop = chat.scrollHeight;
}
function showTyping() {
const typing = document.createElement('div');
typing.className = 'typing';
typing.id = 'typing';
typing.innerHTML = `
<div class="avatar bot">🤖</div>
<div class="typing-bubble">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>`;
chat.appendChild(typing);
chat.scrollTop = chat.scrollHeight;
}
function removeTyping() {
const t = document.getElementById('typing');
if (t) t.remove();
}
function escapeHtml(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
async function sendMsg() {
const msg = input.value.trim();
if (!msg) return;
input.value = '';
input.style.height = 'auto';
appendMessage('user', msg);
showTyping();
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ msg })
});
if (!res.ok) {
let errText = `Server error (Status ${res.status})`;
try {
const errData = await res.json();
errText += `: ${errData.detail || JSON.stringify(errData)}`;
} catch (e) {
try {
const rawText = await res.text();
if (rawText) errText += `: ${rawText}`;
} catch (e2) {}
}
throw new Error(errText);
}
const data = await res.json();
removeTyping();
appendMessage('bot', data.response || 'No response received.');
} catch (err) {
removeTyping();
appendMessage('bot', `⚠️ Error connecting to server or processing request:\n${err.message || err}`);
}
}
function quickAsk(text) {
input.value = text;
sendMsg();
}
</script>
</body>
</html>