Public Speaking Assistant

Public Speaking Assistant

Prepare Your Speech

Timer

00:00:00

Session Analysis

Your performance results will appear here after stopping the timer.

${sessionData.duration}

Pacing (WPM)

${sessionData.wpm} ${pacingFeedback}

Filler Words Detected:

${Object.entries(sessionData.fillerCounts).map(([word, count]) => ` ${word}: ${count} `).join('') || '

None detected. Great job!

'}
`; document.getElementById('pdfButtonContainer').classList.remove('hidden'); } function addLiveFeedback(message, type = 'info') { const container = document.getElementById('liveFeedbackContainer'); document.getElementById('liveFeedbackPlaceholder').classList.add('hidden'); const colors = { info: 'text-cyan-400', warning: 'text-yellow-400', alert: 'text-red-400' }; const timestamp = new Date(liveTimer.seconds * 1000).toISOString().substr(14, 5); const p = document.createElement('p'); p.className = `feedback-item ${colors[type]}`; p.innerHTML = `[${timestamp}] ${message}`; container.appendChild(p); container.scrollTop = container.scrollHeight; } // --- TIMER & CONTROLS --- function manageTimer(timerObj, action) { switch(action) { case 'start': timerObj.isPaused = false; timerObj.startBtn.disabled = true; timerObj.pauseBtn.disabled = false; timerObj.stopBtn.disabled = false; timerObj.interval = setInterval(() => { if (!timerObj.isPaused) { timerObj.seconds++; timerObj.display.textContent = new Date(timerObj.seconds * 1000).toISOString().substr(11, 8); if (timerObj === liveTimer) runLiveAssistantLogic(); } }, 1000); break; case 'pause': timerObj.isPaused = !timerObj.isPaused; timerObj.pauseBtn.textContent = timerObj.isPaused ? 'Resume' : 'Pause'; break; case 'stop': clearInterval(timerObj.interval); timerObj.isPaused = true; timerObj.startBtn.disabled = false; timerObj.pauseBtn.disabled = true; timerObj.stopBtn.disabled = true; timerObj.pauseBtn.textContent = 'Pause'; if (timerObj === timer) analyzeSession(); timerObj.seconds = 0; // Reset for next time break; } } function runLiveAssistantLogic() { // Simulate filler word detection if (Math.random() < 0.2 && fillerWords.length > 0) { // 20% chance each second const word = fillerWords[Math.floor(Math.random() * fillerWords.length)]; addLiveFeedback(`Filler word detected: "${word}"`, 'alert'); } // Simulate pacing feedback every 10 seconds if (liveTimer.seconds > 0 && liveTimer.seconds % 10 === 0) { const pacingRoll = Math.random(); if (pacingRoll < 0.33) addLiveFeedback('Pacing is a bit fast.', 'warning'); else if (pacingRoll < 0.66) addLiveFeedback('Pacing is a bit slow.', 'warning'); else addLiveFeedback('Pacing is good.', 'info'); } } function analyzeSession() { const speechText = document.getElementById('speechText').value; const wordCount = speechText.trim().split(/\s+/).filter(Boolean).length; const minutes = timer.seconds / 60; const wpm = minutes > 0 ? Math.round(wordCount / minutes) : 0; // Simulate finding filler words based on duration const fillerCounts = {}; if(fillerWords.length > 0) { const totalFillers = Math.floor(timer.seconds / 10); // 1 filler every 10 seconds on average for (let i = 0; i < totalFillers; i++) { const word = fillerWords[Math.floor(Math.random() * fillerWords.length)]; fillerCounts[word] = (fillerCounts[word] || 0) + 1; } } sessionData = { title: document.getElementById('speechTitle').value || 'Untitled Speech', duration: timer.display.textContent, wpm: wpm, fillerCounts: fillerCounts }; displayAnalysis(); } // --- EVENT HANDLERS & LOGIC --- window.changeTab = (tabName) => { currentTab = tabName; Object.values(tabElements).forEach(el => el.classList.add('hidden')); Object.values(tabButtons).forEach(btn => btn.classList.remove('active')); tabElements[tabName].classList.remove('hidden'); tabButtons[tabName].classList.add('active'); updateNavButtons(); }; window.navigateTabs = (direction) => { const currentIndex = tabs.indexOf(currentTab); const nextIndex = direction === 'next' ? (currentIndex + 1) : (currentIndex - 1); if (nextIndex >= 0 && nextIndex < tabs.length) changeTab(tabs[nextIndex]); }; const updateNavButtons = () => { const currentIndex = tabs.indexOf(currentTab); prevButton.classList.toggle('invisible', currentIndex === 0); nextButton.classList.toggle('invisible', currentIndex === tabs.length - 1); }; function setupEventListeners() { addWordForm.addEventListener('submit', (e) => { e.preventDefault(); const input = document.getElementById('newWord'); const newWord = input.value.trim().toLowerCase(); if (newWord && !fillerWords.includes(newWord)) { fillerWords.push(newWord); renderWordList(); input.value = ''; } }); timer.startBtn.addEventListener('click', () => manageTimer(timer, 'start')); timer.pauseBtn.addEventListener('click', () => manageTimer(timer, 'pause')); timer.stopBtn.addEventListener('click', () => manageTimer(timer, 'stop')); liveTimer.startBtn.addEventListener('click', () => manageTimer(liveTimer, 'start')); liveTimer.pauseBtn.addEventListener('click', () => manageTimer(liveTimer, 'pause')); liveTimer.stopBtn.addEventListener('click', () => manageTimer(liveTimer, 'stop')); } window.deleteWord = (wordToDelete) => { fillerWords = fillerWords.filter(w => w !== wordToDelete); renderWordList(); }; // --- PDF DOWNLOAD --- window.downloadPDF = async function() { if (!sessionData) return; const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'pt', 'a4'); const canvas = document.createElement('canvas'); canvas.width = 400; canvas.height = 200; if (Object.keys(sessionData.fillerCounts).length > 0) { new Chart(canvas.getContext('2d'), { type: 'bar', data: { labels: Object.keys(sessionData.fillerCounts), datasets: [{ label: 'Count', data: Object.values(sessionData.fillerCounts), backgroundColor: '#EF4444' }] }, options: { responsive: false, animation: false, plugins: { legend: { display: false } } } }); } await new Promise(resolve => setTimeout(resolve, 500)); const chartImage = canvas.toDataURL('image/png'); const pageWidth = doc.internal.pageSize.getWidth(); const pageHeight = doc.internal.pageSize.getHeight(); doc.setFillColor(41, 128, 185); doc.rect(0, 0, pageWidth, 60, 'F'); doc.setFontSize(20); doc.setTextColor(255); doc.setFont('helvetica', 'bold'); doc.text('Public Speaking Performance Report', 30, 38); doc.setFontSize(12); doc.setTextColor(50); doc.text(`Speech Title: ${sessionData.title}`, 30, 90); doc.text(`Practice Duration: ${sessionData.duration}`, 30, 105); doc.autoTable({ startY: 125, head: [['Metric', 'Result', 'Feedback']], body: [ ['Pacing', `${sessionData.wpm} WPM`, sessionData.wpm > 160 ? 'Too Fast' : (sessionData.wpm < 120 ? 'Too Slow' : 'Good Pace')], ['Filler Words', `${Object.values(sessionData.fillerCounts).reduce((a, b) => a + b, 0)} total`, 'Aim to reduce for clarity'] ], theme: 'grid', headStyles: { fillColor: [41, 128, 185] }, }); let finalY = doc.lastAutoTable.finalY; if (Object.keys(sessionData.fillerCounts).length > 0) { doc.setFontSize(14); doc.text('Filler Word Distribution', 30, finalY + 30); doc.addImage(chartImage, 'PNG', 30, finalY + 40, 400, 200); } const pageCount = doc.internal.getNumberOfPages(); for(let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(8); doc.setTextColor(150); doc.text(`Page ${i} of ${pageCount}`, pageWidth / 2, pageHeight - 20, { align: 'center' }); } doc.save('Speaking_Performance_Report.pdf'); }; // --- INITIALIZE --- initializeApp(); });
Scroll to Top