matsuap commited on
Commit
3ee4e09
·
verified ·
1 Parent(s): c9dcf89

Update src/web/live_transcription.html

Browse files
Files changed (1) hide show
  1. src/web/live_transcription.html +268 -268
src/web/live_transcription.html CHANGED
@@ -1,269 +1,269 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8"/>
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
- <title>Audio Transcription</title>
7
- <style>
8
- body {
9
- font-family: 'Inter', sans-serif;
10
- margin: 20px;
11
- text-align: center;
12
- }
13
- #recordButton {
14
- width: 80px;
15
- height: 80px;
16
- font-size: 36px;
17
- border: none;
18
- border-radius: 50%;
19
- background-color: white;
20
- cursor: pointer;
21
- box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2);
22
- transition: background-color 0.3s ease, transform 0.2s ease;
23
- }
24
- #recordButton.recording {
25
- background-color: #ff4d4d;
26
- color: white;
27
- }
28
- #recordButton:active {
29
- transform: scale(0.95);
30
- }
31
- #status {
32
- margin-top: 20px;
33
- font-size: 16px;
34
- color: #333;
35
- }
36
- .settings-container {
37
- display: flex;
38
- justify-content: center;
39
- align-items: center;
40
- gap: 15px;
41
- margin-top: 20px;
42
- }
43
- .settings {
44
- display: flex;
45
- flex-direction: column;
46
- align-items: flex-start;
47
- gap: 5px;
48
- }
49
- #chunkSelector,
50
- #websocketInput {
51
- font-size: 16px;
52
- padding: 5px;
53
- border-radius: 5px;
54
- border: 1px solid #ddd;
55
- background-color: #f9f9f9;
56
- }
57
- #websocketInput {
58
- width: 200px;
59
- }
60
- #chunkSelector:focus,
61
- #websocketInput:focus {
62
- outline: none;
63
- border-color: #007bff;
64
- }
65
- label {
66
- font-size: 14px;
67
- }
68
- /* Speaker-labeled transcript area */
69
- #linesTranscript {
70
- margin: 20px auto;
71
- max-width: 600px;
72
- text-align: left;
73
- font-size: 16px;
74
- }
75
- #linesTranscript p {
76
- margin: 5px 0;
77
- }
78
- #linesTranscript strong {
79
- color: #333;
80
- }
81
- /* Grey buffer styling */
82
- .buffer {
83
- color: rgb(180, 180, 180);
84
- font-style: italic;
85
- margin-left: 4px;
86
- }
87
- </style>
88
- </head>
89
- <body>
90
-
91
- <div class="settings-container">
92
- <button id="recordButton">🎙️</button>
93
- <div class="settings">
94
- <div>
95
- <label for="chunkSelector">Chunk size (ms):</label>
96
- <select id="chunkSelector">
97
- <option value="500" selected>500 ms</option>
98
- <option value="1000">1000 ms</option>
99
- <option value="2000">2000 ms</option>
100
- <option value="3000">3000 ms</option>
101
- <option value="4000">4000 ms</option>
102
- <option value="5000">5000 ms</option>
103
- </select>
104
- </div>
105
- <div>
106
- <label for="websocketInput">WebSocket URL:</label>
107
- <input id="websocketInput" type="text" value="ws://localhost:8000/asr" />
108
- </div>
109
- </div>
110
- </div>
111
-
112
- <p id="status"></p>
113
-
114
- <!-- Speaker-labeled transcript -->
115
- <div id="linesTranscript"></div>
116
-
117
- <script>
118
- let isRecording = false;
119
- let websocket = null;
120
- let recorder = null;
121
- let chunkDuration = 500;
122
- let websocketUrl = "ws://localhost:8000/asr";
123
- let userClosing = false;
124
-
125
- const statusText = document.getElementById("status");
126
- const recordButton = document.getElementById("recordButton");
127
- const chunkSelector = document.getElementById("chunkSelector");
128
- const websocketInput = document.getElementById("websocketInput");
129
- const linesTranscriptDiv = document.getElementById("linesTranscript");
130
-
131
- chunkSelector.addEventListener("change", () => {
132
- chunkDuration = parseInt(chunkSelector.value);
133
- });
134
-
135
- websocketInput.addEventListener("change", () => {
136
- const urlValue = websocketInput.value.trim();
137
- if (!urlValue.startsWith("ws://") && !urlValue.startsWith("wss://")) {
138
- statusText.textContent = "Invalid WebSocket URL (must start with ws:// or wss://)";
139
- return;
140
- }
141
- websocketUrl = urlValue;
142
- statusText.textContent = "WebSocket URL updated. Ready to connect.";
143
- });
144
-
145
- function setupWebSocket() {
146
- return new Promise((resolve, reject) => {
147
- try {
148
- websocket = new WebSocket(websocketUrl);
149
- } catch (error) {
150
- statusText.textContent = "Invalid WebSocket URL. Please check and try again.";
151
- reject(error);
152
- return;
153
- }
154
-
155
- websocket.onopen = () => {
156
- statusText.textContent = "Connected to server.";
157
- resolve();
158
- };
159
-
160
- websocket.onclose = () => {
161
- if (userClosing) {
162
- statusText.textContent = "WebSocket closed by user.";
163
- } else {
164
- statusText.textContent =
165
- "Disconnected from the WebSocket server. (Check logs if model is loading.)";
166
- }
167
- userClosing = false;
168
- };
169
-
170
- websocket.onerror = () => {
171
- statusText.textContent = "Error connecting to WebSocket.";
172
- reject(new Error("Error connecting to WebSocket"));
173
- };
174
-
175
- // Handle messages from server
176
- websocket.onmessage = (event) => {
177
- const data = JSON.parse(event.data);
178
- /*
179
- The server might send:
180
- {
181
- "lines": [
182
- {"speaker": 0, "text": "Hello."},
183
- {"speaker": 1, "text": "Bonjour."},
184
- ...
185
- ],
186
- "buffer": "..."
187
- }
188
- */
189
- const { lines = [], buffer = "" } = data;
190
- renderLinesWithBuffer(lines, buffer);
191
- };
192
- });
193
- }
194
-
195
- function renderLinesWithBuffer(lines, buffer) {
196
- // Clears if no lines
197
- if (!Array.isArray(lines) || lines.length === 0) {
198
- linesTranscriptDiv.innerHTML = "";
199
- return;
200
- }
201
- // Build the HTML
202
- // The buffer is appended to the last line if it's non-empty
203
- const linesHtml = lines.map((item, idx) => {
204
- let textContent = item.text;
205
- if (idx === lines.length - 1 && buffer) {
206
- textContent += `<span class="buffer">${buffer}</span>`;
207
- }
208
- return `<p><strong>Speaker ${item.speaker}:</strong> ${textContent}</p>`;
209
- }).join("");
210
-
211
- linesTranscriptDiv.innerHTML = linesHtml;
212
- }
213
-
214
- async function startRecording() {
215
- try {
216
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
217
- recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
218
- recorder.ondataavailable = (e) => {
219
- if (websocket && websocket.readyState === WebSocket.OPEN) {
220
- websocket.send(e.data);
221
- }
222
- };
223
- recorder.start(chunkDuration);
224
- isRecording = true;
225
- updateUI();
226
- } catch (err) {
227
- statusText.textContent = "Error accessing microphone. Please allow microphone access.";
228
- }
229
- }
230
-
231
- function stopRecording() {
232
- userClosing = true;
233
- if (recorder) {
234
- recorder.stop();
235
- recorder = null;
236
- }
237
- isRecording = false;
238
-
239
- if (websocket) {
240
- websocket.close();
241
- websocket = null;
242
- }
243
-
244
- updateUI();
245
- }
246
-
247
- async function toggleRecording() {
248
- if (!isRecording) {
249
- linesTranscriptDiv.innerHTML = "";
250
- try {
251
- await setupWebSocket();
252
- await startRecording();
253
- } catch (err) {
254
- statusText.textContent = "Could not connect to WebSocket or access mic. Aborted.";
255
- }
256
- } else {
257
- stopRecording();
258
- }
259
- }
260
-
261
- function updateUI() {
262
- recordButton.classList.toggle("recording", isRecording);
263
- statusText.textContent = isRecording ? "Recording..." : "Click to start transcription";
264
- }
265
-
266
- recordButton.addEventListener("click", toggleRecording);
267
- </script>
268
- </body>
269
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Audio Transcription</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Inter', sans-serif;
10
+ margin: 20px;
11
+ text-align: center;
12
+ }
13
+ #recordButton {
14
+ width: 80px;
15
+ height: 80px;
16
+ font-size: 36px;
17
+ border: none;
18
+ border-radius: 50%;
19
+ background-color: white;
20
+ cursor: pointer;
21
+ box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2);
22
+ transition: background-color 0.3s ease, transform 0.2s ease;
23
+ }
24
+ #recordButton.recording {
25
+ background-color: #ff4d4d;
26
+ color: white;
27
+ }
28
+ #recordButton:active {
29
+ transform: scale(0.95);
30
+ }
31
+ #status {
32
+ margin-top: 20px;
33
+ font-size: 16px;
34
+ color: #333;
35
+ }
36
+ .settings-container {
37
+ display: flex;
38
+ justify-content: center;
39
+ align-items: center;
40
+ gap: 15px;
41
+ margin-top: 20px;
42
+ }
43
+ .settings {
44
+ display: flex;
45
+ flex-direction: column;
46
+ align-items: flex-start;
47
+ gap: 5px;
48
+ }
49
+ #chunkSelector,
50
+ #websocketInput {
51
+ font-size: 16px;
52
+ padding: 5px;
53
+ border-radius: 5px;
54
+ border: 1px solid #ddd;
55
+ background-color: #f9f9f9;
56
+ }
57
+ #websocketInput {
58
+ width: 200px;
59
+ }
60
+ #chunkSelector:focus,
61
+ #websocketInput:focus {
62
+ outline: none;
63
+ border-color: #007bff;
64
+ }
65
+ label {
66
+ font-size: 14px;
67
+ }
68
+ /* Speaker-labeled transcript area */
69
+ #linesTranscript {
70
+ margin: 20px auto;
71
+ max-width: 600px;
72
+ text-align: left;
73
+ font-size: 16px;
74
+ }
75
+ #linesTranscript p {
76
+ margin: 5px 0;
77
+ }
78
+ #linesTranscript strong {
79
+ color: #333;
80
+ }
81
+ /* Grey buffer styling */
82
+ .buffer {
83
+ color: rgb(180, 180, 180);
84
+ font-style: italic;
85
+ margin-left: 4px;
86
+ }
87
+ </style>
88
+ </head>
89
+ <body>
90
+
91
+ <div class="settings-container">
92
+ <button id="recordButton">🎙️</button>
93
+ <div class="settings">
94
+ <div>
95
+ <label for="chunkSelector">Chunk size (ms):</label>
96
+ <select id="chunkSelector">
97
+ <option value="500" selected>500 ms</option>
98
+ <option value="1000">1000 ms</option>
99
+ <option value="2000">2000 ms</option>
100
+ <option value="3000">3000 ms</option>
101
+ <option value="4000">4000 ms</option>
102
+ <option value="5000">5000 ms</option>
103
+ </select>
104
+ </div>
105
+ <div>
106
+ <label for="websocketInput">WebSocket URL:</label>
107
+ <input id="websocketInput" type="text" value="wss://atpeak-realtime-stt-translation.hf.space/asr" />
108
+ </div>
109
+ </div>
110
+ </div>
111
+
112
+ <p id="status"></p>
113
+
114
+ <!-- Speaker-labeled transcript -->
115
+ <div id="linesTranscript"></div>
116
+
117
+ <script>
118
+ let isRecording = false;
119
+ let websocket = null;
120
+ let recorder = null;
121
+ let chunkDuration = 500;
122
+ let websocketUrl = "wss://atpeak-realtime-stt-translation.hf.space/asr";
123
+ let userClosing = false;
124
+
125
+ const statusText = document.getElementById("status");
126
+ const recordButton = document.getElementById("recordButton");
127
+ const chunkSelector = document.getElementById("chunkSelector");
128
+ const websocketInput = document.getElementById("websocketInput");
129
+ const linesTranscriptDiv = document.getElementById("linesTranscript");
130
+
131
+ chunkSelector.addEventListener("change", () => {
132
+ chunkDuration = parseInt(chunkSelector.value);
133
+ });
134
+
135
+ websocketInput.addEventListener("change", () => {
136
+ const urlValue = websocketInput.value.trim();
137
+ if (!urlValue.startsWith("ws://") && !urlValue.startsWith("wss://")) {
138
+ statusText.textContent = "Invalid WebSocket URL (must start with ws:// or wss://)";
139
+ return;
140
+ }
141
+ websocketUrl = urlValue;
142
+ statusText.textContent = "WebSocket URL updated. Ready to connect.";
143
+ });
144
+
145
+ function setupWebSocket() {
146
+ return new Promise((resolve, reject) => {
147
+ try {
148
+ websocket = new WebSocket(websocketUrl);
149
+ } catch (error) {
150
+ statusText.textContent = "Invalid WebSocket URL. Please check and try again.";
151
+ reject(error);
152
+ return;
153
+ }
154
+
155
+ websocket.onopen = () => {
156
+ statusText.textContent = "Connected to server.";
157
+ resolve();
158
+ };
159
+
160
+ websocket.onclose = () => {
161
+ if (userClosing) {
162
+ statusText.textContent = "WebSocket closed by user.";
163
+ } else {
164
+ statusText.textContent =
165
+ "Disconnected from the WebSocket server. (Check logs if model is loading.)";
166
+ }
167
+ userClosing = false;
168
+ };
169
+
170
+ websocket.onerror = () => {
171
+ statusText.textContent = "Error connecting to WebSocket.";
172
+ reject(new Error("Error connecting to WebSocket"));
173
+ };
174
+
175
+ // Handle messages from server
176
+ websocket.onmessage = (event) => {
177
+ const data = JSON.parse(event.data);
178
+ /*
179
+ The server might send:
180
+ {
181
+ "lines": [
182
+ {"speaker": 0, "text": "Hello."},
183
+ {"speaker": 1, "text": "Bonjour."},
184
+ ...
185
+ ],
186
+ "buffer": "..."
187
+ }
188
+ */
189
+ const { lines = [], buffer = "" } = data;
190
+ renderLinesWithBuffer(lines, buffer);
191
+ };
192
+ });
193
+ }
194
+
195
+ function renderLinesWithBuffer(lines, buffer) {
196
+ // Clears if no lines
197
+ if (!Array.isArray(lines) || lines.length === 0) {
198
+ linesTranscriptDiv.innerHTML = "";
199
+ return;
200
+ }
201
+ // Build the HTML
202
+ // The buffer is appended to the last line if it's non-empty
203
+ const linesHtml = lines.map((item, idx) => {
204
+ let textContent = item.text;
205
+ if (idx === lines.length - 1 && buffer) {
206
+ textContent += `<span class="buffer">${buffer}</span>`;
207
+ }
208
+ return `<p><strong>Speaker ${item.speaker}:</strong> ${textContent}</p>`;
209
+ }).join("");
210
+
211
+ linesTranscriptDiv.innerHTML = linesHtml;
212
+ }
213
+
214
+ async function startRecording() {
215
+ try {
216
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
217
+ recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
218
+ recorder.ondataavailable = (e) => {
219
+ if (websocket && websocket.readyState === WebSocket.OPEN) {
220
+ websocket.send(e.data);
221
+ }
222
+ };
223
+ recorder.start(chunkDuration);
224
+ isRecording = true;
225
+ updateUI();
226
+ } catch (err) {
227
+ statusText.textContent = "Error accessing microphone. Please allow microphone access.";
228
+ }
229
+ }
230
+
231
+ function stopRecording() {
232
+ userClosing = true;
233
+ if (recorder) {
234
+ recorder.stop();
235
+ recorder = null;
236
+ }
237
+ isRecording = false;
238
+
239
+ if (websocket) {
240
+ websocket.close();
241
+ websocket = null;
242
+ }
243
+
244
+ updateUI();
245
+ }
246
+
247
+ async function toggleRecording() {
248
+ if (!isRecording) {
249
+ linesTranscriptDiv.innerHTML = "";
250
+ try {
251
+ await setupWebSocket();
252
+ await startRecording();
253
+ } catch (err) {
254
+ statusText.textContent = "Could not connect to WebSocket or access mic. Aborted.";
255
+ }
256
+ } else {
257
+ stopRecording();
258
+ }
259
+ }
260
+
261
+ function updateUI() {
262
+ recordButton.classList.toggle("recording", isRecording);
263
+ statusText.textContent = isRecording ? "Recording..." : "Click to start transcription";
264
+ }
265
+
266
+ recordButton.addEventListener("click", toggleRecording);
267
+ </script>
268
+ </body>
269
  </html>