Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Meeting Minutes Transcriber</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"> | |
| <style> | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .pulse-animation { | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| .waveform { | |
| display: flex; | |
| align-items: center; | |
| height: 40px; | |
| width: 100%; | |
| } | |
| .waveform-bar { | |
| background-color: #4f46e5; | |
| margin: 0 1px; | |
| width: 3px; | |
| transition: height 0.2s ease; | |
| } | |
| .transcript-container { | |
| max-height: 300px; | |
| overflow-y: auto; | |
| scrollbar-width: thin; | |
| } | |
| .transcript-container::-webkit-scrollbar { | |
| width: 5px; | |
| } | |
| .transcript-container::-webkit-scrollbar-thumb { | |
| background-color: #c7d2fe; | |
| border-radius: 10px; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 font-sans"> | |
| <div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl min-h-screen"> | |
| <!-- App Header --> | |
| <header class="bg-indigo-600 text-white p-4"> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-microphone text-xl"></i> | |
| <h1 class="text-xl font-bold">MeetNotes</h1> | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button class="p-1 rounded-full hover:bg-indigo-500"> | |
| <i class="fas fa-cog"></i> | |
| </button> | |
| <button class="p-1 rounded-full hover:bg-indigo-500"> | |
| <i class="fas fa-question-circle"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="p-4"> | |
| <!-- Recording Section --> | |
| <div class="bg-indigo-50 rounded-lg p-4 mb-6"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h2 class="text-lg font-semibold text-indigo-800">Meeting Recording</h2> | |
| <span id="recording-time" class="text-sm font-medium text-indigo-600">00:00:00</span> | |
| </div> | |
| <div class="waveform mb-4" id="waveform"> | |
| <!-- Dynamic waveform bars will be inserted here by JS --> | |
| </div> | |
| <div class="flex justify-center space-x-6"> | |
| <button id="record-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white rounded-full p-4 shadow-lg transition-all"> | |
| <i class="fas fa-microphone text-2xl"></i> | |
| </button> | |
| <button id="stop-btn" class="bg-red-600 hover:bg-red-700 text-white rounded-full p-4 shadow-lg transition-all opacity-50 cursor-not-allowed"> | |
| <i class="fas fa-stop text-2xl"></i> | |
| </button> | |
| <button id="pause-btn" class="bg-yellow-500 hover:bg-yellow-600 text-white rounded-full p-4 shadow-lg transition-all opacity-50 cursor-not-allowed"> | |
| <i class="fas fa-pause text-2xl"></i> | |
| </button> | |
| </div> | |
| <div class="mt-4 text-center text-sm text-gray-600"> | |
| <p id="recording-status">Ready to record meeting</p> | |
| </div> | |
| </div> | |
| <!-- Transcription Section --> | |
| <div class="bg-white border border-gray-200 rounded-lg p-4 mb-6"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h2 class="text-lg font-semibold text-gray-800">Live Transcription</h2> | |
| <button id="copy-transcript" class="text-indigo-600 hover:text-indigo-800 text-sm font-medium"> | |
| <i class="fas fa-copy mr-1"></i> Copy | |
| </button> | |
| </div> | |
| <div class="transcript-container bg-gray-50 p-3 rounded-lg mb-3" id="transcript-container"> | |
| <p id="transcript-text" class="text-gray-700">Transcription will appear here as you speak...</p> | |
| </div> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span id="word-count">0 words</span> | |
| <span id="speaker-count">1 speaker</span> | |
| </div> | |
| </div> | |
| <!-- Minutes Generation Section --> | |
| <div class="bg-white border border-gray-200 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h2 class="text-lg font-semibold text-gray-800">Meeting Minutes</h2> | |
| <button id="generate-minutes" class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1 rounded text-sm font-medium"> | |
| <i class="fas fa-magic mr-1"></i> Generate | |
| </button> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="meeting-title" class="block text-sm font-medium text-gray-700 mb-1">Meeting Title</label> | |
| <input type="text" id="meeting-title" class="w-full p-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500" placeholder="Weekly Team Sync"> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="attendees" class="block text-sm font-medium text-gray-700 mb-1">Attendees</label> | |
| <input type="text" id="attendees" class="w-full p-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500" placeholder="John, Sarah, Mike"> | |
| </div> | |
| <div class="bg-gray-50 p-3 rounded-lg min-h-40"> | |
| <div id="minutes-content" class="text-gray-700"> | |
| <p>Key discussion points, decisions, and action items will be summarized here...</p> | |
| </div> | |
| </div> | |
| <div class="mt-3 flex justify-end space-x-2"> | |
| <button id="save-minutes" class="bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded text-sm font-medium"> | |
| <i class="fas fa-save mr-1"></i> Save | |
| </button> | |
| <button id="share-minutes" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-sm font-medium"> | |
| <i class="fas fa-share-alt mr-1"></i> Share | |
| </button> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Bottom Navigation --> | |
| <nav class="bg-white border-t border-gray-200 p-2"> | |
| <div class="flex justify-around"> | |
| <button class="p-2 text-indigo-600 rounded-full"> | |
| <i class="fas fa-home text-xl"></i> | |
| </button> | |
| <button class="p-2 text-gray-500 hover:text-indigo-600 rounded-full"> | |
| <i class="fas fa-history text-xl"></i> | |
| </button> | |
| <button class="p-2 text-gray-500 hover:text-indigo-600 rounded-full"> | |
| <i class="fas fa-folder text-xl"></i> | |
| </button> | |
| <button class="p-2 text-gray-500 hover:text-indigo-600 rounded-full"> | |
| <i class="fas fa-user text-xl"></i> | |
| </button> | |
| </div> | |
| </nav> | |
| </div> | |
| <script> | |
| // DOM Elements | |
| const recordBtn = document.getElementById('record-btn'); | |
| const stopBtn = document.getElementById('stop-btn'); | |
| const pauseBtn = document.getElementById('pause-btn'); | |
| const recordingStatus = document.getElementById('recording-status'); | |
| const recordingTime = document.getElementById('recording-time'); | |
| const transcriptText = document.getElementById('transcript-text'); | |
| const wordCount = document.getElementById('word-count'); | |
| const speakerCount = document.getElementById('speaker-count'); | |
| const copyTranscript = document.getElementById('copy-transcript'); | |
| const generateMinutes = document.getElementById('generate-minutes'); | |
| const minutesContent = document.getElementById('minutes-content'); | |
| const saveMinutes = document.getElementById('save-minutes'); | |
| const shareMinutes = document.getElementById('share-minutes'); | |
| const waveform = document.getElementById('waveform'); | |
| // App State | |
| let isRecording = false; | |
| let isPaused = false; | |
| let recordingStartTime; | |
| let timerInterval; | |
| let transcript = ''; | |
| let speakers = 1; | |
| // Create waveform bars | |
| for (let i = 0; i < 50; i++) { | |
| const bar = document.createElement('div'); | |
| bar.className = 'waveform-bar'; | |
| bar.style.height = `${Math.random() * 10 + 5}px`; | |
| waveform.appendChild(bar); | |
| } | |
| // Update waveform animation | |
| function updateWaveform() { | |
| const bars = document.querySelectorAll('.waveform-bar'); | |
| bars.forEach(bar => { | |
| bar.style.height = `${Math.random() * 20 + 5}px`; | |
| }); | |
| if (isRecording && !isPaused) { | |
| requestAnimationFrame(updateWaveform); | |
| } | |
| } | |
| // Update recording timer | |
| function updateTimer() { | |
| const now = new Date(); | |
| const elapsed = new Date(now - recordingStartTime); | |
| const hours = elapsed.getUTCHours().toString().padStart(2, '0'); | |
| const minutes = elapsed.getUTCMinutes().toString().padStart(2, '0'); | |
| const seconds = elapsed.getUTCSeconds().toString().padStart(2, '0'); | |
| recordingTime.textContent = `${hours}:${minutes}:${seconds}`; | |
| } | |
| // Start recording | |
| recordBtn.addEventListener('click', () => { | |
| if (!isRecording) { | |
| // Start recording | |
| isRecording = true; | |
| recordingStartTime = new Date(); | |
| recordBtn.innerHTML = '<i class="fas fa-microphone text-2xl pulse-animation"></i>'; | |
| recordBtn.classList.add('ring-4', 'ring-indigo-300'); | |
| stopBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| pauseBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| recordingStatus.textContent = 'Recording in progress...'; | |
| recordingStatus.classList.add('text-indigo-600'); | |
| // Start timer | |
| timerInterval = setInterval(updateTimer, 1000); | |
| // Start waveform animation | |
| updateWaveform(); | |
| // Simulate transcription (in a real app, this would come from a speech-to-text API) | |
| simulateTranscription(); | |
| } | |
| }); | |
| // Stop recording | |
| stopBtn.addEventListener('click', () => { | |
| if (isRecording) { | |
| // Stop recording | |
| isRecording = false; | |
| clearInterval(timerInterval); | |
| recordBtn.innerHTML = '<i class="fas fa-microphone text-2xl"></i>'; | |
| recordBtn.classList.remove('ring-4', 'ring-indigo-300', 'pulse-animation'); | |
| stopBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
| pauseBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
| recordingStatus.textContent = 'Recording saved'; | |
| recordingStatus.classList.remove('text-indigo-600'); | |
| recordingStatus.classList.add('text-green-600'); | |
| // Reset waveform | |
| const bars = document.querySelectorAll('.waveform-bar'); | |
| bars.forEach(bar => { | |
| bar.style.height = '5px'; | |
| }); | |
| } | |
| }); | |
| // Pause/resume recording | |
| pauseBtn.addEventListener('click', () => { | |
| if (isRecording) { | |
| if (isPaused) { | |
| // Resume recording | |
| isPaused = false; | |
| pauseBtn.innerHTML = '<i class="fas fa-pause text-2xl"></i>'; | |
| pauseBtn.classList.remove('bg-gray-500'); | |
| pauseBtn.classList.add('bg-yellow-500'); | |
| recordingStatus.textContent = 'Recording in progress...'; | |
| recordingStatus.classList.add('text-indigo-600'); | |
| recordingStartTime = new Date(new Date() - (new Date() - recordingStartTime)); | |
| timerInterval = setInterval(updateTimer, 1000); | |
| updateWaveform(); | |
| } else { | |
| // Pause recording | |
| isPaused = true; | |
| clearInterval(timerInterval); | |
| pauseBtn.innerHTML = '<i class="fas fa-play text-2xl"></i>'; | |
| pauseBtn.classList.remove('bg-yellow-500'); | |
| pauseBtn.classList.add('bg-gray-500'); | |
| recordingStatus.textContent = 'Recording paused'; | |
| recordingStatus.classList.remove('text-indigo-600'); | |
| recordingStatus.classList.add('text-gray-600'); | |
| } | |
| } | |
| }); | |
| // Simulate transcription (in a real app, this would use Web Speech API or a service like Google Speech-to-Text) | |
| function simulateTranscription() { | |
| if (!isRecording || isPaused) return; | |
| // Sample meeting phrases | |
| const phrases = [ | |
| "Let's start with the project updates.", | |
| "The design phase is 80% complete.", | |
| "We need to address the budget concerns.", | |
| "The deadline has been moved to next Friday.", | |
| "Action item: John will follow up with the client.", | |
| "Sarah mentioned the API integration is delayed.", | |
| "Decision: We'll proceed with option B.", | |
| "Mike raised concerns about the timeline.", | |
| "Next steps: Finalize the requirements document.", | |
| "The team agreed on the new feature priorities." | |
| ]; | |
| // Randomly select a phrase | |
| const phrase = phrases[Math.floor(Math.random() * phrases.length)]; | |
| // Sometimes detect a new speaker (randomly) | |
| if (Math.random() > 0.7) { | |
| speakers++; | |
| speakerCount.textContent = `${speakers} speakers`; | |
| transcript += `\n\nSpeaker ${speakers}: ${phrase}`; | |
| } else { | |
| transcript += ` ${phrase}`; | |
| } | |
| // Update UI | |
| transcriptText.textContent = transcript; | |
| wordCount.textContent = `${transcript.split(/\s+/).length} words`; | |
| // Continue simulating until recording stops | |
| if (isRecording && !isPaused) { | |
| setTimeout(simulateTranscription, Math.random() * 3000 + 1000); | |
| } | |
| } | |
| // Copy transcript to clipboard | |
| copyTranscript.addEventListener('click', () => { | |
| navigator.clipboard.writeText(transcript).then(() => { | |
| const originalText = copyTranscript.innerHTML; | |
| copyTranscript.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!'; | |
| setTimeout(() => { | |
| copyTranscript.innerHTML = originalText; | |
| }, 2000); | |
| }); | |
| }); | |
| // Generate meeting minutes | |
| generateMinutes.addEventListener('click', () => { | |
| if (!transcript.trim()) { | |
| minutesContent.innerHTML = '<p class="text-red-500">No transcription available to generate minutes.</p>'; | |
| return; | |
| } | |
| // Simulate AI processing | |
| minutesContent.innerHTML = '<div class="flex items-center justify-center py-4"><i class="fas fa-spinner fa-spin text-indigo-600 text-2xl mr-2"></i><span>Generating minutes...</span></div>'; | |
| // Simulate API call delay | |
| setTimeout(() => { | |
| // This would be replaced with actual AI processing in a real app | |
| const summary = ` | |
| <h3 class="font-bold text-lg mb-2">Meeting Summary</h3> | |
| <ul class="list-disc pl-5 mb-4 space-y-1"> | |
| <li>Project updates were shared with the team</li> | |
| <li>Design phase is nearing completion</li> | |
| <li>Budget concerns need to be addressed</li> | |
| <li>Deadline adjusted to next Friday</li> | |
| </ul> | |
| <h3 class="font-bold text-lg mb-2">Key Decisions</h3> | |
| <ul class="list-disc pl-5 mb-4 space-y-1"> | |
| <li>Proceeding with option B for the new feature</li> | |
| <li>Revised timeline approved by the team</li> | |
| </ul> | |
| <h3 class="font-bold text-lg mb-2">Action Items</h3> | |
| <ol class="list-decimal pl-5 space-y-1"> | |
| <li><strong>John:</strong> Follow up with client about requirements</li> | |
| <li><strong>Sarah:</strong> Resolve API integration issues</li> | |
| <li><strong>Mike:</strong> Update project timeline document</li> | |
| </ol> | |
| `; | |
| minutesContent.innerHTML = summary; | |
| }, 2000); | |
| }); | |
| // Save minutes | |
| saveMinutes.addEventListener('click', () => { | |
| const title = document.getElementById('meeting-title').value || 'Untitled Meeting'; | |
| const attendees = document.getElementById('attendees').value || 'Not specified'; | |
| // In a real app, this would save to a database or local storage | |
| alert(`Minutes saved for "${title}" with attendees: ${attendees}`); | |
| }); | |
| // Share minutes | |
| shareMinutes.addEventListener('click', () => { | |
| // In a real app, this would use the Web Share API or other sharing methods | |
| alert('Sharing functionality would be implemented here'); | |
| }); | |
| </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=naresh10/myexperiments" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |