Update app.py via AI Editor
Browse files
app.py
CHANGED
@@ -7,18 +7,17 @@ import threading
|
|
7 |
import logging
|
8 |
import time
|
9 |
import base64
|
10 |
-
import json
|
11 |
from datetime import datetime
|
|
|
12 |
from flask import request
|
13 |
import dash
|
14 |
from dash import Dash, dcc, html, Input, Output, State, callback_context, no_update
|
15 |
import dash_bootstrap_components as dbc
|
16 |
-
import dash_daq as daq
|
17 |
import requests
|
18 |
import docx
|
19 |
|
20 |
AUDIO_CHUNK_SIZE_MB = 25
|
21 |
-
AUDIO_EXTS = ['.
|
22 |
TRANSCRIPT_FILENAME = 'transcript.docx'
|
23 |
MINUTES_FILENAME = 'minutes.docx'
|
24 |
RAW_AUDIO_FILENAME = 'meeting_audio.wav'
|
@@ -171,7 +170,108 @@ external_stylesheets = [dbc.themes.BOOTSTRAP, '/assets/custom.css']
|
|
171 |
app = Dash(__name__, external_stylesheets=external_stylesheets, suppress_callback_exceptions=True)
|
172 |
server = app.server
|
173 |
|
174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
audio_path = get_audio_path(user_dir)
|
176 |
transcript_path = get_transcript_path(user_dir)
|
177 |
minutes_path = get_minutes_path(user_dir)
|
@@ -186,22 +286,23 @@ def build_left_nav(sid, user_dir):
|
|
186 |
links.append(dbc.NavLink("Meeting Minutes", href="#", id="nav-minutes", n_clicks=0, className="mb-2"))
|
187 |
links.append(html.A("Download Minutes (docx)", href=f"/download/minutes/{sid}", target="_blank", id="dl-minutes", className="mb-2 d-block"))
|
188 |
links.append(html.Hr())
|
189 |
-
links.append(dbc.Button(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
return dbc.Card(
|
191 |
dbc.CardBody([
|
192 |
html.H4("Navigation/Downloads", className="card-title"),
|
193 |
html.Div(links, id="left-links"),
|
194 |
-
html.Hr(),
|
195 |
-
dbc.Button("Record Meeting", id="btn-record", color="primary", className="mb-2", n_clicks=0),
|
196 |
-
dcc.Upload(
|
197 |
-
id='upload-audio',
|
198 |
-
children=html.Div([
|
199 |
-
'Or drag and drop/upload meeting audio here'
|
200 |
-
]),
|
201 |
-
multiple=False,
|
202 |
-
accept="audio/*",
|
203 |
-
className='mb-2'
|
204 |
-
),
|
205 |
]),
|
206 |
className="mb-2"
|
207 |
)
|
@@ -210,12 +311,12 @@ def build_right_preview(content_type, content, sid, user_dir):
|
|
210 |
if content_type == "audio":
|
211 |
audio_path = get_audio_path(user_dir)
|
212 |
if not file_exists(audio_path):
|
213 |
-
return html.Div("No audio recorded
|
214 |
return html.Audio(src=f"/preview/audio/{sid}", controls=True, style={"width": "100%"})
|
215 |
elif content_type == "transcript":
|
216 |
transcript_path = get_transcript_path(user_dir)
|
217 |
if not file_exists(transcript_path):
|
218 |
-
return html.Div("No transcript found. Please record
|
219 |
with open(transcript_path, "rb") as f:
|
220 |
doc = docx.Document(f)
|
221 |
text = "\n".join([para.text for para in doc.paragraphs])
|
@@ -226,7 +327,7 @@ def build_right_preview(content_type, content, sid, user_dir):
|
|
226 |
elif content_type == "minutes":
|
227 |
minutes_path = get_minutes_path(user_dir)
|
228 |
if not file_exists(minutes_path):
|
229 |
-
return html.Div("No meeting minutes found. Please transcribe meeting first.")
|
230 |
with open(minutes_path, "rb") as f:
|
231 |
doc = docx.Document(f)
|
232 |
text = "\n".join([para.text for para in doc.paragraphs])
|
@@ -241,16 +342,8 @@ app.layout = dbc.Container([
|
|
241 |
dcc.Store(id="store-session", storage_type="session"),
|
242 |
dcc.Store(id="store-preview", data={"type": "none"}),
|
243 |
dcc.Store(id="store-transcript-status", data={"status": "idle"}),
|
|
|
244 |
dcc.Interval(id="interval-refresh", interval=60000, n_intervals=0),
|
245 |
-
# Hidden dcc.Upload to satisfy Dash callback registration
|
246 |
-
html.Div([
|
247 |
-
dcc.Upload(
|
248 |
-
id='upload-audio',
|
249 |
-
children=None,
|
250 |
-
style={"display": "none"}
|
251 |
-
)
|
252 |
-
], style={"display": "none"}),
|
253 |
-
# Hidden dummy components for Dash callback recognition
|
254 |
html.Div([
|
255 |
dbc.Button(id="btn-record", style={"display": "none"}),
|
256 |
dbc.Button(id="btn-clear-all", style={"display": "none"}),
|
@@ -292,20 +385,19 @@ app.layout = dbc.Container([
|
|
292 |
Output("right-preview", "children"),
|
293 |
Output("store-preview", "data"),
|
294 |
Input("interval-refresh", "n_intervals"),
|
295 |
-
Input("
|
296 |
-
Input("btn-record", "n_clicks"),
|
297 |
-
Input("store-transcript-status", "data"),
|
298 |
Input("nav-audio", "n_clicks"),
|
299 |
Input("nav-transcript", "n_clicks"),
|
300 |
Input("nav-minutes", "n_clicks"),
|
301 |
Input("btn-clear-all", "n_clicks"),
|
302 |
State("store-session", "data"),
|
303 |
State("store-preview", "data"),
|
|
|
304 |
prevent_initial_call=False
|
305 |
)
|
306 |
-
def main_controller(interval_refresh,
|
307 |
nav_audio, nav_transcript, nav_minutes, btn_clear_all,
|
308 |
-
session_data, store_preview_data):
|
309 |
|
310 |
ctx = callback_context
|
311 |
triggered = ctx.triggered
|
@@ -314,7 +406,8 @@ def main_controller(interval_refresh, upload_contents, btn_record, transcript_st
|
|
314 |
sid = session_manager.get_session_id()
|
315 |
user_dir = session_manager.get_user_dir(sid)
|
316 |
lock = session_manager.get_lock(sid)
|
317 |
-
|
|
|
318 |
right_preview = build_right_preview("none", None, sid, user_dir)
|
319 |
store_preview = {"type": "none"}
|
320 |
transcript_status = {"status": "idle"}
|
@@ -325,19 +418,18 @@ def main_controller(interval_refresh, upload_contents, btn_record, transcript_st
|
|
325 |
transcript_status = transcript_status_data if transcript_status_data else {"status": "idle"}
|
326 |
store_preview = store_preview_data if store_preview_data else {"type": "none"}
|
327 |
right_preview = build_right_preview(store_preview.get('type', 'none'), None, sid, user_dir)
|
|
|
328 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
329 |
|
330 |
-
elif trigger_id == "
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
transcript_status = {"status": "idle"}
|
340 |
-
return session_store, left_nav, transcript_status, right_preview, store_preview
|
341 |
|
342 |
elif trigger_id == "store-transcript-status":
|
343 |
if transcript_status_data and transcript_status_data.get("status") == "audio_uploaded":
|
@@ -356,16 +448,16 @@ def main_controller(interval_refresh, upload_contents, btn_record, transcript_st
|
|
356 |
minutes_path = get_minutes_path(user_dir)
|
357 |
write_docx(minutes_text, minutes_path)
|
358 |
logging.info(f"Transcription and minutes generated for session {sid}")
|
359 |
-
left_nav = build_left_nav(sid, user_dir)
|
360 |
transcript_status = {"status": "done"}
|
361 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
362 |
else:
|
363 |
-
left_nav = build_left_nav(sid, user_dir)
|
364 |
transcript_status = transcript_status_data if transcript_status_data else {"status": "idle"}
|
365 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
366 |
|
367 |
elif trigger_id in ["nav-audio", "nav-transcript", "nav-minutes"]:
|
368 |
-
left_nav = build_left_nav(sid, user_dir)
|
369 |
if trigger_id == "nav-audio":
|
370 |
right_preview = build_right_preview("audio", None, sid, user_dir)
|
371 |
store_preview = {"type": "audio"}
|
@@ -382,18 +474,31 @@ def main_controller(interval_refresh, upload_contents, btn_record, transcript_st
|
|
382 |
session_manager.clear_session(sid)
|
383 |
logging.info(f"Cleared all files for session {sid}")
|
384 |
user_dir = session_manager.get_user_dir(sid)
|
385 |
-
left_nav = build_left_nav(sid, user_dir)
|
386 |
right_preview = build_right_preview("none", None, sid, user_dir)
|
387 |
transcript_status = {"status": "idle"}
|
388 |
store_preview = {"type": "none"}
|
389 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
390 |
|
391 |
-
left_nav = build_left_nav(sid, user_dir)
|
392 |
right_preview = build_right_preview("none", None, sid, user_dir)
|
393 |
transcript_status = transcript_status_data if transcript_status_data else {"status": "idle"}
|
394 |
store_preview = store_preview_data if store_preview_data else {"type": "none"}
|
395 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
397 |
@server.route('/preview/audio/<sid>')
|
398 |
def serve_audio_preview(sid):
|
399 |
user_dir = session_manager.get_user_dir(sid)
|
|
|
7 |
import logging
|
8 |
import time
|
9 |
import base64
|
|
|
10 |
from datetime import datetime
|
11 |
+
import json
|
12 |
from flask import request
|
13 |
import dash
|
14 |
from dash import Dash, dcc, html, Input, Output, State, callback_context, no_update
|
15 |
import dash_bootstrap_components as dbc
|
|
|
16 |
import requests
|
17 |
import docx
|
18 |
|
19 |
AUDIO_CHUNK_SIZE_MB = 25
|
20 |
+
AUDIO_EXTS = ['.wav']
|
21 |
TRANSCRIPT_FILENAME = 'transcript.docx'
|
22 |
MINUTES_FILENAME = 'minutes.docx'
|
23 |
RAW_AUDIO_FILENAME = 'meeting_audio.wav'
|
|
|
170 |
app = Dash(__name__, external_stylesheets=external_stylesheets, suppress_callback_exceptions=True)
|
171 |
server = app.server
|
172 |
|
173 |
+
recording_js = '''
|
174 |
+
window.dash_clientside = window.dash_clientside || {};
|
175 |
+
window.dash_clientside.meetingrecorder = {
|
176 |
+
recorderState: {
|
177 |
+
mediaRecorder: null,
|
178 |
+
audioChunks: [],
|
179 |
+
isRecording: false
|
180 |
+
},
|
181 |
+
recordOrStop: function(nClicks, currentState) {
|
182 |
+
let that = window.dash_clientside.meetingrecorder;
|
183 |
+
if (!nClicks) {
|
184 |
+
return window.dash_clientside.no_update;
|
185 |
+
}
|
186 |
+
if (!that.recorderState.isRecording) {
|
187 |
+
navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1 }, video: false }).then(function(stream) {
|
188 |
+
const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/wav' });
|
189 |
+
that.recorderState.mediaRecorder = mediaRecorder;
|
190 |
+
that.recorderState.audioChunks = [];
|
191 |
+
that.recorderState.isRecording = true;
|
192 |
+
mediaRecorder.ondataavailable = function(event) {
|
193 |
+
if (event.data.size > 0) {
|
194 |
+
that.recorderState.audioChunks.push(event.data);
|
195 |
+
}
|
196 |
+
};
|
197 |
+
mediaRecorder.onstop = function() {
|
198 |
+
const audioBlob = new Blob(that.recorderState.audioChunks, {type: 'audio/wav'});
|
199 |
+
const reader = new FileReader();
|
200 |
+
reader.onloadend = function() {
|
201 |
+
const base64data = reader.result;
|
202 |
+
const payload = { audio: base64data };
|
203 |
+
window.localStorage.setItem("meeting-audio-latest", base64data);
|
204 |
+
const evt = new CustomEvent("meeting-audio-recorded");
|
205 |
+
window.dispatchEvent(evt);
|
206 |
+
};
|
207 |
+
reader.readAsDataURL(audioBlob);
|
208 |
+
};
|
209 |
+
mediaRecorder.start();
|
210 |
+
});
|
211 |
+
return {isRecording: true};
|
212 |
+
} else {
|
213 |
+
if (that.recorderState.mediaRecorder) {
|
214 |
+
that.recorderState.mediaRecorder.stop();
|
215 |
+
that.recorderState.isRecording = false;
|
216 |
+
}
|
217 |
+
return {isRecording: false};
|
218 |
+
}
|
219 |
+
}
|
220 |
+
};
|
221 |
+
'''
|
222 |
+
|
223 |
+
app.clientside_callback(
|
224 |
+
recording_js + '''
|
225 |
+
return window.dash_clientside.meetingrecorder.recordOrStop(nClicks, currentState);
|
226 |
+
''',
|
227 |
+
Output("store-recording-state", "data"),
|
228 |
+
Input("btn-record", "n_clicks"),
|
229 |
+
State("store-recording-state", "data"),
|
230 |
+
prevent_initial_call=False
|
231 |
+
)
|
232 |
+
|
233 |
+
app.index_string = '''
|
234 |
+
<!DOCTYPE html>
|
235 |
+
<html>
|
236 |
+
<head>
|
237 |
+
{%metas%}
|
238 |
+
<title>Meeting Recorder & Transcriber</title>
|
239 |
+
{%favicon%}
|
240 |
+
{%css%}
|
241 |
+
</head>
|
242 |
+
<body>
|
243 |
+
{%app_entry%}
|
244 |
+
<script>
|
245 |
+
{recording_js}
|
246 |
+
document.addEventListener("DOMContentLoaded", function() {
|
247 |
+
if (!window.meetingAudioListenerRegistered) {
|
248 |
+
window.addEventListener("meeting-audio-recorded", function() {
|
249 |
+
var audioData = window.localStorage.getItem("meeting-audio-latest");
|
250 |
+
if (audioData) {
|
251 |
+
var req = new XMLHttpRequest();
|
252 |
+
req.open("POST", "/record_audio", true);
|
253 |
+
req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
254 |
+
req.onreadystatechange = function() {
|
255 |
+
if (req.readyState === 4) {
|
256 |
+
window.localStorage.removeItem("meeting-audio-latest");
|
257 |
+
window.location.reload();
|
258 |
+
}
|
259 |
+
};
|
260 |
+
req.send(JSON.stringify({ audio: audioData }));
|
261 |
+
}
|
262 |
+
});
|
263 |
+
window.meetingAudioListenerRegistered = true;
|
264 |
+
}
|
265 |
+
});
|
266 |
+
</script>
|
267 |
+
{%config%}
|
268 |
+
{%scripts%}
|
269 |
+
{%renderer%}
|
270 |
+
</body>
|
271 |
+
</html>
|
272 |
+
'''.replace("{recording_js}", recording_js)
|
273 |
+
|
274 |
+
def build_left_nav(sid, user_dir, is_recording):
|
275 |
audio_path = get_audio_path(user_dir)
|
276 |
transcript_path = get_transcript_path(user_dir)
|
277 |
minutes_path = get_minutes_path(user_dir)
|
|
|
286 |
links.append(dbc.NavLink("Meeting Minutes", href="#", id="nav-minutes", n_clicks=0, className="mb-2"))
|
287 |
links.append(html.A("Download Minutes (docx)", href=f"/download/minutes/{sid}", target="_blank", id="dl-minutes", className="mb-2 d-block"))
|
288 |
links.append(html.Hr())
|
289 |
+
links.append(dbc.Button(
|
290 |
+
"Delete All Files", id="btn-clear-all", color="secondary", className="mb-2", n_clicks=0))
|
291 |
+
record_label = "Stop Recording" if is_recording else "Record Meeting"
|
292 |
+
links.append(
|
293 |
+
dbc.Button(
|
294 |
+
record_label,
|
295 |
+
id="btn-record",
|
296 |
+
color="primary" if not is_recording else "danger",
|
297 |
+
className="mb-2",
|
298 |
+
n_clicks=0,
|
299 |
+
style={"width": "100%"}
|
300 |
+
)
|
301 |
+
)
|
302 |
return dbc.Card(
|
303 |
dbc.CardBody([
|
304 |
html.H4("Navigation/Downloads", className="card-title"),
|
305 |
html.Div(links, id="left-links"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
306 |
]),
|
307 |
className="mb-2"
|
308 |
)
|
|
|
311 |
if content_type == "audio":
|
312 |
audio_path = get_audio_path(user_dir)
|
313 |
if not file_exists(audio_path):
|
314 |
+
return html.Div("No audio recorded yet.")
|
315 |
return html.Audio(src=f"/preview/audio/{sid}", controls=True, style={"width": "100%"})
|
316 |
elif content_type == "transcript":
|
317 |
transcript_path = get_transcript_path(user_dir)
|
318 |
if not file_exists(transcript_path):
|
319 |
+
return html.Div("No transcript found. Please record meeting first.")
|
320 |
with open(transcript_path, "rb") as f:
|
321 |
doc = docx.Document(f)
|
322 |
text = "\n".join([para.text for para in doc.paragraphs])
|
|
|
327 |
elif content_type == "minutes":
|
328 |
minutes_path = get_minutes_path(user_dir)
|
329 |
if not file_exists(minutes_path):
|
330 |
+
return html.Div("No meeting minutes found. Please record and transcribe meeting first.")
|
331 |
with open(minutes_path, "rb") as f:
|
332 |
doc = docx.Document(f)
|
333 |
text = "\n".join([para.text for para in doc.paragraphs])
|
|
|
342 |
dcc.Store(id="store-session", storage_type="session"),
|
343 |
dcc.Store(id="store-preview", data={"type": "none"}),
|
344 |
dcc.Store(id="store-transcript-status", data={"status": "idle"}),
|
345 |
+
dcc.Store(id="store-recording-state", data={"isRecording": False}),
|
346 |
dcc.Interval(id="interval-refresh", interval=60000, n_intervals=0),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
347 |
html.Div([
|
348 |
dbc.Button(id="btn-record", style={"display": "none"}),
|
349 |
dbc.Button(id="btn-clear-all", style={"display": "none"}),
|
|
|
385 |
Output("right-preview", "children"),
|
386 |
Output("store-preview", "data"),
|
387 |
Input("interval-refresh", "n_intervals"),
|
388 |
+
Input("store-recording-state", "data"),
|
|
|
|
|
389 |
Input("nav-audio", "n_clicks"),
|
390 |
Input("nav-transcript", "n_clicks"),
|
391 |
Input("nav-minutes", "n_clicks"),
|
392 |
Input("btn-clear-all", "n_clicks"),
|
393 |
State("store-session", "data"),
|
394 |
State("store-preview", "data"),
|
395 |
+
State("store-transcript-status", "data"),
|
396 |
prevent_initial_call=False
|
397 |
)
|
398 |
+
def main_controller(interval_refresh, recording_state,
|
399 |
nav_audio, nav_transcript, nav_minutes, btn_clear_all,
|
400 |
+
session_data, store_preview_data, transcript_status_data):
|
401 |
|
402 |
ctx = callback_context
|
403 |
triggered = ctx.triggered
|
|
|
406 |
sid = session_manager.get_session_id()
|
407 |
user_dir = session_manager.get_user_dir(sid)
|
408 |
lock = session_manager.get_lock(sid)
|
409 |
+
is_recording = (recording_state or {}).get("isRecording", False)
|
410 |
+
left_nav = build_left_nav(sid, user_dir, is_recording)
|
411 |
right_preview = build_right_preview("none", None, sid, user_dir)
|
412 |
store_preview = {"type": "none"}
|
413 |
transcript_status = {"status": "idle"}
|
|
|
418 |
transcript_status = transcript_status_data if transcript_status_data else {"status": "idle"}
|
419 |
store_preview = store_preview_data if store_preview_data else {"type": "none"}
|
420 |
right_preview = build_right_preview(store_preview.get('type', 'none'), None, sid, user_dir)
|
421 |
+
left_nav = build_left_nav(sid, user_dir, is_recording)
|
422 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
423 |
|
424 |
+
elif trigger_id == "store-recording-state":
|
425 |
+
audio_path = get_audio_path(user_dir)
|
426 |
+
if file_exists(audio_path) and not is_recording:
|
427 |
+
left_nav = build_left_nav(sid, user_dir, False)
|
428 |
+
transcript_status = {"status": "audio_uploaded"}
|
429 |
+
return session_store, left_nav, transcript_status, right_preview, store_preview
|
430 |
+
else:
|
431 |
+
left_nav = build_left_nav(sid, user_dir, is_recording)
|
432 |
+
return session_store, left_nav, transcript_status_data if transcript_status_data else {"status": "idle"}, right_preview, store_preview
|
|
|
|
|
433 |
|
434 |
elif trigger_id == "store-transcript-status":
|
435 |
if transcript_status_data and transcript_status_data.get("status") == "audio_uploaded":
|
|
|
448 |
minutes_path = get_minutes_path(user_dir)
|
449 |
write_docx(minutes_text, minutes_path)
|
450 |
logging.info(f"Transcription and minutes generated for session {sid}")
|
451 |
+
left_nav = build_left_nav(sid, user_dir, False)
|
452 |
transcript_status = {"status": "done"}
|
453 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
454 |
else:
|
455 |
+
left_nav = build_left_nav(sid, user_dir, is_recording)
|
456 |
transcript_status = transcript_status_data if transcript_status_data else {"status": "idle"}
|
457 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
458 |
|
459 |
elif trigger_id in ["nav-audio", "nav-transcript", "nav-minutes"]:
|
460 |
+
left_nav = build_left_nav(sid, user_dir, is_recording)
|
461 |
if trigger_id == "nav-audio":
|
462 |
right_preview = build_right_preview("audio", None, sid, user_dir)
|
463 |
store_preview = {"type": "audio"}
|
|
|
474 |
session_manager.clear_session(sid)
|
475 |
logging.info(f"Cleared all files for session {sid}")
|
476 |
user_dir = session_manager.get_user_dir(sid)
|
477 |
+
left_nav = build_left_nav(sid, user_dir, False)
|
478 |
right_preview = build_right_preview("none", None, sid, user_dir)
|
479 |
transcript_status = {"status": "idle"}
|
480 |
store_preview = {"type": "none"}
|
481 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
482 |
|
483 |
+
left_nav = build_left_nav(sid, user_dir, is_recording)
|
484 |
right_preview = build_right_preview("none", None, sid, user_dir)
|
485 |
transcript_status = transcript_status_data if transcript_status_data else {"status": "idle"}
|
486 |
store_preview = store_preview_data if store_preview_data else {"type": "none"}
|
487 |
return session_store, left_nav, transcript_status, right_preview, store_preview
|
488 |
|
489 |
+
@server.route('/record_audio', methods=["POST"])
|
490 |
+
def record_audio():
|
491 |
+
sid = session_manager.get_session_id()
|
492 |
+
user_dir = session_manager.get_user_dir(sid)
|
493 |
+
data = request.get_json(force=True)
|
494 |
+
audio_b64 = data.get("audio", "")
|
495 |
+
if audio_b64:
|
496 |
+
save_uploaded_audio(audio_b64, user_dir)
|
497 |
+
logging.info(f"Audio recorded and saved for session {sid}")
|
498 |
+
return "OK", 200
|
499 |
+
else:
|
500 |
+
return "No audio data", 400
|
501 |
+
|
502 |
@server.route('/preview/audio/<sid>')
|
503 |
def serve_audio_preview(sid):
|
504 |
user_dir = session_manager.get_user_dir(sid)
|