cctc-assistantai / index.html
CCTCASSISTANT's picture
Add 3 files
e082e5f verified
Raw
History Blame Contribute Delete
27.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Virtual Physician Assistant | Canadian Cardiovascular Training Center</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/docx/7.1.0/docx.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Poppins', sans-serif;
background-color: #f0f4f8;
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(220, 38, 38, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(220, 38, 38, 0);
}
}
.typing {
position: relative;
}
.typing::after {
content: "|";
position: absolute;
right: -5px;
animation: blink 1s infinite;
}
@keyframes blink {
0% { opacity: 0; }
50% { opacity: 1; }
100% { opacity: 0; }
}
.card-gradient {
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
}
.voice-wave {
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
height: 40px;
}
.voice-wave span {
display: block;
width: 4px;
height: 10px;
background: #3b82f6;
border-radius: 3px;
animation: voice-wave 1.5s infinite ease-in-out;
}
.voice-wave span:nth-child(2) {
animation-delay: 0.2s;
}
.voice-wave span:nth-child(3) {
animation-delay: 0.4s;
}
.voice-wave span:nth-child(4) {
animation-delay: 0.6s;
}
.voice-wave span:nth-child(5) {
animation-delay: 0.8s;
}
@keyframes voice-wave {
0% { height: 10px; }
50% { height: 30px; }
100% { height: 10px; }
}
</style>
</head>
<body class="min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-red-700 text-white shadow-lg">
<div class="container mx-auto px-4 py-6 flex justify-between items-center">
<div class="flex items-center space-x-4">
<div class="bg-white rounded-full p-2">
<i class="fas fa-heartbeat text-red-700 text-2xl"></i>
</div>
<div>
<h1 class="text-2xl font-bold">Canadian Cardiovascular Training Center</h1>
<p class="text-sm opacity-80">Virtual Physician Assistant</p>
</div>
</div>
<div class="hidden md:flex items-center space-x-2">
<i class="fas fa-user-md text-xl"></i>
<span>Specialist Support System</span>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-grow container mx-auto px-4 py-8">
<div class="max-w-4xl mx-auto">
<!-- Patient Info Card -->
<div class="bg-white rounded-xl shadow-md overflow-hidden mb-8 card-gradient">
<div class="p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Patient Information</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700">Full Name</label>
<p id="patient-name" class="mt-1 text-gray-900 font-medium">Not provided yet</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Date of Birth</label>
<p id="patient-dob" class="mt-1 text-gray-900 font-medium">Not provided yet</p>
</div>
</div>
</div>
</div>
<!-- Virtual Assistant Interface -->
<div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
<div class="p-6 border-b border-gray-200 bg-gray-50">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold text-gray-800">Virtual Physician Assistant</h2>
<div id="status-indicator" class="flex items-center space-x-2">
<span class="relative flex h-3 w-3">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
</span>
<span class="text-sm text-gray-600">Listening</span>
</div>
</div>
</div>
<!-- Conversation Display -->
<div class="p-6 h-96 overflow-y-auto" id="conversation-container">
<div class="flex mb-4">
<div class="flex-shrink-0 mr-3">
<div class="bg-blue-100 rounded-full p-2">
<i class="fas fa-heart text-blue-600"></i>
</div>
</div>
<div class="bg-blue-50 rounded-lg p-4 max-w-3xl">
<p id="assistant-message" class="text-gray-800 typing">Hello, I'm your Virtual Physician Assistant. I'll be asking you some preliminary questions before your appointment. Let's start with your full name.</p>
</div>
</div>
</div>
<!-- Controls -->
<div class="p-6 border-t border-gray-200 bg-gray-50">
<div class="flex items-center justify-between">
<button id="voice-btn" class="bg-red-600 hover:bg-red-700 text-white rounded-full p-4 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 pulse">
<i class="fas fa-microphone text-xl"></i>
</button>
<div id="voice-wave" class="voice-wave hidden">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<button id="complete-btn" class="hidden bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2">
Complete Screening
</button>
</div>
</div>
</div>
<!-- Patient Responses Summary -->
<div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
<div class="p-6 border-b border-gray-200 bg-gray-50">
<h2 class="text-xl font-semibold text-gray-800">Pre-Screening Summary</h2>
</div>
<div class="p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Question</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Response</th>
</tr>
</thead>
<tbody id="responses-table" class="bg-white divide-y divide-gray-200">
<!-- Responses will be added here dynamically -->
<tr>
<td colspan="2" class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">No responses recorded yet</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Download Button -->
<div class="text-center mb-8">
<button id="download-btn" class="hidden bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
<i class="fas fa-download mr-2"></i> Download Summary
</button>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-gray-800 text-white py-6">
<div class="container mx-auto px-4">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="mb-4 md:mb-0">
<p class="text-sm">&copy; 2023 Canadian Cardiovascular Training Center. All rights reserved.</p>
</div>
<div class="flex space-x-6">
<a href="#" class="text-gray-300 hover:text-white">
<i class="fas fa-shield-alt"></i> Privacy Policy
</a>
<a href="#" class="text-gray-300 hover:text-white">
<i class="fas fa-file-alt"></i> Terms of Service
</a>
</div>
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const voiceBtn = document.getElementById('voice-btn');
const voiceWave = document.getElementById('voice-wave');
const completeBtn = document.getElementById('complete-btn');
const conversationContainer = document.getElementById('conversation-container');
const responsesTable = document.getElementById('responses-table');
const patientName = document.getElementById('patient-name');
const patientDob = document.getElementById('patient-dob');
const statusIndicator = document.getElementById('status-indicator');
const assistantMessage = document.getElementById('assistant-message');
const downloadBtn = document.getElementById('download-btn');
// Speech recognition setup
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = false;
recognition.lang = 'en-CA';
// Speech synthesis
const synth = window.speechSynthesis;
// Questions array (now split into separate name and DOB questions)
const questions = [
"What's your first and last name?",
"What's your date of birth?",
"Have you experienced any chest pains within the last 2 months?",
"Have you been experiencing any shortness of breath?",
"Do you struggle walking up or down flights of stairs?",
"Have you had any recent changes to your medication?",
"Have you had any surgical procedures within the last 10 years?",
"Are there any specific notes or details you'd like to mention to the doctor?"
];
// Responses object
const responses = {};
let currentQuestionIndex = 0;
let isListening = false;
// Initialize responses object
questions.forEach((question, index) => {
responses[index] = {
question: question,
answer: "Not answered yet"
};
});
// Speak function
function speak(text) {
if (synth.speaking) {
synth.cancel();
}
const utterance = new SpeechSynthesisUtterance(text);
utterance.rate = 0.9;
utterance.pitch = 1;
utterance.onend = function() {
// When the assistant finishes speaking, start listening
if (currentQuestionIndex < questions.length) {
startListening();
}
};
// Update UI to show assistant is speaking
assistantMessage.classList.add('typing');
assistantMessage.textContent = "";
// Simulate typing effect
let i = 0;
const typingInterval = setInterval(() => {
if (i < text.length) {
assistantMessage.textContent += text.charAt(i);
i++;
conversationContainer.scrollTop = conversationContainer.scrollHeight;
} else {
clearInterval(typingInterval);
assistantMessage.classList.remove('typing');
synth.speak(utterance);
}
}, 30);
}
// Start listening function
function startListening() {
isListening = true;
voiceWave.classList.remove('hidden');
voiceBtn.classList.add('bg-red-700', 'pulse');
voiceBtn.classList.remove('bg-red-600');
recognition.start();
// Update status indicator
statusIndicator.innerHTML = `
<span class="relative flex h-3 w-3">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-green-500"></span>
</span>
<span class="text-sm text-gray-600">Listening</span>
`;
}
// Stop listening function
function stopListening() {
isListening = false;
voiceWave.classList.add('hidden');
voiceBtn.classList.remove('bg-red-700', 'pulse');
voiceBtn.classList.add('bg-red-600');
// Update status indicator
statusIndicator.innerHTML = `
<span class="relative flex h-3 w-3">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
</span>
<span class="text-sm text-gray-600">Ready</span>
`;
}
// Add message to conversation
function addMessage(speaker, message) {
const messageDiv = document.createElement('div');
messageDiv.className = 'flex mb-4';
if (speaker === 'assistant') {
messageDiv.innerHTML = `
<div class="flex-shrink-0 mr-3">
<div class="bg-blue-100 rounded-full p-2">
<i class="fas fa-heart text-blue-600"></i>
</div>
</div>
<div class="bg-blue-50 rounded-lg p-4 max-w-3xl">
<p class="text-gray-800">${message}</p>
</div>
`;
} else {
messageDiv.innerHTML = `
<div class="flex-shrink-0 mr-3 ml-auto">
<div class="bg-green-100 rounded-full p-2">
<i class="fas fa-user text-green-600"></i>
</div>
</div>
<div class="bg-green-50 rounded-lg p-4 max-w-3xl">
<p class="text-gray-800">${message}</p>
</div>
`;
}
conversationContainer.appendChild(messageDiv);
conversationContainer.scrollTop = conversationContainer.scrollHeight;
}
// Update responses table
function updateResponsesTable() {
responsesTable.innerHTML = '';
for (const key in responses) {
const response = responses[key];
const row = document.createElement('tr');
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${response.question}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${response.answer}</td>
`;
responsesTable.appendChild(row);
}
}
// Generate and download Word document
function generateWordDocument() {
const { Document, Paragraph, TextRun, Packer, HeadingLevel } = docx;
// Format current date
const today = new Date();
const dateString = today.toLocaleDateString('en-CA', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
// Create document content
const doc = new Document({
sections: [{
properties: {},
children: [
new Paragraph({
text: "Canadian Cardiovascular Training Center",
heading: HeadingLevel.HEADING_1,
spacing: { after: 200 }
}),
new Paragraph({
text: "Virtual Physician Assistant - Pre-Screening Report",
heading: HeadingLevel.HEADING_2,
spacing: { after: 200 }
}),
new Paragraph({
text: `Report Date: ${dateString}`,
spacing: { after: 400 }
}),
new Paragraph({
text: `Patient Name: ${patientName.textContent}`,
spacing: { after: 100 }
}),
new Paragraph({
text: `Date of Birth: ${patientDob.textContent}`,
spacing: { after: 400 }
}),
new Paragraph({
text: "Pre-Screening Responses:",
heading: HeadingLevel.HEADING_3,
spacing: { after: 200 }
}),
// Add questions and answers
...Object.values(responses).map(response => {
return new Paragraph({
children: [
new TextRun({
text: response.question + ": ",
bold: true
}),
new TextRun({
text: response.answer
})
],
spacing: { after: 150 }
});
})
]
}]
});
// Generate and download the document
Packer.toBlob(doc).then(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `PreScreening_${patientName.textContent.replace(/\s+/g, '_')}_${dateString.replace(/,/g, '')}.docx`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
}
// Handle voice button click
voiceBtn.addEventListener('click', function() {
if (isListening) {
recognition.stop();
stopListening();
} else {
if (currentQuestionIndex === 0) {
speak(questions[currentQuestionIndex]);
} else {
startListening();
}
}
});
// Handle complete button click
completeBtn.addEventListener('click', function() {
speak("Thank you for completing the pre-screening questions. Please wait for the specialist to see you. Your responses have been recorded and will be reviewed by the doctor.");
addMessage('assistant', "Pre-screening completed. Please wait for the specialist to see you.");
completeBtn.classList.add('hidden');
downloadBtn.classList.remove('hidden');
// Disable voice button
voiceBtn.disabled = true;
voiceBtn.classList.remove('bg-red-600', 'bg-red-700', 'pulse');
voiceBtn.classList.add('bg-gray-400');
// Update status indicator
statusIndicator.innerHTML = `
<span class="relative flex h-3 w-3">
<span class="relative inline-flex rounded-full h-3 w-3 bg-gray-500"></span>
</span>
<span class="text-sm text-gray-600">Completed</span>
`;
});
// Handle download button click
downloadBtn.addEventListener('click', generateWordDocument);
// Speech recognition events
recognition.onresult = function(event) {
const transcript = event.results[0][0].transcript;
stopListening();
// Add user message to conversation
addMessage('user', transcript);
// Store response
responses[currentQuestionIndex].answer = transcript;
// Confirm the response back to the user
let confirmationMessage = "";
if (currentQuestionIndex === 0) {
// For name question
patientName.textContent = transcript;
confirmationMessage = `I have your name as ${transcript}.`;
} else if (currentQuestionIndex === 1) {
// For DOB question
patientDob.textContent = transcript;
confirmationMessage = `I have your date of birth as ${transcript}.`;
} else {
// For all other questions
confirmationMessage = `You answered: ${transcript}.`;
}
// Add confirmation message
setTimeout(() => {
addMessage('assistant', confirmationMessage);
// Update responses table
updateResponsesTable();
// Move to next question or complete
currentQuestionIndex++;
if (currentQuestionIndex < questions.length) {
setTimeout(() => {
speak(questions[currentQuestionIndex]);
}, 1000);
} else {
completeBtn.classList.remove('hidden');
}
}, 1000);
};
recognition.onerror = function(event) {
console.error('Speech recognition error', event.error);
stopListening();
if (event.error === 'no-speech') {
addMessage('assistant', "I didn't hear anything. Please try again.");
setTimeout(() => {
speak(questions[currentQuestionIndex]);
}, 1000);
} else {
addMessage('assistant', "There was an error with the speech recognition. Please try again.");
voiceBtn.classList.remove('bg-red-700', 'pulse');
voiceBtn.classList.add('bg-red-600');
}
};
// Start with the first question
setTimeout(() => {
speak(questions[0]);
}, 1000);
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=CCTCASSISTANT/cctc-assistantai" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>