|
|
<!DOCTYPE html>
|
|
|
<html lang="en" data-theme="light">
|
|
|
<head>
|
|
|
<meta charset="utf-8" />
|
|
|
<title>Medical AI Assistant</title>
|
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
|
|
|
|
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
|
|
|
|
|
|
|
|
<link rel="stylesheet"
|
|
|
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
|
|
|
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
|
|
|
crossorigin="anonymous">
|
|
|
|
|
|
|
|
|
<link rel="stylesheet"
|
|
|
href="https://use.fontawesome.com/releases/v5.5.0/css/all.css"
|
|
|
integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU"
|
|
|
crossorigin="anonymous">
|
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"/>
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
<div class="page">
|
|
|
<div class="chat-card shadow-lg">
|
|
|
|
|
|
<div class="chat-header">
|
|
|
<div class="avatar">
|
|
|
<img src="https://cdn-icons-png.flaticon.com/512/387/387569.png" alt="Bot avatar"/>
|
|
|
<span class="status-dot" aria-hidden="true"></span>
|
|
|
</div>
|
|
|
|
|
|
<div class="hdr-text">
|
|
|
<div class="title">Dr.Rag</div>
|
|
|
<div class="subtitle">A calm space to check in with yourself</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="hdr-actions ml-auto">
|
|
|
<button class="btn-icon" id="themeToggle" aria-label="Toggle theme" title="Toggle theme">
|
|
|
<i class="far fa-moon"></i>
|
|
|
</button>
|
|
|
<button class="btn-icon" id="clearBtn" title="Clear chat" aria-label="Clear chat">
|
|
|
<i class="fas fa-broom"></i>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div id="chatBody" class="chat-body" role="log" aria-live="polite">
|
|
|
|
|
|
<div class="empty-state" id="emptyState">
|
|
|
<div class="empty-emoji">🩺</div>
|
|
|
<div class="empty-title">Hey, how are you feeling today?</div>
|
|
|
<div class="empty-hint">You can ask about symptoms, medications, or just talk.</div>
|
|
|
<div class="empty-suggestions">
|
|
|
<button class="chip" type="button">Track my headache today</button>
|
|
|
<button class="chip" type="button">What are common side-effects of ibuprofen?</button>
|
|
|
<button class="chip" type="button">Help me breathe and relax</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div id="typing" class="typing hidden" aria-hidden="true">
|
|
|
<span></span><span></span><span></span>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="chat-footer">
|
|
|
<form id="messageForm" class="w-100 d-flex" autocomplete="off">
|
|
|
<textarea id="text" name="msg" class="input" rows="1"
|
|
|
placeholder="Type your message... (Enter to send, Shift+Enter for new line)" required
|
|
|
aria-label="Message input"></textarea>
|
|
|
<button type="submit" id="send" class="send-btn" aria-label="Send message" title="Send">
|
|
|
<i class="fas fa-paper-plane"></i>
|
|
|
</button>
|
|
|
</form>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
|
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
|
|
|
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
|
|
|
crossorigin="anonymous"></script>
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
const chatBody = document.getElementById('chatBody');
|
|
|
const form = document.getElementById('messageForm');
|
|
|
const input = document.getElementById('text');
|
|
|
const sendBtn = document.getElementById('send');
|
|
|
const typing = document.getElementById('typing');
|
|
|
const clearBtn = document.getElementById('clearBtn');
|
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
|
const emptyState = document.getElementById('emptyState');
|
|
|
|
|
|
|
|
|
function scrollToBottom() {
|
|
|
chatBody.scrollTop = chatBody.scrollHeight;
|
|
|
}
|
|
|
|
|
|
function timeNow() {
|
|
|
const d = new Date();
|
|
|
return d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'});
|
|
|
}
|
|
|
|
|
|
function escapeHtml(str){
|
|
|
const div = document.createElement('div');
|
|
|
div.textContent = str;
|
|
|
return div.innerHTML;
|
|
|
}
|
|
|
|
|
|
function bubble(html, who='bot') {
|
|
|
|
|
|
emptyState?.classList.add('hidden');
|
|
|
|
|
|
const wrap = document.createElement('div');
|
|
|
wrap.className = `msg ${who}`;
|
|
|
wrap.innerHTML = `
|
|
|
${who === 'bot'
|
|
|
? `<div class="msg-avatar"><img src="https://cdn-icons-png.flaticon.com/512/387/387569.png" alt="Bot"/></div>`
|
|
|
: ``}
|
|
|
<div class="msg-bubble">
|
|
|
<div class="msg-text">${html}</div>
|
|
|
<div class="msg-time" aria-hidden="true">${timeNow()}</div>
|
|
|
</div>
|
|
|
`;
|
|
|
chatBody.appendChild(wrap);
|
|
|
scrollToBottom();
|
|
|
}
|
|
|
|
|
|
|
|
|
input.addEventListener('keydown', (e) => {
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
e.preventDefault();
|
|
|
form.dispatchEvent(new Event('submit'));
|
|
|
}
|
|
|
});
|
|
|
|
|
|
clearBtn.addEventListener('click', () => {
|
|
|
|
|
|
chatBody.querySelectorAll('.msg').forEach(m => m.remove());
|
|
|
|
|
|
emptyState?.classList.remove('hidden');
|
|
|
|
|
|
input.value = '';
|
|
|
input.focus();
|
|
|
});
|
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => {
|
|
|
const chip = e.target.closest('.chip');
|
|
|
if (!chip) return;
|
|
|
input.value = chip.textContent.trim();
|
|
|
form.dispatchEvent(new Event('submit'));
|
|
|
});
|
|
|
|
|
|
|
|
|
form.addEventListener('submit', function (e) {
|
|
|
e.preventDefault();
|
|
|
const rawText = input.value.trim();
|
|
|
if (!rawText) return;
|
|
|
|
|
|
|
|
|
bubble(escapeHtml(rawText), 'user');
|
|
|
|
|
|
|
|
|
input.value = '';
|
|
|
input.style.height = '48px';
|
|
|
|
|
|
|
|
|
typing.classList.remove('hidden');
|
|
|
sendBtn.disabled = true;
|
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
type: 'POST',
|
|
|
url: '/get',
|
|
|
data: { msg: rawText }
|
|
|
}).done(function (data) {
|
|
|
typing.classList.add('hidden');
|
|
|
sendBtn.disabled = false;
|
|
|
|
|
|
bubble(data, 'bot');
|
|
|
}).fail(function () {
|
|
|
typing.classList.add('hidden');
|
|
|
sendBtn.disabled = false;
|
|
|
bubble("Sorry, I couldn't reach the server. Please try again.", 'bot');
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
input.addEventListener('input', () => {
|
|
|
input.style.height = '48px';
|
|
|
input.style.height = Math.min(input.scrollHeight, 140) + 'px';
|
|
|
});
|
|
|
|
|
|
|
|
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
|
const savedTheme = localStorage.getItem('theme') || (prefersDark ? 'dark' : 'light');
|
|
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
|
updateThemeIcon(savedTheme);
|
|
|
|
|
|
themeToggle.addEventListener('click', () => {
|
|
|
const next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
|
|
document.documentElement.setAttribute('data-theme', next);
|
|
|
localStorage.setItem('theme', next);
|
|
|
updateThemeIcon(next);
|
|
|
});
|
|
|
|
|
|
function updateThemeIcon(theme) {
|
|
|
themeToggle.innerHTML = theme === 'dark'
|
|
|
? '<i class="far fa-sun"></i>'
|
|
|
: '<i class="far fa-moon"></i>';
|
|
|
}
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|
|
|
|