vue0
V
<template> <div id="app"> <div class="container"> <h1>Real-Time Speech Translation</h1> <div class="controls"> <button @click="startListening" :disabled="isListening"> {{ isListening ? 'Listening...' : 'Start Listening' }} </button> <button @click="stopListening" :disabled="!isListening"> Stop Listening </button> <div class="file-upload"> <input type="file" id="audioFile" ref="fileInput" accept=".mp3,.wav,.ogg" @change="handleFileUpload"> <label for="audioFile">Upload Audio File</label> </div> </div> <div class="status"> <p v-if="statusMessage" :class="statusClass">{{ statusMessage }}</p> <p v-if="errorMessage" class="error">{{ errorMessage }}</p> </div> <div class="transcription-container"> <div class="transcription-box"> <h2>Original (English)</h2> <div class="transcription-content"> <p v-for="(line, index) in originalText" :key="'original-'+index">{{ line }}</p> <p v-if="isProcessing" class="processing-indicator">Processing...</p> </div> </div> <div class="transcription-box"> <h2>Translation (Spanish)</h2> <div class="transcription-content"> <p v-for="(line, index) in translatedText" :key="'translated-'+index">{{ line }}</p> <p v-if="isProcessing" class="processing-indicator">Processing...</p> </div> </div> </div> <div class="metrics-panel" v-if="showMetrics"> <h3>Performance Metrics</h3> <div class="metrics-grid"> <div class="metric-card"> <div class="metric-title">Transcription Time</div> <div class="metric-value">{{ latestMetrics.transcriptionTime }}s</div> <div class="metric-subtitle">Speech-to-Text</div> </div> <div class="metric-card"> <div class="metric-title">Translation Time</div> <div class="metric-value">{{ latestMetrics.translationTime }}s</div> <div class="metric-subtitle">Text-to-Text</div> </div> <div class="metric-card"> <div class="metric-title">Total Processing</div> <div class="metric-value">{{ latestMetrics.totalProcessingTime }}s</div> <div class="metric-subtitle">End-to-End</div> </div> <div class="metric-card"> <div class="metric-title">Average Total</div> <div class="metric-value">{{ averageMetrics.totalProcessingTime }}s</div> <div class="metric-subtitle">Running Average</div> </div> </div> <button @click="toggleMetrics" class="toggle-button">Hide Metrics</button> </div> <button v-else @click="toggleMetrics" class="toggle-button">Show Metrics</button> <div class="debug" v-if="debugMode"> <h3>Debug Information</h3> <pre>Polling Status: {{ pollingActive ? 'Active' : 'Inactive' }}</pre> <pre>Last Poll: {{ lastPollTime }}</pre> <pre>Total Transcriptions: {{ totalTranscriptions }}</pre> <pre>Metrics History: {{ metricsHistory.length }} entries</pre> <button @click="toggleDebug" class="toggle-button">Hide Debug</button> </div> <button v-else @click="toggleDebug" class="toggle-button">Show Debug</button> </div> </div> </template> <script> export default { name: 'App', data() { return { isListening: false, isProcessing: false, pollingActive: false, pollingInterval: null, originalText: [], translatedText: [], statusMessage: '', errorMessage: '', debugMode: true, showMetrics: true, lastPollTime: 'Never', lastSeenIndex: 0, totalTranscriptions: 0, baseUrl: 'http://localhost:5001', metricsHistory: [], latestMetrics: { transcriptionTime: 0, translationTime: 0, totalProcessingTime: 0 }, averageMetrics: { transcriptionTime: 0, translationTime: 0, totalProcessingTime: 0 } } }, computed: { statusClass() { return { 'status-info': !this.errorMessage, 'status-error': this.errorMessage } } }, created() { // Nothing to setup initially }, beforeDestroy() { this.stopPolling(); }, methods: { startPolling() { if (this.pollingInterval) { clearInterval(this.pollingInterval); } this.pollingActive = true; this.pollingInterval = setInterval(() => { this.pollTranscriptions(); }, 1000); // Poll every second }, stopPolling() { if (this.pollingInterval) { clearInterval(this.pollingInterval); this.pollingInterval = null; } this.pollingActive = false; }, async pollTranscriptions() { try { const response = await fetch(`${this.baseUrl}/api/poll_transcriptions?last_seen=${this.lastSeenIndex}`); if (!response.ok) { throw new Error('Failed to poll transcriptions'); } const data = await response.json(); this.lastPollTime = new Date().toLocaleTimeString(); this.totalTranscriptions = data.total_count; // Process new transcriptions for (const item of data.transcriptions) { if (item.error) { this.handleError(item.error); } else { this.handleTranscriptionUpdate(item); // Process metrics if available if (item.metrics) { this.processMetrics(item.metrics); } } } // Update last seen index this.lastSeenIndex = data.total_count; } catch (error) { console.error('Polling error:', error); } }, processMetrics(metrics) { // Store the latest metrics this.latestMetrics = { transcriptionTime: metrics.transcription_time, translationTime: metrics.translation_time, totalProcessingTime: metrics.total_processing_time }; // Add to history this.metricsHistory.push(this.latestMetrics); // Calculate running averages if (this.metricsHistory.length > 0) { const totals = this.metricsHistory.reduce((acc, curr) => { return { transcriptionTime: acc.transcriptionTime + curr.transcriptionTime, translationTime: acc.translationTime + curr.translationTime, totalProcessingTime: acc.totalProcessingTime + curr.totalProcessingTime }; }, { transcriptionTime: 0, translationTime: 0, totalProcessingTime: 0 }); const count = this.metricsHistory.length; this.averageMetrics = { transcriptionTime: (totals.transcriptionTime / count).toFixed(3), translationTime: (totals.translationTime / count).toFixed(3), totalProcessingTime: (totals.totalProcessingTime / count).toFixed(3) }; } // Limit history size to prevent memory issues if (this.metricsHistory.length > 50) { this.metricsHistory = this.metricsHistory.slice(-50); } }, handleTranscriptionUpdate(payload) { this.isProcessing = false; this.originalText.push(payload.original); this.translatedText.push(payload.translation); // Auto-scroll to bottom this.$nextTick(() => { const containers = document.querySelectorAll('.transcription-content'); containers.forEach(container => { container.scrollTop = container.scrollHeight; }); }); }, handleError(error) { console.error('Error:', error); this.errorMessage = error; this.isProcessing = false; // Clear error after 5 seconds setTimeout(() => { this.errorMessage = ''; }, 5000); }, async startListening() { this.isListening = true; this.isProcessing = true; this.statusMessage = 'Starting microphone listening...'; this.lastSeenIndex = 0; // Reset the counter this.metricsHistory = []; // Reset metrics history try { const response = await fetch(`${this.baseUrl}/api/start_listening`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Failed to start listening'); } // Start polling for updates this.startPolling(); this.statusMessage = 'Listening to microphone...'; } catch (error) { this.handleError(error.message); this.isListening = false; this.isProcessing = false; } }, async stopListening() { this.isListening = false; this.statusMessage = 'Stopping microphone listening...'; try { const response = await fetch(`${this.baseUrl}/api/stop_listening`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) throw new Error('Failed to stop listening'); // Continue polling for any remaining transcriptions setTimeout(() => { this.stopPolling(); }, 3000); // Poll for 3 more seconds to catch final transcriptions this.statusMessage = 'Microphone stopped'; } catch (error) { this.handleError(error.message); } }, async handleFileUpload(event) { const file = event.target.files[0]; if (!file) return; this.isProcessing = true; this.statusMessage = `Processing file: ${file.name}`; this.lastSeenIndex = 0; // Reset the counter this.metricsHistory = []; // Reset metrics history const formData = new FormData(); formData.append('file', file); try { const response = await fetch(`${this.baseUrl}/api/upload`, { method: 'POST', body: formData }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Failed to upload file'); } const data = await response.json(); // Start polling for file processing updates this.startPolling(); this.statusMessage = data.status === 'processing' ? `Processing file: ${file.name}` : 'File uploaded successfully'; } catch (error) { this.handleError(error.message); } finally { // Reset file input this.$refs.fileInput.value = ''; } }, toggleMetrics() { this.showMetrics = !this.showMetrics; }, toggleDebug() { this.debugMode = !this.debugMode; } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; margin: 0; padding: 20px; min-height: 100vh; background-color: #f5f7fa; } .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } h1 { text-align: center; color: #2c3e50; margin-bottom: 30px; } .controls { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px; justify-content: center; } button { padding: 10px 15px; background-color: #42b983; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } button:hover { background-color: #3aa876; } button:disabled { background-color: #cccccc; cursor: not-allowed; } .file-upload { position: relative; overflow: hidden; display: inline-block; } .file-upload input[type="file"] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; } .file-upload label { display: inline-block; padding: 10px 15px; background-color: #4285f4; color: white; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } .file-upload label:hover { background-color: #3367d6; } .status { margin: 15px 0; text-align: center; } .status-info { color: #42b983; } .status-error { color: #ff5252; } .error { color: #ff5252; text-align: center; } .transcription-container { display: flex; gap: 20px; margin-top: 20px; } .transcription-box { flex: 1; border: 1px solid #e0e0e0; border-radius: 8px; padding: 15px; background-color: #f9f9f9; } .transcription-box h2 { margin-top: 0; color: #2c3e50; border-bottom: 1px solid #e0e0e0; padding-bottom: 10px; } .transcription-content { height: 300px; overflow-y: auto; padding: 10px; background-color: white; border-radius: 4px; } .transcription-content p { margin: 5px 0; line-height: 1.5; } .processing-indicator { color: #888; font-style: italic; } .metrics-panel { margin-top: 30px; padding: 15px; background-color: #f9f9f9; border-radius: 8px; border: 1px solid #e0e0e0; } .metrics-panel h3 { margin-top: 0; color: #2c3e50; text-align: center; margin-bottom: 15px; } .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 15px; } .metric-card { background-color: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); text-align: center; } .metric-title { font-weight: bold; color: #4285f4; margin-bottom: 5px; } .metric-value { font-size: 1.8em; font-weight: bold; color: #2c3e50; margin: 5px 0; } .metric-subtitle { font-size: 0.9em; color: #888; } .toggle-button { display: block; margin: 0 auto; background-color: #757575; } .toggle-button:hover { background-color: #616161; } .debug { margin-top: 30px; padding: 15px; background-color: #f0f0f0; border-radius: 8px; font-family: monospace; } .debug h3 { margin-top: 0; } @media (max-width: 768px) { .transcription-container { flex-direction: column; } .metrics-grid { grid-template-columns: 1fr; } } </style> make the ui way better. also add a video component with:Subtitle Overlay & UI Integration: Eventyay Video Integration: Develop a module that overlays interpreted subtitles onto the live video stream from the eventyay video platform. Customization: Provide configurable options (e.g., font size, color, position, language selection, and toggle controls) to optimize subtitle display for diverse event settings.