Update app.py
Browse files
app.py
CHANGED
@@ -54,17 +54,7 @@ if not grok_api_key:
|
|
54 |
server = flask.Flask(__name__)
|
55 |
app = dash.Dash(__name__, server=server, external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True)
|
56 |
|
57 |
-
session_data = defaultdict(lambda: {
|
58 |
-
"audio_path": None,
|
59 |
-
"transcript": None,
|
60 |
-
"minutes": None,
|
61 |
-
"temp_dir": None,
|
62 |
-
"original_filename": None,
|
63 |
-
"diarized_transcript": None,
|
64 |
-
"diarization_done": False,
|
65 |
-
"advanced_diarized_transcript": None,
|
66 |
-
"preview_mode": "auto"
|
67 |
-
})
|
68 |
session_locks = defaultdict(threading.Lock)
|
69 |
|
70 |
def get_session_dir(session_id):
|
@@ -163,186 +153,6 @@ def transcribe_audio(file_path):
|
|
163 |
logging.error(f"An unexpected error occurred during transcription: {e}")
|
164 |
return f"Error during transcription: An unexpected error occurred."
|
165 |
|
166 |
-
def diarize_transcript_ai(transcript, model_name, session_id):
|
167 |
-
logging.info(f"Starting AI diarization using {model_name} for session {session_id}")
|
168 |
-
if not transcript or "Error:" in transcript:
|
169 |
-
return "Error: Cannot diarize invalid or missing transcript."
|
170 |
-
prompt = (
|
171 |
-
"You are given a transcript of a conversation or meeting. "
|
172 |
-
"Please analyze the text and assign speaker turns as Speaker 1, Speaker 2, etc. "
|
173 |
-
"If a person introduces themselves by name, try to use their name as the speaker label. "
|
174 |
-
"Otherwise, assign speakers based on changes in voice, speech patterns, or cues in the transcript. "
|
175 |
-
"Format the output as alternating lines, each starting with the speaker label, for example:\n"
|
176 |
-
"Speaker 1: Hello and welcome.\n"
|
177 |
-
"Speaker 2: Thank you. My name is Alex.\n"
|
178 |
-
"Alex: I have a question about...\n"
|
179 |
-
"Speaker 1: Please go ahead.\n"
|
180 |
-
"If unsure, use Speaker 1, Speaker 2, etc. Do not invent content.\n\n"
|
181 |
-
f"Transcript:\n{transcript}\n\n"
|
182 |
-
"Diarized Transcript:"
|
183 |
-
)
|
184 |
-
with session_locks[session_id]:
|
185 |
-
try:
|
186 |
-
if model_name == 'openai':
|
187 |
-
if not openai.api_key: return "Error: OpenAI API key not configured."
|
188 |
-
client = openai.OpenAI()
|
189 |
-
response = client.chat.completions.create(
|
190 |
-
model="gpt-3.5-turbo",
|
191 |
-
messages=[
|
192 |
-
{"role": "system", "content": "You are a professional assistant for meeting AI diarization."},
|
193 |
-
{"role": "user", "content": prompt}
|
194 |
-
],
|
195 |
-
timeout=120
|
196 |
-
)
|
197 |
-
logging.info(f"OpenAI diarization successful for session {session_id}")
|
198 |
-
return response.choices[0].message.content
|
199 |
-
elif model_name == 'gemini':
|
200 |
-
if not genai: return "Error: Google Gemini API not configured or key missing."
|
201 |
-
model = genai.GenerativeModel('gemini-1.5-flash-latest')
|
202 |
-
response = model.generate_content(
|
203 |
-
prompt,
|
204 |
-
request_options={'timeout': 120}
|
205 |
-
)
|
206 |
-
logging.info(f"Gemini diarization successful for session {session_id}")
|
207 |
-
if response.parts:
|
208 |
-
return response.text
|
209 |
-
else:
|
210 |
-
logging.warning(f"Gemini response blocked or empty for diarization for session {session_id}. Reason: {response.prompt_feedback}")
|
211 |
-
return f"Error: Gemini response blocked or empty. Reason: {response.prompt_feedback}"
|
212 |
-
elif model_name == 'anthropic':
|
213 |
-
if not anthropic: return "Error: Anthropic API not configured or key missing."
|
214 |
-
response = anthropic.messages.create(
|
215 |
-
model="claude-3-5-haiku-20241022",
|
216 |
-
max_tokens=2000,
|
217 |
-
messages=[
|
218 |
-
{
|
219 |
-
"role": "user",
|
220 |
-
"content": prompt
|
221 |
-
}
|
222 |
-
],
|
223 |
-
timeout=120
|
224 |
-
)
|
225 |
-
logging.info(f"Anthropic diarization successful for session {session_id}")
|
226 |
-
if response.content and isinstance(response.content, list) and hasattr(response.content[0], 'text'):
|
227 |
-
return response.content[0].text
|
228 |
-
else:
|
229 |
-
logging.error(f"Could not extract content from Anthropic response (diarization): {response}")
|
230 |
-
return "Error: Could not extract content from Anthropic response."
|
231 |
-
elif model_name == 'grok':
|
232 |
-
if not grok_api_key: return "Error: Grok API key (via Groq) not configured."
|
233 |
-
groq_url = "https://api.groq.com/openai/v1/chat/completions"
|
234 |
-
headers = {
|
235 |
-
"Authorization": f"Bearer {grok_api_key}",
|
236 |
-
"Content-Type": "application/json"
|
237 |
-
}
|
238 |
-
data = {
|
239 |
-
"model": "grok-3-mini-fast-beta",
|
240 |
-
"messages": [
|
241 |
-
{"role": "system", "content": "You are a professional assistant for meeting AI diarization."},
|
242 |
-
{"role": "user", "content": prompt}
|
243 |
-
],
|
244 |
-
"max_tokens": 2000,
|
245 |
-
"temperature": 0.7
|
246 |
-
}
|
247 |
-
response = requests.post(groq_url, headers=headers, json=data, timeout=120)
|
248 |
-
response.raise_for_status()
|
249 |
-
logging.info(f"Groq ({data['model']}) diarization successful for session {session_id}")
|
250 |
-
return response.json()["choices"][0]["message"]["content"]
|
251 |
-
else:
|
252 |
-
logging.warning(f"Invalid model selection for diarization: {model_name}")
|
253 |
-
return "Error: Invalid model selection"
|
254 |
-
except Exception as e:
|
255 |
-
logging.error(f"Error diarizing transcript with {model_name} for session {session_id}: {e}", exc_info=True)
|
256 |
-
return f"Error diarizing transcript using {model_name}: An unexpected error occurred."
|
257 |
-
|
258 |
-
def advanced_diarize_transcript_ai(transcript, model_name, session_id):
|
259 |
-
logging.info(f"Starting advanced AI diarization using {model_name} for session {session_id}")
|
260 |
-
if not transcript or "Error:" in transcript:
|
261 |
-
return "Error: Cannot diarize invalid or missing transcript."
|
262 |
-
prompt = (
|
263 |
-
"Analyze the given transcript to identify distinct speakers without labeled identifiers. "
|
264 |
-
"Create unique speaker embeddings based on individual speech patterns, vocabulary choices, and linguistic styles. "
|
265 |
-
"Examine the context and content of each utterance to detect likely speaker changes. "
|
266 |
-
"Recognize typical conversation structures and turn-taking behaviors to differentiate between speakers. "
|
267 |
-
"Finally, use topic modeling to identify shifts in subject matter and areas of expertise, associating certain topics with specific speakers. "
|
268 |
-
"Based on this analysis, assign speaker labels (e.g., Speaker 1, Speaker 2, name if given) to each utterance in the transcript.\n\n"
|
269 |
-
f"Transcript:\n{transcript}\n\n"
|
270 |
-
"Diarized Transcript:"
|
271 |
-
)
|
272 |
-
with session_locks[session_id]:
|
273 |
-
try:
|
274 |
-
if model_name == 'openai':
|
275 |
-
if not openai.api_key: return "Error: OpenAI API key not configured."
|
276 |
-
client = openai.OpenAI()
|
277 |
-
response = client.chat.completions.create(
|
278 |
-
model="gpt-3.5-turbo",
|
279 |
-
messages=[
|
280 |
-
{"role": "system", "content": "You are a professional assistant for meeting AI diarization."},
|
281 |
-
{"role": "user", "content": prompt}
|
282 |
-
],
|
283 |
-
timeout=120
|
284 |
-
)
|
285 |
-
logging.info(f"OpenAI advanced diarization successful for session {session_id}")
|
286 |
-
return response.choices[0].message.content
|
287 |
-
elif model_name == 'gemini':
|
288 |
-
if not genai: return "Error: Google Gemini API not configured or key missing."
|
289 |
-
model = genai.GenerativeModel('gemini-1.5-flash-latest')
|
290 |
-
response = model.generate_content(
|
291 |
-
prompt,
|
292 |
-
request_options={'timeout': 120}
|
293 |
-
)
|
294 |
-
logging.info(f"Gemini advanced diarization successful for session {session_id}")
|
295 |
-
if response.parts:
|
296 |
-
return response.text
|
297 |
-
else:
|
298 |
-
logging.warning(f"Gemini response blocked or empty for advanced diarization for session {session_id}. Reason: {response.prompt_feedback}")
|
299 |
-
return f"Error: Gemini response blocked or empty. Reason: {response.prompt_feedback}"
|
300 |
-
elif model_name == 'anthropic':
|
301 |
-
if not anthropic: return "Error: Anthropic API not configured or key missing."
|
302 |
-
response = anthropic.messages.create(
|
303 |
-
model="claude-3-5-haiku-20241022",
|
304 |
-
max_tokens=2000,
|
305 |
-
messages=[
|
306 |
-
{
|
307 |
-
"role": "user",
|
308 |
-
"content": prompt
|
309 |
-
}
|
310 |
-
],
|
311 |
-
timeout=120
|
312 |
-
)
|
313 |
-
logging.info(f"Anthropic advanced diarization successful for session {session_id}")
|
314 |
-
if response.content and isinstance(response.content, list) and hasattr(response.content[0], 'text'):
|
315 |
-
return response.content[0].text
|
316 |
-
else:
|
317 |
-
logging.error(f"Could not extract content from Anthropic response (advanced diarization): {response}")
|
318 |
-
return "Error: Could not extract content from Anthropic response."
|
319 |
-
elif model_name == 'grok':
|
320 |
-
if not grok_api_key: return "Error: Grok API key (via Groq) not configured."
|
321 |
-
groq_url = "https://api.groq.com/openai/v1/chat/completions"
|
322 |
-
headers = {
|
323 |
-
"Authorization": f"Bearer {grok_api_key}",
|
324 |
-
"Content-Type": "application/json"
|
325 |
-
}
|
326 |
-
data = {
|
327 |
-
"model": "grok-3-mini-fast-beta",
|
328 |
-
"messages": [
|
329 |
-
{"role": "system", "content": "You are a professional assistant for meeting AI diarization."},
|
330 |
-
{"role": "user", "content": prompt}
|
331 |
-
],
|
332 |
-
"max_tokens": 2000,
|
333 |
-
"temperature": 0.7
|
334 |
-
}
|
335 |
-
response = requests.post(groq_url, headers=headers, json=data, timeout=120)
|
336 |
-
response.raise_for_status()
|
337 |
-
logging.info(f"Groq ({data['model']}) advanced diarization successful for session {session_id}")
|
338 |
-
return response.json()["choices"][0]["message"]["content"]
|
339 |
-
else:
|
340 |
-
logging.warning(f"Invalid model selection for advanced diarization: {model_name}")
|
341 |
-
return "Error: Invalid model selection"
|
342 |
-
except Exception as e:
|
343 |
-
logging.error(f"Error in advanced diarization with {model_name} for session {session_id}: {e}", exc_info=True)
|
344 |
-
return f"Error advanced diarizing transcript using {model_name}: An unexpected error occurred."
|
345 |
-
|
346 |
def generate_minutes_ai(transcript, model_name, session_id):
|
347 |
logging.info(f"Generating minutes using {model_name} for session {session_id}")
|
348 |
if not transcript or "Error:" in transcript:
|
@@ -465,112 +275,87 @@ app.layout = dbc.Container([
|
|
465 |
dcc.Download(id="download-transcript"),
|
466 |
dcc.Download(id="download-audio"),
|
467 |
dcc.Download(id="download-minutes"),
|
468 |
-
dcc.Download(id="download-diarized"),
|
469 |
dbc.Row([
|
470 |
-
dbc.Col(
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
html.H5("Transcript / Minutes"),
|
551 |
-
html.Div(id="transcript-preview", style={
|
552 |
-
"height": "400px",
|
553 |
-
"overflow-y": "scroll",
|
554 |
-
"border": "1px solid #ccc",
|
555 |
-
"padding": "10px",
|
556 |
-
"white-space": "pre-wrap",
|
557 |
-
"word-wrap": "break-word",
|
558 |
-
"background-color": "#f9f9f9"
|
559 |
-
}),
|
560 |
-
html.H5("Downloads", className="mt-3"),
|
561 |
-
dbc.Row([
|
562 |
-
dbc.Col(dbc.Button("Download Transcript (.docx)", id="download-transcript-btn", color="info", className="w-100 mb-2", disabled=True), width=12, md=4),
|
563 |
-
dbc.Col(dbc.Button("Download Minutes (.docx)", id="download-minutes-btn", color="info", className="w-100 mb-2", disabled=True), width=12, md=4),
|
564 |
-
dbc.Col(dbc.Button("Download Processed Audio", id="download-audio-btn", color="info", className="w-100 mb-2", disabled=True), width=12, md=4),
|
565 |
-
]),
|
566 |
-
])
|
567 |
-
]
|
568 |
-
),
|
569 |
-
html.Div(id="loading-output", style={"height": "0px", "visibility": "hidden"}),
|
570 |
-
]),
|
571 |
-
style={'height': '80vh', 'overflow-y': 'auto', 'position': 'relative'}
|
572 |
-
), width=12, lg=8
|
573 |
-
),
|
574 |
])
|
575 |
], fluid=True)
|
576 |
|
@@ -607,81 +392,38 @@ def manage_session_id(existing_session_id):
|
|
607 |
return final_session_id
|
608 |
|
609 |
@app.callback(
|
610 |
-
[
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
Input('audio-uploader', 'contents'),
|
627 |
-
Input("minutes-btn", "n_clicks"),
|
628 |
-
Input("delete-btn", "n_clicks"),
|
629 |
-
Input("diarize-btn", "n_clicks"),
|
630 |
-
Input("diarize-checkbox", "value"),
|
631 |
-
Input("nav-original-btn", "n_clicks"),
|
632 |
-
Input("nav-diarized-btn", "n_clicks"),
|
633 |
-
Input("nav-minutes-btn", "n_clicks"),
|
634 |
-
Input("advanced-diarize-btn", "n_clicks"),
|
635 |
-
],
|
636 |
-
[
|
637 |
-
State("session-id", "data"),
|
638 |
-
State("model-selection", "value"),
|
639 |
-
State("transcript-preview", "children"),
|
640 |
-
State('audio-uploader', 'filename'),
|
641 |
-
State("diarize-checkbox", "value")
|
642 |
-
],
|
643 |
prevent_initial_call=True
|
644 |
)
|
645 |
-
def handle_actions(
|
646 |
-
upload_contents, minutes_clicks, delete_clicks, diarize_clicks, diarize_checkbox_val,
|
647 |
-
nav_original_clicks, nav_diarized_clicks, nav_minutes_clicks, advanced_diarize_clicks,
|
648 |
-
session_id, selected_model, existing_preview, filename, diarize_checkbox_val2
|
649 |
-
):
|
650 |
-
diarize_checkbox = diarize_checkbox_val if diarize_checkbox_val is not None else diarize_checkbox_val2
|
651 |
if not session_id:
|
652 |
logging.warning("Session ID missing in handle_actions.")
|
653 |
-
return "Status: Error - Session ID missing", "", True, True, True, True, True, None, "Status: Error"
|
654 |
ctx = dash.callback_context
|
655 |
triggered_id = ctx.triggered_id if hasattr(ctx, 'triggered_id') else (ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None)
|
656 |
current_transcript = session_data[session_id].get("transcript", "")
|
657 |
current_minutes = session_data[session_id].get("minutes", "")
|
658 |
current_audio_path = session_data[session_id].get("audio_path", None)
|
659 |
original_filename = session_data[session_id].get("original_filename", None)
|
660 |
-
|
661 |
-
diarization_done = session_data[session_id].get("diarization_done", False)
|
662 |
-
advanced_diarized_transcript = session_data[session_id].get("advanced_diarized_transcript", None)
|
663 |
-
preview_mode = session_data[session_id].get("preview_mode", "auto")
|
664 |
-
output_text = ""
|
665 |
-
# Preview mode logic
|
666 |
-
if preview_mode == "original":
|
667 |
-
output_text = current_transcript if current_transcript else "No transcript available."
|
668 |
-
elif preview_mode == "diarized":
|
669 |
-
output_text = advanced_diarized_transcript if advanced_diarized_transcript else (diarized_transcript if diarized_transcript else "No diarized transcript available.")
|
670 |
-
elif preview_mode == "minutes":
|
671 |
-
output_text = current_minutes if current_minutes else "No minutes available."
|
672 |
-
else:
|
673 |
-
output_text = current_minutes if current_minutes else (
|
674 |
-
advanced_diarized_transcript if advanced_diarized_transcript else (
|
675 |
-
diarized_transcript if diarize_checkbox and diarized_transcript else (
|
676 |
-
current_transcript if current_transcript else "Upload an audio or video file to begin."
|
677 |
-
)
|
678 |
-
)
|
679 |
-
)
|
680 |
status_msg = "Status: Idle"
|
681 |
if current_minutes and "Error:" not in current_minutes:
|
682 |
status_msg = "Status: Session restored. Minutes loaded."
|
683 |
-
elif (diarize_checkbox or advanced_diarized_transcript) and (advanced_diarized_transcript or diarized_transcript) and "Error:" not in (advanced_diarized_transcript or diarized_transcript or ""):
|
684 |
-
status_msg = "Status: Session restored. Diarized transcript loaded."
|
685 |
elif current_transcript and "Error:" not in current_transcript:
|
686 |
status_msg = "Status: Session restored. Transcript loaded. Ready for Minutes Generation."
|
687 |
elif current_audio_path and os.path.exists(current_audio_path):
|
@@ -693,18 +435,12 @@ def handle_actions(
|
|
693 |
dl_minutes_disabled = not bool(current_minutes and "Error:" not in current_minutes)
|
694 |
dl_audio_disabled = not bool(current_audio_path and os.path.exists(current_audio_path))
|
695 |
delete_disabled = not bool(session_data.get(session_id, {}).get("temp_dir"))
|
696 |
-
diarize_disabled = not bool(current_transcript and "Error:" not in current_transcript)
|
697 |
-
dl_diarized_disabled = not bool((advanced_diarized_transcript or diarized_transcript) and "Error:" not in (advanced_diarized_transcript or diarized_transcript or ""))
|
698 |
-
uploaded_filename_text = original_filename if original_filename else ""
|
699 |
-
advanced_diarize_disabled = diarize_disabled
|
700 |
loading_output = None
|
701 |
upload_status_msg = f"Status: {'Loaded: ' + original_filename if original_filename else 'Ready to Upload'}"
|
702 |
start_time = time.time()
|
703 |
-
# File upload logic
|
704 |
if triggered_id == 'audio-uploader' and upload_contents is not None and filename is not None:
|
705 |
logging.info(f"File uploaded for session {session_id}, filename: {filename}")
|
706 |
session_data[session_id]["original_filename"] = filename
|
707 |
-
uploaded_filename_text = filename
|
708 |
upload_status_msg = f"Status: Processing Uploaded File ({filename})..."
|
709 |
status_msg = "Status: Processing Upload..."
|
710 |
loading_output = "Processing Upload..."
|
@@ -719,18 +455,12 @@ def handle_actions(
|
|
719 |
session_data[session_id]["transcript"] = None
|
720 |
session_data[session_id]["minutes"] = None
|
721 |
session_data[session_id]["original_filename"] = None
|
722 |
-
session_data[session_id]["diarized_transcript"] = None
|
723 |
-
session_data[session_id]["diarization_done"] = False
|
724 |
-
session_data[session_id]["advanced_diarized_transcript"] = None
|
725 |
minutes_disabled = True
|
726 |
dl_transcript_disabled = True
|
727 |
dl_minutes_disabled = True
|
728 |
dl_audio_disabled = True
|
729 |
delete_disabled = False
|
730 |
-
|
731 |
-
dl_diarized_disabled = True
|
732 |
-
advanced_diarize_disabled = True
|
733 |
-
return status_msg, output_text, minutes_disabled, dl_transcript_disabled, dl_minutes_disabled, dl_audio_disabled, delete_disabled, None, upload_status_msg, diarize_disabled, dl_diarized_disabled, uploaded_filename_text, advanced_diarize_disabled
|
734 |
safe_upload_filename = f"uploaded_file{f_ext}"
|
735 |
upload_file_path = os.path.join(session_dir, safe_upload_filename)
|
736 |
saved_upload_path = save_base64_data(upload_contents, upload_file_path)
|
@@ -762,13 +492,7 @@ def handle_actions(
|
|
762 |
dl_minutes_disabled = True
|
763 |
dl_audio_disabled = True
|
764 |
delete_disabled = False
|
765 |
-
|
766 |
-
dl_diarized_disabled = True
|
767 |
-
session_data[session_id]["diarized_transcript"] = None
|
768 |
-
session_data[session_id]["diarization_done"] = False
|
769 |
-
session_data[session_id]["advanced_diarized_transcript"] = None
|
770 |
-
advanced_diarize_disabled = True
|
771 |
-
return status_msg, output_text, minutes_disabled, dl_transcript_disabled, dl_minutes_disabled, dl_audio_disabled, delete_disabled, None, upload_status_msg, diarize_disabled, dl_diarized_disabled, uploaded_filename_text, advanced_diarize_disabled
|
772 |
else:
|
773 |
audio_path_for_transcription = saved_upload_path
|
774 |
session_data[session_id]["audio_path"] = saved_upload_path
|
@@ -781,9 +505,6 @@ def handle_actions(
|
|
781 |
transcript_text = transcribe_audio(audio_path_for_transcription)
|
782 |
session_data[session_id]["transcript"] = transcript_text
|
783 |
session_data[session_id]["minutes"] = None
|
784 |
-
session_data[session_id]["diarized_transcript"] = None
|
785 |
-
session_data[session_id]["diarization_done"] = False
|
786 |
-
session_data[session_id]["advanced_diarized_transcript"] = None
|
787 |
if "Error:" in transcript_text:
|
788 |
status_msg = f"Status: Transcription Failed - {transcript_text}"
|
789 |
output_text = transcript_text
|
@@ -792,9 +513,6 @@ def handle_actions(
|
|
792 |
dl_minutes_disabled = True
|
793 |
delete_disabled = False
|
794 |
upload_status_msg = f"Status: Transcription Failed. ({filename})"
|
795 |
-
diarize_disabled = True
|
796 |
-
dl_diarized_disabled = True
|
797 |
-
advanced_diarize_disabled = True
|
798 |
else:
|
799 |
status_msg = "Status: Transcription Complete. Ready for Minutes Generation."
|
800 |
output_text = transcript_text
|
@@ -803,9 +521,6 @@ def handle_actions(
|
|
803 |
dl_minutes_disabled = True
|
804 |
delete_disabled = False
|
805 |
upload_status_msg = f"Status: Processed & Transcribed: {filename}"
|
806 |
-
diarize_disabled = False
|
807 |
-
dl_diarized_disabled = True
|
808 |
-
advanced_diarize_disabled = False
|
809 |
processing_time = time.time() - start_time
|
810 |
logging.info(f"File processing and transcription took {processing_time:.2f} seconds for session {session_id}")
|
811 |
else:
|
@@ -819,28 +534,15 @@ def handle_actions(
|
|
819 |
dl_minutes_disabled = True
|
820 |
dl_audio_disabled = True
|
821 |
delete_disabled = False
|
822 |
-
diarize_disabled = True
|
823 |
-
dl_diarized_disabled = True
|
824 |
-
session_data[session_id]["diarized_transcript"] = None
|
825 |
-
session_data[session_id]["diarization_done"] = False
|
826 |
-
session_data[session_id]["advanced_diarized_transcript"] = None
|
827 |
-
advanced_diarize_disabled = True
|
828 |
elif triggered_id == "minutes-btn" and minutes_clicks:
|
829 |
logging.info(f"Generate Minutes button clicked for session {session_id}")
|
830 |
-
|
831 |
-
if
|
832 |
-
transcript_to_use = advanced_diarized_transcript if advanced_diarized_transcript else diarized_transcript
|
833 |
-
elif diarize_checkbox and (advanced_diarized_transcript or diarized_transcript):
|
834 |
-
transcript_to_use = advanced_diarized_transcript if advanced_diarized_transcript else diarized_transcript
|
835 |
-
else:
|
836 |
-
transcript_to_use = current_transcript
|
837 |
-
if transcript_to_use and "Error:" not in transcript_to_use:
|
838 |
status_msg = f"Status: Generating Minutes ({selected_model})..."
|
839 |
loading_output = "Generating Minutes..."
|
840 |
-
minutes_text = generate_minutes_ai(
|
841 |
session_data[session_id]["minutes"] = minutes_text
|
842 |
output_text = minutes_text
|
843 |
-
session_data[session_id]["preview_mode"] = "minutes"
|
844 |
if "Error:" in minutes_text:
|
845 |
status_msg = f"Status: Minutes Generation Failed - {minutes_text}"
|
846 |
dl_minutes_disabled = True
|
@@ -854,77 +556,10 @@ def handle_actions(
|
|
854 |
dl_audio_disabled = not bool(session_data.get(session_id, {}).get("audio_path") and os.path.exists(session_data.get(session_id, {}).get("audio_path", "")))
|
855 |
delete_disabled = False
|
856 |
upload_status_msg = f"Status: Processed & Transcribed: {session_data[session_id].get('original_filename', 'File')}"
|
857 |
-
diarize_disabled = not bool(session_data[session_id].get("transcript") and "Error:" not in session_data[session_id].get("transcript"))
|
858 |
-
dl_diarized_disabled = not bool((session_data[session_id].get("advanced_diarized_transcript") or session_data[session_id].get("diarized_transcript")) and "Error:" not in (session_data[session_id].get("advanced_diarized_transcript") or session_data[session_id].get("diarized_transcript") or ""))
|
859 |
-
advanced_diarize_disabled = diarize_disabled
|
860 |
else:
|
861 |
status_msg = "Status: Cannot generate minutes - No valid transcript available."
|
862 |
output_text = existing_preview
|
863 |
minutes_disabled = True
|
864 |
-
elif triggered_id == "diarize-btn" and diarize_clicks:
|
865 |
-
logging.info(f"Diarize button clicked for session {session_id}")
|
866 |
-
current_transcript = session_data[session_id].get("transcript", "")
|
867 |
-
if current_transcript and "Error:" not in current_transcript:
|
868 |
-
status_msg = f"Status: Diarizing Transcript ({selected_model})..."
|
869 |
-
loading_output = "Diarizing..."
|
870 |
-
diarized_text = diarize_transcript_ai(current_transcript, selected_model, session_id)
|
871 |
-
session_data[session_id]["diarized_transcript"] = diarized_text
|
872 |
-
session_data[session_id]["diarization_done"] = "Error:" not in diarized_text
|
873 |
-
output_text = diarized_text
|
874 |
-
session_data[session_id]["preview_mode"] = "diarized"
|
875 |
-
if "Error:" in diarized_text:
|
876 |
-
status_msg = f"Status: Diarization Failed - {diarized_text}"
|
877 |
-
dl_diarized_disabled = True
|
878 |
-
else:
|
879 |
-
status_msg = "Status: Diarization Complete."
|
880 |
-
dl_diarized_disabled = False
|
881 |
-
diarize_disabled = False
|
882 |
-
advanced_diarize_disabled = False
|
883 |
-
else:
|
884 |
-
status_msg = "Status: Cannot diarize - No valid transcript available."
|
885 |
-
output_text = existing_preview
|
886 |
-
diarize_disabled = True
|
887 |
-
dl_diarized_disabled = True
|
888 |
-
advanced_diarize_disabled = True
|
889 |
-
elif triggered_id == "advanced-diarize-btn" and advanced_diarize_clicks:
|
890 |
-
logging.info(f"Advanced Diarize button clicked for session {session_id}")
|
891 |
-
current_transcript = session_data[session_id].get("transcript", "")
|
892 |
-
if current_transcript and "Error:" not in current_transcript:
|
893 |
-
status_msg = f"Status: Advanced Diarizing Transcript ({selected_model})..."
|
894 |
-
loading_output = "Advanced Diarizing..."
|
895 |
-
adv_diarized_text = advanced_diarize_transcript_ai(current_transcript, selected_model, session_id)
|
896 |
-
session_data[session_id]["advanced_diarized_transcript"] = adv_diarized_text
|
897 |
-
output_text = adv_diarized_text
|
898 |
-
session_data[session_id]["preview_mode"] = "diarized"
|
899 |
-
if "Error:" in adv_diarized_text:
|
900 |
-
status_msg = f"Status: Advanced Diarization Failed - {adv_diarized_text}"
|
901 |
-
dl_diarized_disabled = True
|
902 |
-
else:
|
903 |
-
status_msg = "Status: Advanced Diarization Complete."
|
904 |
-
dl_diarized_disabled = False
|
905 |
-
diarize_disabled = False
|
906 |
-
advanced_diarize_disabled = False
|
907 |
-
else:
|
908 |
-
status_msg = "Status: Cannot advanced diarize - No valid transcript available."
|
909 |
-
output_text = existing_preview
|
910 |
-
diarize_disabled = True
|
911 |
-
dl_diarized_disabled = True
|
912 |
-
advanced_diarize_disabled = True
|
913 |
-
elif triggered_id == "nav-original-btn" and nav_original_clicks:
|
914 |
-
logging.info(f"Nav: View Original Transcript for session {session_id}")
|
915 |
-
output_text = current_transcript if current_transcript else "No transcript available."
|
916 |
-
session_data[session_id]["preview_mode"] = "original"
|
917 |
-
status_msg = "Status: Viewing Original Transcript."
|
918 |
-
elif triggered_id == "nav-diarized-btn" and nav_diarized_clicks:
|
919 |
-
logging.info(f"Nav: View Diarized Transcript for session {session_id}")
|
920 |
-
output_text = advanced_diarized_transcript if advanced_diarized_transcript else (diarized_transcript if diarized_transcript else "No diarized transcript available.")
|
921 |
-
session_data[session_id]["preview_mode"] = "diarized"
|
922 |
-
status_msg = "Status: Viewing Diarized Transcript."
|
923 |
-
elif triggered_id == "nav-minutes-btn" and nav_minutes_clicks:
|
924 |
-
logging.info(f"Nav: View Minutes for session {session_id}")
|
925 |
-
output_text = current_minutes if current_minutes else "No minutes available."
|
926 |
-
session_data[session_id]["preview_mode"] = "minutes"
|
927 |
-
status_msg = "Status: Viewing Minutes."
|
928 |
elif triggered_id == "delete-btn" and delete_clicks:
|
929 |
logging.info(f"Delete button clicked for session {session_id}")
|
930 |
cleanup_session(session_id)
|
@@ -935,49 +570,24 @@ def handle_actions(
|
|
935 |
dl_minutes_disabled = True
|
936 |
dl_audio_disabled = True
|
937 |
delete_disabled = True
|
938 |
-
diarize_disabled = True
|
939 |
-
dl_diarized_disabled = True
|
940 |
-
advanced_diarize_disabled = True
|
941 |
upload_status_msg = "Status: Ready to Upload"
|
942 |
-
uploaded_filename_text = ""
|
943 |
else:
|
944 |
-
|
945 |
-
|
946 |
-
|
947 |
-
|
948 |
-
|
949 |
-
|
950 |
-
|
951 |
-
|
952 |
-
|
953 |
-
|
954 |
-
|
955 |
-
|
956 |
-
|
957 |
-
|
958 |
-
|
959 |
-
|
960 |
-
upload_status_msg = f"Status: Error processing {loaded_original_filename}?"
|
961 |
-
elif loaded_audio_path and os.path.exists(loaded_audio_path):
|
962 |
-
upload_status_msg = f"Status: Processed audio loaded ({loaded_original_filename or 'previous file'})."
|
963 |
-
else:
|
964 |
-
upload_status_msg = "Status: Ready to Upload"
|
965 |
-
uploaded_filename_text = loaded_original_filename if loaded_original_filename else ""
|
966 |
-
pmode = session_data[session_id].get("preview_mode", "auto")
|
967 |
-
if pmode == "original":
|
968 |
-
output_text = loaded_transcript if loaded_transcript else "No transcript available."
|
969 |
-
elif pmode == "diarized":
|
970 |
-
output_text = loaded_adv_diarized if loaded_adv_diarized else (loaded_diarized if loaded_diarized else "No diarized transcript available.")
|
971 |
-
elif pmode == "minutes":
|
972 |
-
output_text = loaded_minutes if loaded_minutes else "No minutes available."
|
973 |
-
else:
|
974 |
-
output_text = loaded_minutes if loaded_minutes else (
|
975 |
-
loaded_adv_diarized if loaded_adv_diarized else (
|
976 |
-
loaded_diarized if diarize_checkbox and loaded_diarized else (
|
977 |
-
loaded_transcript if loaded_transcript else "Upload an audio or video file to begin."
|
978 |
-
)
|
979 |
-
)
|
980 |
-
)
|
981 |
return (
|
982 |
status_msg,
|
983 |
output_text,
|
@@ -987,11 +597,7 @@ def handle_actions(
|
|
987 |
dl_audio_disabled,
|
988 |
delete_disabled,
|
989 |
loading_output,
|
990 |
-
upload_status_msg
|
991 |
-
diarize_disabled,
|
992 |
-
dl_diarized_disabled,
|
993 |
-
uploaded_filename_text,
|
994 |
-
advanced_diarize_disabled
|
995 |
)
|
996 |
|
997 |
@app.callback(
|
@@ -1068,37 +674,7 @@ def download_audio_file(n_clicks, session_id):
|
|
1068 |
logging.error(f"Processed audio file not found at path {audio_path} for session {session_id}")
|
1069 |
return None
|
1070 |
|
1071 |
-
@app.callback(
|
1072 |
-
Output("download-diarized", "data"),
|
1073 |
-
Input("download-diarized-btn", "n_clicks"),
|
1074 |
-
State("session-id", "data"),
|
1075 |
-
prevent_initial_call=True,
|
1076 |
-
)
|
1077 |
-
def download_diarized_file(n_clicks, session_id):
|
1078 |
-
diarized = None
|
1079 |
-
if session_id and session_data.get(session_id, {}).get("advanced_diarized_transcript"):
|
1080 |
-
diarized = session_data[session_id]["advanced_diarized_transcript"]
|
1081 |
-
elif session_id and session_data.get(session_id, {}).get("diarized_transcript"):
|
1082 |
-
diarized = session_data[session_id]["diarized_transcript"]
|
1083 |
-
else:
|
1084 |
-
logging.warning(f"Download diarized transcript requested but no data found for session {session_id}.")
|
1085 |
-
return None
|
1086 |
-
if "Error:" in diarized:
|
1087 |
-
logging.warning(f"Attempted to download diarized transcript containing an error for session {session_id}.")
|
1088 |
-
return None
|
1089 |
-
session_dir = get_session_dir(session_id)
|
1090 |
-
diarized_filename = os.path.join(session_dir, f"diarized_{uuid.uuid4()}.docx")
|
1091 |
-
saved_doc_path = save_to_word(diarized, diarized_filename)
|
1092 |
-
if saved_doc_path:
|
1093 |
-
logging.info(f"Sending diarized transcript file: {saved_doc_path}")
|
1094 |
-
original_filename_base = os.path.splitext(session_data[session_id].get("original_filename", "meeting"))[0]
|
1095 |
-
download_filename = f"{original_filename_base}_diarized.docx"
|
1096 |
-
return dcc.send_file(saved_doc_path, filename=download_filename)
|
1097 |
-
else:
|
1098 |
-
logging.error(f"Failed to create Word document for diarized transcript download for session {session_id}")
|
1099 |
-
return dcc.send_data_frame(lambda: diarized, "meeting_diarized.txt")
|
1100 |
-
|
1101 |
if __name__ == '__main__':
|
1102 |
print("Starting the Dash application...")
|
1103 |
-
app.run(debug=
|
1104 |
print("Dash application has finished running.")
|
|
|
54 |
server = flask.Flask(__name__)
|
55 |
app = dash.Dash(__name__, server=server, external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True)
|
56 |
|
57 |
+
session_data = defaultdict(lambda: {"audio_path": None, "transcript": None, "minutes": None, "temp_dir": None, "original_filename": None})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
session_locks = defaultdict(threading.Lock)
|
59 |
|
60 |
def get_session_dir(session_id):
|
|
|
153 |
logging.error(f"An unexpected error occurred during transcription: {e}")
|
154 |
return f"Error during transcription: An unexpected error occurred."
|
155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
def generate_minutes_ai(transcript, model_name, session_id):
|
157 |
logging.info(f"Generating minutes using {model_name} for session {session_id}")
|
158 |
if not transcript or "Error:" in transcript:
|
|
|
275 |
dcc.Download(id="download-transcript"),
|
276 |
dcc.Download(id="download-audio"),
|
277 |
dcc.Download(id="download-minutes"),
|
|
|
278 |
dbc.Row([
|
279 |
+
dbc.Col(html.H1("AI Meeting Assistant", className="text-center my-4"), width=12)
|
280 |
+
]),
|
281 |
+
dbc.Row([
|
282 |
+
dbc.Col(dbc.Card(
|
283 |
+
dbc.CardBody([
|
284 |
+
html.H4("Controls", className="card-title"),
|
285 |
+
html.Div("Upload meeting audio or video file:"),
|
286 |
+
dcc.Upload(
|
287 |
+
id='audio-uploader',
|
288 |
+
children=html.Div([
|
289 |
+
'Drag and Drop or ',
|
290 |
+
html.A('Select Audio/Video File')
|
291 |
+
]),
|
292 |
+
style={
|
293 |
+
'width': '100%',
|
294 |
+
'height': '60px',
|
295 |
+
'lineHeight': '60px',
|
296 |
+
'borderWidth': '1px',
|
297 |
+
'borderStyle': 'dashed',
|
298 |
+
'borderRadius': '5px',
|
299 |
+
'textAlign': 'center',
|
300 |
+
'margin': '10px 0'
|
301 |
+
},
|
302 |
+
multiple=False,
|
303 |
+
accept='audio/*,video/*'
|
304 |
+
),
|
305 |
+
html.Div(id='upload-status', children='Status: Ready to Upload', className="mt-2"),
|
306 |
+
dbc.Button("Generate Minutes", id="minutes-btn", color="secondary", className="mt-3 w-100", disabled=True),
|
307 |
+
html.H5("Select AI Model", className="mt-4"),
|
308 |
+
dcc.Dropdown(
|
309 |
+
id='model-selection',
|
310 |
+
options=[
|
311 |
+
{'label': 'OpenAI GPT-3.5 Turbo', 'value': 'openai', 'disabled': not openai.api_key},
|
312 |
+
{'label': 'Google Gemini 1.5 Flash', 'value': 'gemini', 'disabled': not genai},
|
313 |
+
{'label': 'Anthropic Claude 3.5 Haiku', 'value': 'anthropic', 'disabled': not anthropic},
|
314 |
+
{'label': 'Grok 3 Mini', 'value': 'grok', 'disabled': not grok_api_key}
|
315 |
+
],
|
316 |
+
value='openai' if openai.api_key else ('gemini' if genai else ('anthropic' if anthropic else ('grok' if grok_api_key else None))),
|
317 |
+
clearable=False,
|
318 |
+
className="mt-2",
|
319 |
+
disabled=not (openai.api_key or genai or anthropic or grok_api_key)
|
320 |
+
),
|
321 |
+
dbc.Button("Delete Session Data", id="delete-btn", color="warning", className="mt-4 w-100", disabled=True),
|
322 |
+
]),
|
323 |
+
style={'height': '80vh', 'overflow-y': 'auto'}
|
324 |
+
), width=12, lg=4),
|
325 |
+
dbc.Col(dbc.Card(
|
326 |
+
dbc.CardBody([
|
327 |
+
dcc.Loading(
|
328 |
+
id="loading",
|
329 |
+
type="default",
|
330 |
+
parent_style={'position': 'relative', 'height': '100%'},
|
331 |
+
style={'position': 'absolute', 'top': '50%', 'left': '50%', 'transform': 'translate(-50%, -50%)', 'zIndex':'1000'},
|
332 |
+
children=[
|
333 |
+
html.Div([
|
334 |
+
html.H4("Output", className="card-title"),
|
335 |
+
html.Div(id="status", children="Status: Idle", className="mb-2"),
|
336 |
+
html.H5("Transcript / Minutes"),
|
337 |
+
html.Div(id="transcript-preview", style={
|
338 |
+
"height": "400px",
|
339 |
+
"overflow-y": "scroll",
|
340 |
+
"border": "1px solid #ccc",
|
341 |
+
"padding": "10px",
|
342 |
+
"white-space": "pre-wrap",
|
343 |
+
"word-wrap": "break-word",
|
344 |
+
"background-color": "#f9f9f9"
|
345 |
+
}),
|
346 |
+
html.H5("Downloads", className="mt-3"),
|
347 |
+
dbc.Row([
|
348 |
+
dbc.Col(dbc.Button("Download Transcript (.docx)", id="download-transcript-btn", color="info", className="w-100 mb-2", disabled=True), width=12, md=4),
|
349 |
+
dbc.Col(dbc.Button("Download Minutes (.docx)", id="download-minutes-btn", color="info", className="w-100 mb-2", disabled=True), width=12, md=4),
|
350 |
+
dbc.Col(dbc.Button("Download Processed Audio", id="download-audio-btn", color="info", className="w-100 mb-2", disabled=True), width=12, md=4),
|
351 |
+
]),
|
352 |
+
])
|
353 |
+
]
|
354 |
+
),
|
355 |
+
html.Div(id="loading-output", style={"height": "0px", "visibility": "hidden"}),
|
356 |
+
]),
|
357 |
+
style={'height': '80vh', 'overflow-y': 'auto', 'position': 'relative'}
|
358 |
+
), width=12, lg=8),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
359 |
])
|
360 |
], fluid=True)
|
361 |
|
|
|
392 |
return final_session_id
|
393 |
|
394 |
@app.callback(
|
395 |
+
[Output("status", "children"),
|
396 |
+
Output("transcript-preview", "children"),
|
397 |
+
Output("minutes-btn", "disabled"),
|
398 |
+
Output("download-transcript-btn", "disabled"),
|
399 |
+
Output("download-minutes-btn", "disabled"),
|
400 |
+
Output("download-audio-btn", "disabled"),
|
401 |
+
Output("delete-btn", "disabled"),
|
402 |
+
Output("loading-output", "children"),
|
403 |
+
Output("upload-status", "children")],
|
404 |
+
[Input('audio-uploader', 'contents'),
|
405 |
+
Input("minutes-btn", "n_clicks"),
|
406 |
+
Input("delete-btn", "n_clicks")],
|
407 |
+
[State("session-id", "data"),
|
408 |
+
State("model-selection", "value"),
|
409 |
+
State("transcript-preview", "children"),
|
410 |
+
State('audio-uploader', 'filename')],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
411 |
prevent_initial_call=True
|
412 |
)
|
413 |
+
def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, selected_model, existing_preview, filename):
|
|
|
|
|
|
|
|
|
|
|
414 |
if not session_id:
|
415 |
logging.warning("Session ID missing in handle_actions.")
|
416 |
+
return "Status: Error - Session ID missing", "", True, True, True, True, True, None, "Status: Error"
|
417 |
ctx = dash.callback_context
|
418 |
triggered_id = ctx.triggered_id if hasattr(ctx, 'triggered_id') else (ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None)
|
419 |
current_transcript = session_data[session_id].get("transcript", "")
|
420 |
current_minutes = session_data[session_id].get("minutes", "")
|
421 |
current_audio_path = session_data[session_id].get("audio_path", None)
|
422 |
original_filename = session_data[session_id].get("original_filename", None)
|
423 |
+
output_text = current_minutes if current_minutes else (current_transcript if current_transcript else "Upload an audio or video file to begin.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
424 |
status_msg = "Status: Idle"
|
425 |
if current_minutes and "Error:" not in current_minutes:
|
426 |
status_msg = "Status: Session restored. Minutes loaded."
|
|
|
|
|
427 |
elif current_transcript and "Error:" not in current_transcript:
|
428 |
status_msg = "Status: Session restored. Transcript loaded. Ready for Minutes Generation."
|
429 |
elif current_audio_path and os.path.exists(current_audio_path):
|
|
|
435 |
dl_minutes_disabled = not bool(current_minutes and "Error:" not in current_minutes)
|
436 |
dl_audio_disabled = not bool(current_audio_path and os.path.exists(current_audio_path))
|
437 |
delete_disabled = not bool(session_data.get(session_id, {}).get("temp_dir"))
|
|
|
|
|
|
|
|
|
438 |
loading_output = None
|
439 |
upload_status_msg = f"Status: {'Loaded: ' + original_filename if original_filename else 'Ready to Upload'}"
|
440 |
start_time = time.time()
|
|
|
441 |
if triggered_id == 'audio-uploader' and upload_contents is not None and filename is not None:
|
442 |
logging.info(f"File uploaded for session {session_id}, filename: {filename}")
|
443 |
session_data[session_id]["original_filename"] = filename
|
|
|
444 |
upload_status_msg = f"Status: Processing Uploaded File ({filename})..."
|
445 |
status_msg = "Status: Processing Upload..."
|
446 |
loading_output = "Processing Upload..."
|
|
|
455 |
session_data[session_id]["transcript"] = None
|
456 |
session_data[session_id]["minutes"] = None
|
457 |
session_data[session_id]["original_filename"] = None
|
|
|
|
|
|
|
458 |
minutes_disabled = True
|
459 |
dl_transcript_disabled = True
|
460 |
dl_minutes_disabled = True
|
461 |
dl_audio_disabled = True
|
462 |
delete_disabled = False
|
463 |
+
return status_msg, output_text, minutes_disabled, dl_transcript_disabled, dl_minutes_disabled, dl_audio_disabled, delete_disabled, None, upload_status_msg
|
|
|
|
|
|
|
464 |
safe_upload_filename = f"uploaded_file{f_ext}"
|
465 |
upload_file_path = os.path.join(session_dir, safe_upload_filename)
|
466 |
saved_upload_path = save_base64_data(upload_contents, upload_file_path)
|
|
|
492 |
dl_minutes_disabled = True
|
493 |
dl_audio_disabled = True
|
494 |
delete_disabled = False
|
495 |
+
return status_msg, output_text, minutes_disabled, dl_transcript_disabled, dl_minutes_disabled, dl_audio_disabled, delete_disabled, None, upload_status_msg
|
|
|
|
|
|
|
|
|
|
|
|
|
496 |
else:
|
497 |
audio_path_for_transcription = saved_upload_path
|
498 |
session_data[session_id]["audio_path"] = saved_upload_path
|
|
|
505 |
transcript_text = transcribe_audio(audio_path_for_transcription)
|
506 |
session_data[session_id]["transcript"] = transcript_text
|
507 |
session_data[session_id]["minutes"] = None
|
|
|
|
|
|
|
508 |
if "Error:" in transcript_text:
|
509 |
status_msg = f"Status: Transcription Failed - {transcript_text}"
|
510 |
output_text = transcript_text
|
|
|
513 |
dl_minutes_disabled = True
|
514 |
delete_disabled = False
|
515 |
upload_status_msg = f"Status: Transcription Failed. ({filename})"
|
|
|
|
|
|
|
516 |
else:
|
517 |
status_msg = "Status: Transcription Complete. Ready for Minutes Generation."
|
518 |
output_text = transcript_text
|
|
|
521 |
dl_minutes_disabled = True
|
522 |
delete_disabled = False
|
523 |
upload_status_msg = f"Status: Processed & Transcribed: {filename}"
|
|
|
|
|
|
|
524 |
processing_time = time.time() - start_time
|
525 |
logging.info(f"File processing and transcription took {processing_time:.2f} seconds for session {session_id}")
|
526 |
else:
|
|
|
534 |
dl_minutes_disabled = True
|
535 |
dl_audio_disabled = True
|
536 |
delete_disabled = False
|
|
|
|
|
|
|
|
|
|
|
|
|
537 |
elif triggered_id == "minutes-btn" and minutes_clicks:
|
538 |
logging.info(f"Generate Minutes button clicked for session {session_id}")
|
539 |
+
current_transcript = session_data[session_id].get("transcript", "")
|
540 |
+
if current_transcript and "Error:" not in current_transcript:
|
|
|
|
|
|
|
|
|
|
|
|
|
541 |
status_msg = f"Status: Generating Minutes ({selected_model})..."
|
542 |
loading_output = "Generating Minutes..."
|
543 |
+
minutes_text = generate_minutes_ai(current_transcript, selected_model, session_id)
|
544 |
session_data[session_id]["minutes"] = minutes_text
|
545 |
output_text = minutes_text
|
|
|
546 |
if "Error:" in minutes_text:
|
547 |
status_msg = f"Status: Minutes Generation Failed - {minutes_text}"
|
548 |
dl_minutes_disabled = True
|
|
|
556 |
dl_audio_disabled = not bool(session_data.get(session_id, {}).get("audio_path") and os.path.exists(session_data.get(session_id, {}).get("audio_path", "")))
|
557 |
delete_disabled = False
|
558 |
upload_status_msg = f"Status: Processed & Transcribed: {session_data[session_id].get('original_filename', 'File')}"
|
|
|
|
|
|
|
559 |
else:
|
560 |
status_msg = "Status: Cannot generate minutes - No valid transcript available."
|
561 |
output_text = existing_preview
|
562 |
minutes_disabled = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
563 |
elif triggered_id == "delete-btn" and delete_clicks:
|
564 |
logging.info(f"Delete button clicked for session {session_id}")
|
565 |
cleanup_session(session_id)
|
|
|
570 |
dl_minutes_disabled = True
|
571 |
dl_audio_disabled = True
|
572 |
delete_disabled = True
|
|
|
|
|
|
|
573 |
upload_status_msg = "Status: Ready to Upload"
|
|
|
574 |
else:
|
575 |
+
loaded_audio_path = session_data.get(session_id, {}).get("audio_path")
|
576 |
+
loaded_transcript = session_data.get(session_id, {}).get("transcript")
|
577 |
+
loaded_minutes = session_data.get(session_id, {}).get("minutes")
|
578 |
+
temp_dir_exists = bool(session_data.get(session_id, {}).get("temp_dir"))
|
579 |
+
loaded_original_filename = session_data.get(session_id, {}).get("original_filename")
|
580 |
+
dl_audio_disabled = not (loaded_audio_path and os.path.exists(loaded_audio_path))
|
581 |
+
minutes_disabled = not (loaded_transcript and "Error:" not in loaded_transcript)
|
582 |
+
dl_transcript_disabled = not (loaded_transcript and "Error:" not in loaded_transcript)
|
583 |
+
dl_minutes_disabled = not (loaded_minutes and "Error:" not in loaded_minutes)
|
584 |
+
delete_disabled = not (loaded_audio_path or loaded_transcript or loaded_minutes or temp_dir_exists or loaded_original_filename)
|
585 |
+
if loaded_original_filename and dl_audio_disabled and not loaded_transcript:
|
586 |
+
upload_status_msg = f"Status: Error processing {loaded_original_filename}?"
|
587 |
+
elif loaded_audio_path and os.path.exists(loaded_audio_path):
|
588 |
+
upload_status_msg = f"Status: Processed audio loaded ({loaded_original_filename or 'previous file'})."
|
589 |
+
else:
|
590 |
+
upload_status_msg = "Status: Ready to Upload"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
591 |
return (
|
592 |
status_msg,
|
593 |
output_text,
|
|
|
597 |
dl_audio_disabled,
|
598 |
delete_disabled,
|
599 |
loading_output,
|
600 |
+
upload_status_msg
|
|
|
|
|
|
|
|
|
601 |
)
|
602 |
|
603 |
@app.callback(
|
|
|
674 |
logging.error(f"Processed audio file not found at path {audio_path} for session {session_id}")
|
675 |
return None
|
676 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
677 |
if __name__ == '__main__':
|
678 |
print("Starting the Dash application...")
|
679 |
+
app.run(debug=False, host='0.0.0.0', port=7860)
|
680 |
print("Dash application has finished running.")
|