File size: 15,880 Bytes
4207399
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Audio Transcription Studio</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
    <style>

        body {

            font-family: 'Inter', sans-serif;

            background-color: #1a1a2e; /* Dark purple background */

            color: #ffffff;

        }



        .container-bg {

            background-color: #2c2c44; /* Slightly lighter purple for containers */

        }

        

        .panel-bg {

            background-color: #22223b; /* Darker panel background */

        }



        .input-field {

            background-color: #3b3b55;

            border: 1px solid #4a4a6b;

            color: #e0e0e0;

        }

        

        .button-glow {

            box-shadow: 0 0 10px 2px #6a1b9a;

        }

        

        .glow-text {

            text-shadow: 0 0 8px #d1c4e9;

        }

    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-8">
    <div class="w-full max-w-6xl">
        <!-- Main Header -->
        <header class="text-center mb-10">
            <h1 class="text-5xl font-extrabold text-[#d1c4e9] glow-text mb-2">Audio Transcription Studio</h1>
            <p class="text-lg text-gray-400">Record high-quality audio and get real-time AI-powered transcriptions with speaker detection.</p>
        </header>

        <!-- Main Content Grid -->
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
            <!-- Left Panel: Live Transcription -->
            <div class="lg:col-span-2 panel-bg p-8 rounded-2xl shadow-xl">
                <h2 class="text-2xl font-bold mb-4 text-[#d1c4e9]"><i class="fas fa-file-alt mr-2"></i> Live Transcription</h2>
                
                <!-- Recording Status & Button -->
                <div id="recording-status-area" class="flex flex-col items-center justify-center p-6 mb-8">
                    <div id="status-spinner" class="relative w-32 h-32 hidden">
                        <div class="absolute inset-0 border-4 border-purple-500 rounded-full animate-ping"></div>
                        <div class="absolute inset-4 border-4 border-purple-400 rounded-full animate-ping delay-200"></div>
                        <div class="absolute inset-8 border-4 border-purple-300 rounded-full animate-ping delay-400"></div>
                        <div class="flex items-center justify-center h-full w-full">
                             <i class="fas fa-microphone text-4xl text-white"></i>
                        </div>
                    </div>
                    <div id="status-icon" class="relative w-32 h-32 flex items-center justify-center bg-purple-600 rounded-full">
                        <i class="fas fa-microphone text-4xl text-white"></i>
                    </div>
                    <p id="status-text" class="mt-4 text-green-400 font-semibold text-lg">Ready to record</p>
                    <div id="start-stop-buttons" class="mt-4">
                        <button id="start-btn" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-6 rounded-full transition duration-300 button-glow disabled:opacity-50 disabled:cursor-not-allowed">
                            Start Recording
                        </button>
                        <button id="stop-btn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-6 rounded-full transition duration-300 disabled:opacity-50 disabled:cursor-not-allowed hidden">
                            Stop Recording
                        </button>
                    </div>
                </div>

                <!-- Live Transcription Display -->
                <div id="live-transcription" class="bg-[#1b1b2a] p-6 rounded-lg h-96 overflow-y-auto border border-[#3b3b55]">
                    <p class="text-gray-400 text-center text-lg mt-12">Start recording to see live transcription</p>
                </div>
            </div>

            <!-- Right Panel: Recording Settings & Files -->
            <div class="lg:col-span-1 space-y-8">
                <!-- Recording Settings Panel -->
                <div class="panel-bg p-8 rounded-2xl shadow-xl">
                    <h2 class="text-2xl font-bold mb-4 text-[#d1c4e9]"><i class="fas fa-cogs mr-2"></i> Recording Settings</h2>
                    <div class="space-y-6">
                        <!-- Microphone Device -->
                        <div>
                            <label for="mic-select" class="block text-sm font-medium text-gray-400 mb-2"><i class="fas fa-microphone mr-2"></i>Microphone Device</label>
                            <select id="mic-select" class="block w-full rounded-md shadow-sm p-3 input-field focus:ring-purple-500 focus:border-purple-500">
                                <option value="">Loading devices...</option>
                            </select>
                        </div>
                        
                        <!-- System Audio -->
                        <div>
                            <label for="sys-select" class="block text-sm font-medium text-gray-400 mb-2"><i class="fas fa-desktop mr-2"></i>System Audio (Optional)</label>
                            <select id="sys-select" class="block w-full rounded-md shadow-sm p-3 input-field focus:ring-purple-500 focus:border-purple-500">
                                <option value="null">None</option>
                            </select>
                        </div>
                        
                        <!-- Chunk Length -->
                        <div>
                            <label for="chunk-secs-input" class="block text-sm font-medium text-gray-400 mb-2"><i class="fas fa-clock mr-2"></i>Chunk Length (seconds)</label>
                            <input type="number" id="chunk-secs-input" value="5" min="1" class="block w-full rounded-md shadow-sm p-3 input-field focus:ring-purple-500 focus:border-purple-500">
                        </div>
                        
                        <!-- Transcription Model -->
                        <div>
                            <label for="model-input" class="block text-sm font-medium text-gray-400 mb-2"><i class="fas fa-brain mr-2"></i>Transcription Model</label>
                            <select id="model-input" class="block w-full rounded-md shadow-sm p-3 input-field focus:ring-purple-500 focus:border-purple-500">
                                <option value="medium">Medium (Balanced)</option>
                                <option value="small">Small</option>
                                <option value="large">Large</option>
                            </select>
                        </div>
                        
                        <!-- Disable Transcription Toggle -->
                        <div class="flex items-center">
                            <input id="no-transcribe-checkbox" type="checkbox" class="h-5 w-5 text-purple-600 focus:ring-purple-500 rounded border-gray-600 bg-gray-700">
                            <label for="no-transcribe-checkbox" class="ml-2 block text-sm text-gray-300">Disable Transcription</label>
                        </div>
                    </div>
                </div>

                <!-- Recording Files Panel -->
                <div class="panel-bg p-8 rounded-2xl shadow-xl">
                    <h2 class="text-2xl font-bold mb-4 text-[#d1c4e9]"><i class="fas fa-folder-open mr-2"></i> Recording Files</h2>
                    <div id="final-files-list" class="space-y-2 text-gray-300">
                        <p class="text-gray-500">No files yet...</p>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>

        const micSelect = document.getElementById('mic-select');

        const sysSelect = document.getElementById('sys-select');

        const chunkSecsInput = document.getElementById('chunk-secs-input');

        const modelInput = document.getElementById('model-input');

        const noTranscribeCheckbox = document.getElementById('no-transcribe-checkbox');

        const startBtn = document.getElementById('start-btn');

        const stopBtn = document.getElementById('stop-btn');

        const statusText = document.getElementById('status-text');

        const liveTranscription = document.getElementById('live-transcription');

        const finalFilesList = document.getElementById('final-files-list');

        const statusIcon = document.getElementById('status-icon');

        const statusSpinner = document.getElementById('status-spinner');



        let statusPollingInterval;



        // Fetch available audio devices and populate the dropdowns

        async function fetchDevices() {

            try {

                const response = await fetch('/api/devices');

                const data = await response.json();

                

                const micOptions = data.devices.map(device => `<option value="${device.index}">${device.name}</option>`).join('');

                micSelect.innerHTML = micOptions;

                

                const sysOptions = `<option value="null">None</option>` + micOptions;

                sysSelect.innerHTML = sysOptions;



                if (data.devices.length > 0) {

                    micSelect.value = data.devices[0].index;

                }

            } catch (error) {

                console.error('Error fetching devices:', error);

                micSelect.innerHTML = `<option>Error loading devices</option>`;

                sysSelect.innerHTML = `<option>Error loading devices</option>`;

            }

        }



        // Fetch final files and display them

        async function fetchFinalFiles() {

            try {

                const response = await fetch('/api/final-files');

                const data = await response.json();

                if (data.files.length > 0) {

                    const filesHtml = data.files.map(file => `

                        <a href="${file.url}" class="flex items-center text-purple-400 hover:text-purple-300 transition-colors duration-200" target="_blank">

                            <i class="fas fa-file-waveform mr-2"></i><span>${file.name}</span>

                        </a>

                    `).join('');

                    finalFilesList.innerHTML = filesHtml;

                } else {

                    finalFilesList.innerHTML = `<p class="text-gray-500">No files yet...</p>`;

                }

            } catch (error) {

                console.error('Error fetching final files:', error);

                finalFilesList.innerHTML = `<p class="text-red-500">Error loading files.</p>`;

            }

        }



        // Poll the server for recording status and live segments

        function startStatusPolling() {

            statusPollingInterval = setInterval(async () => {

                try {

                    const response = await fetch('/api/recording-status');

                    const data = await response.json();



                    if (data.recording) {

                        statusText.textContent = 'Recording...';

                        statusText.classList.remove('text-green-400');

                        statusText.classList.add('text-purple-400');

                        statusIcon.classList.add('hidden');

                        statusSpinner.classList.remove('hidden');

                        

                        liveTranscription.innerHTML = '';

                        if (data.live_segments.length === 0) {

                            liveTranscription.innerHTML = `<p class="text-gray-400 text-center text-lg mt-12">Recording started. Waiting for transcription...</p>`;

                        } else {

                            data.live_segments.forEach(segment => {

                                const p = document.createElement('p');

                                p.className = 'text-gray-200 mb-1 leading-snug';

                                p.innerHTML = `<span class="font-semibold text-purple-300">${segment.speaker}:</span> ${segment.text}`;

                                liveTranscription.appendChild(p);

                            });

                            liveTranscription.scrollTop = liveTranscription.scrollHeight;

                        }

                        fetchFinalFiles();

                        

                    } else {

                        statusText.textContent = 'Ready to record';

                        statusText.classList.remove('text-purple-400');

                        statusText.classList.add('text-green-400');

                        statusIcon.classList.remove('hidden');

                        statusSpinner.classList.add('hidden');

                        clearInterval(statusPollingInterval);

                        startBtn.classList.remove('hidden');

                        stopBtn.classList.add('hidden');

                        fetchFinalFiles(); 

                    }

                } catch (error) {

                    console.error('Error polling status:', error);

                    clearInterval(statusPollingInterval);

                }

            }, 1000); 

        }



        // Start recording

        startBtn.addEventListener('click', async () => {

            const mic = micSelect.value;

            const sys = sysSelect.value === 'null' ? null : sysSelect.value;

            const chunk_secs = chunkSecsInput.value;

            const model = modelInput.value;

            const no_transcribe = noTranscribeCheckbox.checked;



            try {

                const response = await fetch('/api/start-recording', {

                    method: 'POST',

                    headers: { 'Content-Type': 'application/json' },

                    body: JSON.stringify({ mic, sys, chunk_secs, model, no_transcribe })

                });

                

                if (response.ok) {

                    startBtn.classList.add('hidden');

                    stopBtn.classList.remove('hidden');

                    liveTranscription.innerHTML = `<p class="text-gray-400 text-center text-lg mt-12">Starting recording...</p>`;

                    startStatusPolling();

                } else {

                    const error = await response.json();

                    alert(`Error: ${error.error}`);

                }

            } catch (error) {

                console.error('Failed to start recording:', error);

                alert('Failed to start recording. Check server connection.');

            }

        });



        // Stop recording

        stopBtn.addEventListener('click', async () => {

            try {

                const response = await fetch('/api/stop-recording', {

                    method: 'POST'

                });

                if (response.ok) {

                    // Status polling will handle UI updates after the server stops

                }

            } catch (error) {

                console.error('Failed to stop recording:', error);

            }

        });



        // Initial setup on page load

        window.onload = () => {

            fetchDevices();

            fetchFinalFiles();

        };



    </script>
</body>
</html>