Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>MeetGenius AI β Live Meeting</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script> | |
| tailwind.config = { darkMode: 'class' } | |
| </script> | |
| <style> | |
| /* π΄ Blinking recording dot */ | |
| @keyframes pulse { | |
| 0% { opacity: 1; } | |
| 50% { opacity: 0.3; } | |
| 100% { opacity: 1; } | |
| } | |
| .record-dot { | |
| animation: pulse 1s infinite; | |
| } | |
| /* π Zigzag waveform animation */ | |
| .wave { | |
| display: flex; | |
| gap: 4px; | |
| } | |
| .wave span { | |
| width: 4px; | |
| height: 12px; | |
| background: #22c55e; | |
| animation: waveMove 1s infinite ease-in-out; | |
| } | |
| .wave span:nth-child(2) { animation-delay: 0.1s; } | |
| .wave span:nth-child(3) { animation-delay: 0.2s; } | |
| .wave span:nth-child(4) { animation-delay: 0.3s; } | |
| .wave span:nth-child(5) { animation-delay: 0.4s; } | |
| @keyframes waveMove { | |
| 0%, 100% { height: 8px; } | |
| 50% { height: 24px; } | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-indigo-950 | |
| flex items-center justify-center px-6"> | |
| <div class="max-w-4xl w-full bg-white dark:bg-slate-900/90 | |
| border border-slate-700 rounded-2xl shadow-2xl p-10"> | |
| <h1 class="text-3xl font-bold text-center text-gray-900 dark:text-white mb-4"> | |
| ποΈ Live Meeting Intelligence | |
| </h1> | |
| <p class="text-center text-gray-600 dark:text-slate-400 mb-6"> | |
| Works with Google Meet, Zoom, Webex (mic-based recording) | |
| </p> | |
| <!-- π΄ RECORDING STATUS + TIMER --> | |
| <div class="flex items-center justify-center gap-4 mb-4"> | |
| <div id="recordingIndicator" class="hidden flex items-center gap-2 text-red-500 font-semibold"> | |
| <span class="w-3 h-3 bg-red-500 rounded-full record-dot"></span> | |
| Recording | |
| </div> | |
| <div id="timer" class="text-slate-400 font-mono text-lg"> | |
| 00:00:00 | |
| </div> | |
| </div> | |
| <!-- π ZIGZAG WAVEFORM --> | |
| <div id="waveform" class="hidden flex justify-center mb-6"> | |
| <div class="wave"> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| </div> | |
| </div> | |
| <!-- BUTTONS --> | |
| <div class="flex justify-center gap-4 mb-6"> | |
| <button id="startBtn" | |
| class="px-6 py-3 bg-green-600 text-white rounded-xl"> | |
| βΆ Start Recording | |
| </button> | |
| <button id="stopBtn" disabled | |
| class="px-6 py-3 bg-red-600 text-white rounded-xl"> | |
| βΉ Stop & Analyze | |
| </button> | |
| <button id="timestampBtn" | |
| class="px-6 py-3 bg-indigo-600 text-white rounded-xl"> | |
| β± Timestamp: OFF | |
| </button> | |
| <button id="clearBtn" | |
| class="px-6 py-3 bg-slate-600 text-white rounded-xl"> | |
| π§Ή Clear | |
| </button> | |
| </div> | |
| <div id="status" class="text-center text-slate-400 mb-6"></div> | |
| <!-- OUTPUT --> | |
| <div id="output" class="hidden space-y-6"> | |
| <div class="p-5 bg-slate-100 dark:bg-slate-800 rounded-xl border-l-4 border-indigo-500"> | |
| <h3 class="font-semibold mb-2">π§ Summary</h3> | |
| <pre id="summary" class="whitespace-pre-wrap"></pre> | |
| </div> | |
| <div class="p-5 bg-slate-100 dark:bg-slate-800 rounded-xl border-l-4 border-green-500"> | |
| <h3 class="font-semibold mb-2">π Notes</h3> | |
| <pre id="notes" class="whitespace-pre-wrap"></pre> | |
| </div> | |
| <div class="p-5 bg-slate-100 dark:bg-slate-800 rounded-xl border-l-4 border-yellow-500"> | |
| <h3 class="font-semibold mb-2">β Conclusion</h3> | |
| <pre id="conclusion" class="whitespace-pre-wrap"></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let mediaRecorder; | |
| let audioChunks = []; | |
| let timerInterval; | |
| let seconds = 0; | |
| let timestampsEnabled = false; | |
| const startBtn = document.getElementById("startBtn"); | |
| const stopBtn = document.getElementById("stopBtn"); | |
| const timestampBtn = document.getElementById("timestampBtn"); | |
| const clearBtn = document.getElementById("clearBtn"); | |
| const status = document.getElementById("status"); | |
| const timer = document.getElementById("timer"); | |
| const indicator = document.getElementById("recordingIndicator"); | |
| const waveform = document.getElementById("waveform"); | |
| /* β± Timer */ | |
| function startTimer() { | |
| seconds = 0; | |
| timer.textContent = "00:00:00"; | |
| timerInterval = setInterval(() => { | |
| seconds++; | |
| const h = String(Math.floor(seconds / 3600)).padStart(2, "0"); | |
| const m = String(Math.floor((seconds % 3600) / 60)).padStart(2, "0"); | |
| const s = String(seconds % 60).padStart(2, "0"); | |
| timer.textContent = `${h}:${m}:${s}`; | |
| }, 1000); | |
| } | |
| function stopTimer() { | |
| clearInterval(timerInterval); | |
| } | |
| /* βΆ Start Recording */ | |
| startBtn.onclick = async () => { | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| mediaRecorder = new MediaRecorder(stream); | |
| audioChunks = []; | |
| mediaRecorder.ondataavailable = e => audioChunks.push(e.data); | |
| mediaRecorder.start(); | |
| indicator.classList.remove("hidden"); | |
| waveform.classList.remove("hidden"); | |
| startTimer(); | |
| status.textContent = "ποΈ Recording live meeting..."; | |
| startBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| }; | |
| /* βΉ Stop Recording */ | |
| stopBtn.onclick = async () => { | |
| mediaRecorder.stop(); | |
| stopTimer(); | |
| indicator.classList.add("hidden"); | |
| waveform.classList.add("hidden"); | |
| status.textContent = "β³ Analyzing meeting..."; | |
| mediaRecorder.onstop = async () => { | |
| const blob = new Blob(audioChunks, { type: "audio/wav" }); | |
| const formData = new FormData(); | |
| formData.append("audio", blob, "live.wav"); | |
| formData.append("timestamps", timestampsEnabled); | |
| const res = await fetch("/live/process", { | |
| method: "POST", | |
| body: formData | |
| }); | |
| const data = await res.json(); | |
| document.getElementById("output").classList.remove("hidden"); | |
| document.getElementById("summary").textContent = data.summary; | |
| document.getElementById("notes").textContent = data.notes; | |
| document.getElementById("conclusion").textContent = data.conclusion; | |
| status.textContent = "β Meeting analyzed successfully"; | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| }; | |
| }; | |
| /* β± Timestamp Toggle */ | |
| timestampBtn.onclick = () => { | |
| timestampsEnabled = !timestampsEnabled; | |
| timestampBtn.textContent = timestampsEnabled | |
| ? "β± Timestamp: ON" | |
| : "β± Timestamp: OFF"; | |
| }; | |
| /* π§Ή Clear Output */ | |
| clearBtn.onclick = () => { | |
| document.getElementById("output").classList.add("hidden"); | |
| document.getElementById("summary").textContent = ""; | |
| document.getElementById("notes").textContent = ""; | |
| document.getElementById("conclusion").textContent = ""; | |
| status.textContent = ""; | |
| stopTimer(); | |
| timer.textContent = "00:00:00"; | |
| }; | |
| </script> | |
| </body> | |
| </html> | |