| <!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; |
| } |
| |
| |
| 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 { |
| 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 { |
| 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-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 { |
| 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; |
| } |
| |
| |
| .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 { |
| 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 { |
| 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'); |
| |
| |
| input.addEventListener('input', () => { |
| input.style.height = 'auto'; |
| input.style.height = Math.min(input.scrollHeight, 100) + 'px'; |
| }); |
| |
| |
| 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, '&') |
| .replace(/</g, '<') |
| .replace(/>/g, '>'); |
| } |
| |
| 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> |