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> |