bluenevus commited on
Commit
cdffd44
·
1 Parent(s): 62cbd18

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +547 -123
app.py CHANGED
@@ -54,7 +54,17 @@ 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: {"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,6 +163,186 @@ def transcribe_audio(file_path):
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,87 +465,112 @@ app.layout = dbc.Container([
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,38 +607,81 @@ def manage_session_id(existing_session_id):
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,12 +693,18 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,12 +719,18 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,7 +762,13 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,6 +781,9 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,6 +792,9 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,6 +803,9 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,15 +819,28 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,10 +854,77 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,24 +935,49 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
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,7 +987,11 @@ def handle_actions(upload_contents, minutes_clicks, delete_clicks, session_id, s
597
  dl_audio_disabled,
598
  delete_disabled,
599
  loading_output,
600
- upload_status_msg
 
 
 
 
601
  )
602
 
603
  @app.callback(
@@ -674,7 +1068,37 @@ def download_audio_file(n_clicks, session_id):
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.")
 
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
  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
  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
+ dbc.Card(
472
+ dbc.CardBody([
473
+ html.H4("Controls", className="card-title"),
474
+ html.Div("Upload meeting audio or video file:"),
475
+ dcc.Upload(
476
+ id='audio-uploader',
477
+ children=html.Div([
478
+ 'Drag and Drop or ',
479
+ html.A('Select Audio/Video File')
480
+ ]),
481
+ style={
482
+ 'width': '100%',
483
+ 'height': '60px',
484
+ 'lineHeight': '60px',
485
+ 'borderWidth': '1px',
486
+ 'borderStyle': 'dashed',
487
+ 'borderRadius': '5px',
488
+ 'textAlign': 'center',
489
+ 'margin': '10px 0'
490
+ },
491
+ multiple=False,
492
+ accept='audio/*,video/*'
493
+ ),
494
+ html.Div(id='upload-status', children='Status: Ready to Upload', className="mt-2"),
495
+ html.Div(id='uploaded-filename', style={'fontWeight': 'bold', 'marginBottom': '8px'}),
496
+ html.Hr(),
497
+ html.H5("View Output", className="mt-2"),
498
+ dbc.Button("View Original Transcript", id="nav-original-btn", color="tertiary", className="mb-2 w-100"),
499
+ dbc.Button("View Diarized Transcript", id="nav-diarized-btn", color="tertiary", className="mb-2 w-100"),
500
+ dbc.Button("View Minutes", id="nav-minutes-btn", color="tertiary", className="mb-2 w-100"),
501
+ html.Hr(),
502
+ html.H5("Select AI Model", className="mt-2"),
503
+ dcc.Dropdown(
504
+ id='model-selection',
505
+ options=[
506
+ {'label': 'OpenAI GPT-3.5 Turbo', 'value': 'openai', 'disabled': not openai.api_key},
507
+ {'label': 'Google Gemini 1.5 Flash', 'value': 'gemini', 'disabled': not genai},
508
+ {'label': 'Anthropic Claude 3.5 Haiku', 'value': 'anthropic', 'disabled': not anthropic},
509
+ {'label': 'Grok 3 Mini', 'value': 'grok', 'disabled': not grok_api_key}
510
+ ],
511
+ value='openai' if openai.api_key else (
512
+ 'gemini' if genai else (
513
+ 'anthropic' if anthropic else (
514
+ 'grok' if grok_api_key else None
515
+ )
516
+ )
517
+ ),
518
+ clearable=False,
519
+ className="mt-2",
520
+ disabled=not (openai.api_key or genai or anthropic or grok_api_key)
521
+ ),
522
+ dbc.Checkbox(
523
+ id="diarize-checkbox",
524
+ className="mt-3",
525
+ value=False,
526
+ label="Diarize Speakers (AI)",
527
+ style={"fontWeight": "bold"}
528
+ ),
529
+ dbc.Button("Diarize Transcript (Simple)", id="diarize-btn", color="primary", className="mt-2 w-100", disabled=True),
530
+ dbc.Button("Diarize Transcript (Advanced)", id="advanced-diarize-btn", color="secondary", className="mt-2 w-100", disabled=True),
531
+ dbc.Button("Download Diarized Transcript (.docx)", id="download-diarized-btn", color="info", className="mt-2 w-100", disabled=True),
532
+ dbc.Button("Generate Minutes", id="minutes-btn", color="secondary", className="mt-4 w-100", disabled=True),
533
+ dbc.Button("Delete Session Data", id="delete-btn", color="warning", className="mt-4 w-100", disabled=True),
534
+ ]),
535
+ style={'height': '80vh', 'overflow-y': 'auto'}
536
+ ), width=12, lg=4
537
+ ),
538
+ dbc.Col(
539
+ dbc.Card(
540
+ dbc.CardBody([
541
+ dcc.Loading(
542
+ id="loading",
543
+ type="default",
544
+ parent_style={'position': 'relative', 'height': '100%'},
545
+ style={'position': 'absolute', 'top': '50%', 'left': '50%', 'transform': 'translate(-50%, -50%)', 'zIndex':'1000'},
546
+ children=[
547
+ html.Div([
548
+ html.H4("Output", className="card-title"),
549
+ html.Div(id="status", children="Status: Idle", className="mb-2"),
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
  return final_session_id
608
 
609
  @app.callback(
610
+ [
611
+ Output("status", "children"),
612
+ Output("transcript-preview", "children"),
613
+ Output("minutes-btn", "disabled"),
614
+ Output("download-transcript-btn", "disabled"),
615
+ Output("download-minutes-btn", "disabled"),
616
+ Output("download-audio-btn", "disabled"),
617
+ Output("delete-btn", "disabled"),
618
+ Output("loading-output", "children"),
619
+ Output("upload-status", "children"),
620
+ Output("diarize-btn", "disabled"),
621
+ Output("download-diarized-btn", "disabled"),
622
+ Output("uploaded-filename", "children"),
623
+ Output("advanced-diarize-btn", "disabled"),
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", True, True, "", True
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
+ diarized_transcript = session_data[session_id].get("diarized_transcript", None)
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
  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
  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
+ diarize_disabled = True
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
  dl_minutes_disabled = True
763
  dl_audio_disabled = True
764
  delete_disabled = False
765
+ diarize_disabled = True
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
  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
  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
  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
  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
+ transcript_to_use = None
831
+ if preview_mode == "diarized":
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(transcript_to_use, selected_model, session_id)
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
  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
  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
+ loaded_audio_path = session_data.get(session_id, {}).get("audio_path")
945
+ loaded_transcript = session_data.get(session_id, {}).get("transcript")
946
+ loaded_minutes = session_data.get(session_id, {}).get("minutes")
947
+ loaded_diarized = session_data.get(session_id, {}).get("diarized_transcript")
948
+ loaded_adv_diarized = session_data.get(session_id, {}).get("advanced_diarized_transcript")
949
+ temp_dir_exists = bool(session_data.get(session_id, {}).get("temp_dir"))
950
+ loaded_original_filename = session_data.get(session_id, {}).get("original_filename")
951
+ dl_audio_disabled = not (loaded_audio_path and os.path.exists(loaded_audio_path))
952
+ minutes_disabled = not (loaded_transcript and "Error:" not in loaded_transcript)
953
+ dl_transcript_disabled = not (loaded_transcript and "Error:" not in loaded_transcript)
954
+ dl_minutes_disabled = not (loaded_minutes and "Error:" not in loaded_minutes)
955
+ diarize_disabled = not (loaded_transcript and "Error:" not in loaded_transcript)
956
+ advanced_diarize_disabled = diarize_disabled
957
+ dl_diarized_disabled = not ((loaded_adv_diarized or loaded_diarized) and "Error:" not in (loaded_adv_diarized or loaded_diarized or ""))
958
+ delete_disabled = not (loaded_audio_path or loaded_transcript or loaded_minutes or loaded_diarized or temp_dir_exists or loaded_original_filename or loaded_adv_diarized)
959
+ if loaded_original_filename and dl_audio_disabled and not loaded_transcript:
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
  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
  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=True, host='0.0.0.0', port=7860)
1104
  print("Dash application has finished running.")