| | import gradio as gr
|
| | import time
|
| | import json
|
| | import os
|
| | import subprocess
|
| | from datetime import datetime, timedelta
|
| | from typing import List, Tuple, Optional
|
| | from backend import (
|
| | ALLOWED_LANGS, AUDIO_FORMATS, transcription_manager,
|
| | allowed_file, User
|
| | )
|
| | from ai_summary import ai_summary_manager
|
| |
|
| | def format_status(status):
|
| | """Convert status to user-friendly format"""
|
| | status_map = {
|
| | 'pending': 'β³ Queued',
|
| | 'processing': 'π Processing',
|
| | 'completed': 'β
Done',
|
| | 'failed': 'β Failed'
|
| | }
|
| | return status_map.get(status, status)
|
| |
|
| | def format_processing_time(created_at, completed_at=None):
|
| | """Calculate and format processing time"""
|
| | try:
|
| | start_time = datetime.fromisoformat(created_at)
|
| | if completed_at:
|
| | end_time = datetime.fromisoformat(completed_at)
|
| | duration = end_time - start_time
|
| | else:
|
| | duration = datetime.now() - start_time
|
| |
|
| | total_seconds = int(duration.total_seconds())
|
| | if total_seconds < 60:
|
| | return f"{total_seconds}s"
|
| | elif total_seconds < 3600:
|
| | minutes = total_seconds // 60
|
| | seconds = total_seconds % 60
|
| | return f"{minutes}m {seconds}s"
|
| | else:
|
| | hours = total_seconds // 3600
|
| | minutes = (total_seconds % 3600) // 60
|
| | return f"{hours}h {minutes}m"
|
| | except:
|
| | return "Unknown"
|
| |
|
| | def get_user_stats_display(user: User):
|
| | """Get comprehensive user statistics for display"""
|
| | if not user:
|
| | return "π€ Please log in to view statistics"
|
| |
|
| | try:
|
| |
|
| | transcript_stats = transcription_manager.get_user_stats(user.user_id)
|
| |
|
| |
|
| | summary_stats = transcription_manager.get_user_summary_stats(user.user_id)
|
| |
|
| | total_transcripts = transcript_stats.get('total_jobs', 0)
|
| | total_summaries = summary_stats.get('total_jobs', 0)
|
| |
|
| | stats_text = f"π€ {user.username} | ποΈ Transcripts: {total_transcripts} | π€ AI Summaries: {total_summaries}"
|
| |
|
| |
|
| | processing_transcripts = transcript_stats.get('by_status', {}).get('processing', 0)
|
| | processing_summaries = summary_stats.get('by_status', {}).get('processing', 0)
|
| |
|
| | if processing_transcripts > 0:
|
| | stats_text += f" | π Transcribing: {processing_transcripts}"
|
| | if processing_summaries > 0:
|
| | stats_text += f" | π Summarizing: {processing_summaries}"
|
| |
|
| | return stats_text
|
| |
|
| | except Exception as e:
|
| | return f"π€ {user.username} | Stats error: {str(e)}"
|
| |
|
| |
|
| | def register_user(email, username, password, confirm_password, gdpr_consent, data_retention_consent, marketing_consent):
|
| | """Register new user account"""
|
| | try:
|
| | print(f"π Registration attempt for: {username} ({email})")
|
| |
|
| |
|
| | if not email or not username or not password:
|
| | return "β All fields are required", gr.update(visible=False)
|
| |
|
| | if password != confirm_password:
|
| | return "β Passwords do not match", gr.update(visible=False)
|
| |
|
| | if not gdpr_consent:
|
| | return "β GDPR consent is required to create an account", gr.update(visible=False)
|
| |
|
| | if not data_retention_consent:
|
| | return "β Data retention agreement is required", gr.update(visible=False)
|
| |
|
| |
|
| | success, message, user_id = transcription_manager.register_user(
|
| | email, username, password, gdpr_consent, data_retention_consent, marketing_consent
|
| | )
|
| |
|
| | print(f"π Registration result: success={success}, message={message}")
|
| |
|
| | if success:
|
| | print(f"β
User registered successfully: {username}")
|
| | return f"β
{message}! Please log in with your credentials.", gr.update(visible=True)
|
| | else:
|
| | print(f"β Registration failed: {message}")
|
| | return f"β {message}", gr.update(visible=False)
|
| |
|
| | except Exception as e:
|
| | print(f"β Registration error: {str(e)}")
|
| | return f"β Registration error: {str(e)}", gr.update(visible=False)
|
| |
|
| | def login_user(login, password):
|
| | """Login user"""
|
| | try:
|
| | print(f"π Login attempt for: {login}")
|
| |
|
| | if not login or not password:
|
| | return "β Please enter both username/email and password", None, gr.update(visible=True), gr.update(visible=False), "π€ Please log in to view your statistics..."
|
| |
|
| | success, message, user = transcription_manager.login_user(login, password)
|
| | print(f"π Login result: success={success}, message={message}")
|
| |
|
| | if success and user:
|
| | print(f"β
User logged in successfully: {user.username}")
|
| | stats_display = get_user_stats_display(user)
|
| | return f"β
Welcome back, {user.username}!", user, gr.update(visible=False), gr.update(visible=True), stats_display
|
| | else:
|
| | print(f"β Login failed: {message}")
|
| | return f"β {message}", None, gr.update(visible=True), gr.update(visible=False), "π€ Please log in to view your statistics..."
|
| |
|
| | except Exception as e:
|
| | print(f"β Login error: {str(e)}")
|
| | return f"β Login error: {str(e)}", None, gr.update(visible=True), gr.update(visible=False), "π€ Please log in to view your statistics..."
|
| |
|
| | def logout_user():
|
| | """Logout user"""
|
| | print("π User logged out")
|
| | return None, "π You have been logged out. Please log in to continue.", gr.update(visible=True), gr.update(visible=False), "π€ Please log in to view your statistics..."
|
| |
|
| |
|
| | def submit_transcription(file, language, audio_format, diarization_enabled, speakers,
|
| | profanity, punctuation, timestamps, lexical, user):
|
| | """Submit transcription job - requires authenticated user"""
|
| | if not user:
|
| | return (
|
| | "β Please log in to submit transcriptions",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | if file is None:
|
| | return (
|
| | "Please upload an audio or video file first.",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | try:
|
| |
|
| | try:
|
| | if isinstance(file, str):
|
| | if os.path.exists(file):
|
| | with open(file, 'rb') as f:
|
| | file_bytes = f.read()
|
| | original_filename = os.path.basename(file)
|
| | else:
|
| | return (
|
| | "File not found. Please try uploading again.",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| | else:
|
| | file_path = str(file)
|
| | if os.path.exists(file_path):
|
| | with open(file_path, 'rb') as f:
|
| | file_bytes = f.read()
|
| | original_filename = os.path.basename(file_path)
|
| | else:
|
| | return (
|
| | "Unable to process file. Please try again.",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| | except Exception as e:
|
| | return (
|
| | f"Error reading file: {str(e)}",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | file_extension = original_filename.split('.')[-1].lower() if '.' in original_filename else ""
|
| | supported_extensions = set(AUDIO_FORMATS) | {
|
| | 'mp4', 'mov', 'avi', 'mkv', 'webm', 'm4a', '3gp', 'f4v',
|
| | 'wmv', 'asf', 'rm', 'rmvb', 'flv', 'mpg', 'mpeg', 'mts', 'vob'
|
| | }
|
| |
|
| | if file_extension not in supported_extensions and file_extension != "":
|
| | return (
|
| | f"Unsupported file format: .{file_extension}",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | if len(file_bytes) > 500 * 1024 * 1024:
|
| | return (
|
| | "File too large. Please upload files smaller than 500MB.",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | settings = {
|
| | 'audio_format': audio_format,
|
| | 'diarization_enabled': diarization_enabled,
|
| | 'speakers': speakers,
|
| | 'profanity': profanity,
|
| | 'punctuation': punctuation,
|
| | 'timestamps': timestamps,
|
| | 'lexical': lexical
|
| | }
|
| |
|
| |
|
| | job_id = transcription_manager.submit_transcription(
|
| | file_bytes, original_filename, user.user_id, language, settings
|
| | )
|
| |
|
| |
|
| | job_state = {
|
| | 'current_job_id': job_id,
|
| | 'start_time': datetime.now().isoformat(),
|
| | 'auto_refresh_active': True,
|
| | 'last_status': 'pending'
|
| | }
|
| |
|
| |
|
| | stats_display = get_user_stats_display(user)
|
| |
|
| | return (
|
| | f"π Transcription started for: {original_filename}\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Job ID: {job_id}",
|
| | job_state,
|
| | gr.update(visible=True, value="π Auto-refresh active"),
|
| | stats_display
|
| | )
|
| |
|
| | except Exception as e:
|
| | print(f"β Error submitting transcription: {str(e)}")
|
| | return (
|
| | f"Error: {str(e)}",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | def check_current_job_status(job_state, user):
|
| | """Check status of current job with improved transcript handling"""
|
| | if not user:
|
| | return (
|
| | "β Please log in to check status",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | if not job_state or 'current_job_id' not in job_state:
|
| | return (
|
| | "No active job",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | job_id = job_state['current_job_id']
|
| |
|
| | try:
|
| | job = transcription_manager.get_job_status(job_id)
|
| | if not job or job.user_id != user.user_id:
|
| | return (
|
| | "Job not found or access denied",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | processing_time = format_processing_time(job.created_at, job.completed_at)
|
| |
|
| |
|
| | last_status = job_state.get('last_status', '')
|
| | if job.status != last_status:
|
| | print(f"π [{user.username}] Job status change: {last_status} β {job.status} ({job.original_filename})")
|
| | job_state['last_status'] = job.status
|
| |
|
| |
|
| | stats_display = get_user_stats_display(user)
|
| |
|
| |
|
| | if job.status == 'completed' and job.transcript_text and job.transcript_text.strip():
|
| |
|
| | job_state['auto_refresh_active'] = False
|
| |
|
| |
|
| | try:
|
| | transcript_file = create_transcript_file(job.transcript_text, job_id)
|
| | print(f"β
[{user.username}] Transcription ready: {len(job.transcript_text)} characters")
|
| | except Exception as e:
|
| | print(f"β οΈ [{user.username}] Error creating transcript file: {str(e)}")
|
| | transcript_file = None
|
| |
|
| | return (
|
| | f"β
Transcription completed in {processing_time}",
|
| | job.transcript_text,
|
| | gr.update(visible=True, value=transcript_file) if transcript_file else gr.update(visible=False),
|
| | f"Processed: {job.original_filename}",
|
| | gr.update(visible=False),
|
| | stats_display
|
| | )
|
| |
|
| | elif job.status == 'failed':
|
| |
|
| | job_state['auto_refresh_active'] = False
|
| | error_msg = job.error_message[:100] + "..." if job.error_message and len(job.error_message) > 100 else job.error_message or "Unknown error"
|
| | return (
|
| | f"β Transcription failed after {processing_time}",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Error: {error_msg}",
|
| | gr.update(visible=False),
|
| | stats_display
|
| | )
|
| |
|
| | elif job.status == 'processing':
|
| |
|
| | auto_refresh_active = job_state.get('auto_refresh_active', False)
|
| | return (
|
| | f"π Processing... ({processing_time} elapsed)\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Converting and analyzing: {job.original_filename}",
|
| | gr.update(visible=True, value="π Auto-refresh active") if auto_refresh_active else gr.update(visible=False),
|
| | stats_display
|
| | )
|
| |
|
| | elif job.status == 'completed' and (not job.transcript_text or not job.transcript_text.strip()):
|
| |
|
| | auto_refresh_active = job_state.get('auto_refresh_active', False)
|
| | return (
|
| | f"π Finalizing transcript... ({processing_time} elapsed)\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Retrieving results: {job.original_filename}",
|
| | gr.update(visible=True, value="π Auto-refresh active") if auto_refresh_active else gr.update(visible=False),
|
| | stats_display
|
| | )
|
| |
|
| | else:
|
| |
|
| | auto_refresh_active = job_state.get('auto_refresh_active', False)
|
| | return (
|
| | f"β³ Queued for processing... ({processing_time} waiting)\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Waiting: {job.original_filename}",
|
| | gr.update(visible=True, value="π Auto-refresh active") if auto_refresh_active else gr.update(visible=False),
|
| | stats_display
|
| | )
|
| |
|
| | except Exception as e:
|
| | print(f"β Error checking job status: {str(e)}")
|
| | return (
|
| | f"Error checking status: {str(e)}",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | def get_available_transcripts(user):
|
| | """Get list of available transcripts for AI summarization"""
|
| | if not user:
|
| | return gr.update(choices=[], value=[])
|
| |
|
| | try:
|
| |
|
| | completed_jobs = transcription_manager.get_user_history(user.user_id, limit=50)
|
| | completed_transcripts = [
|
| | job for job in completed_jobs
|
| | if job.status == 'completed' and job.transcript_text
|
| | ]
|
| |
|
| |
|
| | choices = []
|
| | for job in completed_transcripts[:20]:
|
| | label = f"{job.original_filename} ({job.created_at[:16]})"
|
| | choices.append((label, job.job_id))
|
| |
|
| | return gr.update(choices=choices, value=[])
|
| |
|
| | except Exception as e:
|
| | print(f"β Error getting available transcripts: {str(e)}")
|
| | return gr.update(choices=[], value=[])
|
| |
|
| | def submit_ai_summary_enhanced(existing_transcripts, new_audio_video_file, document_image_files,
|
| | ai_instructions, summary_format, output_language, focus_areas,
|
| | include_timestamps, include_action_items, user):
|
| | """Enhanced AI summary submission with immediate transcript processing"""
|
| | if not user:
|
| | return (
|
| | "β Please log in to generate AI summaries",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | has_existing_transcripts = existing_transcripts and len(existing_transcripts) > 0
|
| | has_new_audio_video = new_audio_video_file is not None
|
| | has_document_images = document_image_files and len(document_image_files) > 0
|
| |
|
| | if not has_existing_transcripts and not has_new_audio_video and not has_document_images:
|
| | return (
|
| | "β Please provide content: select existing transcripts, upload audio/video file, or upload documents/images",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | if not ai_instructions.strip():
|
| | return (
|
| | "β Please provide AI instructions for the summary",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | try:
|
| |
|
| | transcription_job_id = None
|
| | if has_new_audio_video:
|
| |
|
| | try:
|
| |
|
| | if isinstance(new_audio_video_file, str):
|
| | file_path = new_audio_video_file
|
| | else:
|
| | file_path = str(new_audio_video_file)
|
| |
|
| | with open(file_path, 'rb') as f:
|
| | file_bytes = f.read()
|
| |
|
| | original_filename = os.path.basename(file_path)
|
| |
|
| |
|
| | transcription_settings = {
|
| | 'audio_format': 'wav',
|
| | 'diarization_enabled': True,
|
| | 'speakers': 5,
|
| | 'profanity': 'masked',
|
| | 'punctuation': 'automatic',
|
| | 'timestamps': True,
|
| | 'lexical': False
|
| | }
|
| |
|
| |
|
| | transcription_job_id = transcription_manager.submit_transcription(
|
| | file_bytes,
|
| | original_filename,
|
| | user.user_id,
|
| | "th-TH",
|
| | transcription_settings
|
| | )
|
| |
|
| | print(f"ποΈ [{user.username}] Audio/video submitted for transcription: {transcription_job_id[:8]}...")
|
| |
|
| |
|
| | summary_job_state = {
|
| | 'waiting_for_transcription': True,
|
| | 'transcription_job_id': transcription_job_id,
|
| | 'start_time': datetime.now().isoformat(),
|
| | 'auto_refresh_active': True,
|
| | 'last_status': 'waiting_for_transcription',
|
| | 'ai_instructions': ai_instructions,
|
| | 'summary_format': summary_format,
|
| | 'output_language': output_language,
|
| | 'focus_areas': focus_areas,
|
| | 'include_timestamps': include_timestamps,
|
| | 'include_action_items': include_action_items,
|
| | 'existing_transcripts': existing_transcripts if existing_transcripts else [],
|
| | 'document_image_files': document_image_files if document_image_files else [],
|
| | 'user_id': user.user_id
|
| | }
|
| |
|
| |
|
| | stats_display = get_user_stats_display(user)
|
| |
|
| | return (
|
| | f"ποΈ Audio/video submitted for transcription\nβ³ AI Summary will start automatically when transcription completes\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Transcription Job: {transcription_job_id[:8]}... β Will auto-trigger AI Summary",
|
| | summary_job_state,
|
| | gr.update(visible=True, value="π Waiting for transcription"),
|
| | stats_display
|
| | )
|
| |
|
| | except Exception as e:
|
| | print(f"β Error submitting audio/video for transcription: {str(e)}")
|
| | return (
|
| | f"β Error processing audio/video file: {str(e)}",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | else:
|
| | transcript_ids = existing_transcripts if existing_transcripts else []
|
| | document_files = document_image_files if document_image_files else []
|
| |
|
| |
|
| | if has_existing_transcripts and not has_document_images:
|
| | content_mode = "Existing Transcripts"
|
| | elif has_document_images and not has_existing_transcripts:
|
| | content_mode = "Text Documents"
|
| | else:
|
| | content_mode = "Mixed Content"
|
| |
|
| |
|
| | settings = {
|
| | 'content_mode': content_mode,
|
| | 'format': summary_format,
|
| | 'output_language': output_language,
|
| | 'focus_areas': focus_areas,
|
| | 'include_timestamps': include_timestamps,
|
| | 'include_action_items': include_action_items,
|
| | 'language': "th-TH"
|
| | }
|
| |
|
| |
|
| | job_id = ai_summary_manager.submit_summary_job_enhanced(
|
| | user_id=user.user_id,
|
| | content_mode=content_mode,
|
| | summary_type=summary_format,
|
| | user_prompt=ai_instructions,
|
| | existing_transcript_ids=transcript_ids,
|
| | audio_video_files=[],
|
| | document_files=document_files,
|
| | settings=settings
|
| | )
|
| |
|
| |
|
| | summary_job_state = {
|
| | 'current_summary_job_id': job_id,
|
| | 'start_time': datetime.now().isoformat(),
|
| | 'auto_refresh_active': True,
|
| | 'last_status': 'pending'
|
| | }
|
| |
|
| |
|
| | stats_display = get_user_stats_display(user)
|
| |
|
| |
|
| | source_parts = []
|
| | if has_existing_transcripts:
|
| | source_parts.append(f"{len(transcript_ids)} existing transcripts")
|
| | if has_document_images:
|
| | source_parts.append(f"{len(document_files)} document/image files")
|
| |
|
| | source_info = " + ".join(source_parts)
|
| |
|
| | return (
|
| | f"π€ AI Summary started with {source_info}\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"AI Job ID: {job_id}",
|
| | summary_job_state,
|
| | gr.update(visible=True, value="π AI Auto-refresh active"),
|
| | stats_display
|
| | )
|
| |
|
| | except Exception as e:
|
| | print(f"β Error submitting enhanced AI summary: {str(e)}")
|
| | return (
|
| | f"β Error: {str(e)}",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | {},
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | def check_ai_summary_status(summary_job_state, user):
|
| | """Check status of AI summary job with auto-trigger logic for completed transcriptions"""
|
| | if not user:
|
| | return (
|
| | "β Please log in to check AI summary status",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | if not summary_job_state:
|
| | return (
|
| | "No active AI summary job",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | try:
|
| |
|
| | if summary_job_state.get('waiting_for_transcription'):
|
| | transcription_job_id = summary_job_state.get('transcription_job_id')
|
| | if not transcription_job_id:
|
| | return (
|
| | "β Error: Missing transcription job ID",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | transcription_job = transcription_manager.get_job_status(transcription_job_id)
|
| | if not transcription_job:
|
| | return (
|
| | "β Transcription job not found",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | processing_time = format_processing_time(summary_job_state['start_time'])
|
| |
|
| | if transcription_job.status == 'pending':
|
| | return (
|
| | f"β³ Transcription queued... ({processing_time} elapsed)\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Transcription: {transcription_job.original_filename}",
|
| | gr.update(visible=True, value="π Waiting for transcription"),
|
| | get_user_stats_display(user)
|
| | )
|
| |
|
| | elif transcription_job.status == 'processing':
|
| | transcription_time = format_processing_time(transcription_job.created_at)
|
| | return (
|
| | f"ποΈ Transcribing... ({transcription_time} transcribing, {processing_time} total)\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Transcribing: {transcription_job.original_filename}",
|
| | gr.update(visible=True, value="π Transcription in progress"),
|
| | get_user_stats_display(user)
|
| | )
|
| |
|
| | elif transcription_job.status == 'failed':
|
| | summary_job_state['auto_refresh_active'] = False
|
| | return (
|
| | f"β Transcription failed - Cannot proceed\nError: {transcription_job.error_message or 'Unknown error'}",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Failed: {transcription_job.original_filename}",
|
| | gr.update(visible=False),
|
| | get_user_stats_display(user)
|
| | )
|
| |
|
| | elif transcription_job.status == 'completed':
|
| | if not transcription_job.transcript_text or not transcription_job.transcript_text.strip():
|
| | return (
|
| | f"π Transcription completed, retrieving text... ({processing_time} elapsed)\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Getting transcript: {transcription_job.original_filename}",
|
| | gr.update(visible=True, value="π Getting transcript"),
|
| | get_user_stats_display(user)
|
| | )
|
| |
|
| |
|
| | print(f"β
Transcription completed, triggering AI summary immediately...")
|
| |
|
| | try:
|
| |
|
| | transcript_ids = summary_job_state.get('existing_transcripts', [])
|
| | transcript_ids.append(transcription_job_id)
|
| |
|
| |
|
| | settings = {
|
| | 'content_mode': "New Audio/Video Files",
|
| | 'format': summary_job_state.get('summary_format', 'ΰΈΰΈΰΈͺΰΈ£ΰΈΈΰΈΰΈΰΈΉΰΉΰΈΰΈ£ΰΈ΄ΰΈ«ΰΈ²ΰΈ£'),
|
| | 'output_language': summary_job_state.get('output_language', 'Thai'),
|
| | 'focus_areas': summary_job_state.get('focus_areas', ''),
|
| | 'include_timestamps': summary_job_state.get('include_timestamps', True),
|
| | 'include_action_items': summary_job_state.get('include_action_items', True),
|
| | 'language': "th-TH"
|
| | }
|
| |
|
| |
|
| | job_id = ai_summary_manager.submit_summary_job_enhanced(
|
| | user_id=summary_job_state['user_id'],
|
| | content_mode="New Audio/Video Files",
|
| | summary_type=summary_job_state.get('summary_format', 'ΰΈΰΈΰΈͺΰΈ£ΰΈΈΰΈΰΈΰΈΉΰΉΰΈΰΈ£ΰΈ΄ΰΈ«ΰΈ²ΰΈ£'),
|
| | user_prompt=summary_job_state.get('ai_instructions', ''),
|
| | existing_transcript_ids=transcript_ids,
|
| | audio_video_files=[],
|
| | document_files=summary_job_state.get('document_image_files', []),
|
| | settings=settings
|
| | )
|
| |
|
| | print(f"π€ AI Summary job created immediately: {job_id}")
|
| |
|
| |
|
| | summary_job_state.update({
|
| | 'waiting_for_transcription': False,
|
| | 'current_summary_job_id': job_id,
|
| | 'transcription_completed_at': datetime.now().isoformat(),
|
| | 'last_status': 'ai_started'
|
| | })
|
| |
|
| | return (
|
| | f"β
Transcription done! π€ AI Summary started immediately\nπ Using transcript: {len(transcription_job.transcript_text):,} characters\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"AI Processing: {transcription_job.original_filename}",
|
| | gr.update(visible=True, value="π AI Summary active"),
|
| | get_user_stats_display(user)
|
| | )
|
| |
|
| | except Exception as e:
|
| | print(f"β Error triggering AI summary: {str(e)}")
|
| | return (
|
| | f"β Transcription completed but AI summary failed to start: {str(e)}",
|
| | "",
|
| | gr.update(visible=False),
|
| | "AI Summary creation failed",
|
| | gr.update(visible=False),
|
| | get_user_stats_display(user)
|
| | )
|
| |
|
| |
|
| | if 'current_summary_job_id' not in summary_job_state:
|
| | return (
|
| | "No active AI summary job",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | job_id = summary_job_state['current_summary_job_id']
|
| | job = ai_summary_manager.get_summary_status(job_id)
|
| |
|
| | if not job or job.user_id != user.user_id:
|
| | return (
|
| | "AI summary job not found or access denied",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | processing_time = format_processing_time(job.created_at, job.completed_at)
|
| |
|
| |
|
| | last_status = summary_job_state.get('last_status', '')
|
| | if job.status != last_status:
|
| | print(f"π [{user.username}] AI Summary status: {last_status} β {job.status}")
|
| | summary_job_state['last_status'] = job.status
|
| |
|
| |
|
| | stats_display = get_user_stats_display(user)
|
| |
|
| |
|
| | if job.status == 'completed' and job.summary_text and job.summary_text.strip():
|
| |
|
| | summary_job_state['auto_refresh_active'] = False
|
| |
|
| |
|
| | try:
|
| | summary_file = create_summary_file(job.summary_text, job_id)
|
| | print(f"β
[{user.username}] AI Summary ready: {len(job.summary_text)} characters")
|
| | except Exception as e:
|
| | print(f"β οΈ [{user.username}] Error creating summary file: {str(e)}")
|
| | summary_file = None
|
| |
|
| | total_time = format_processing_time(summary_job_state['start_time'])
|
| | return (
|
| | f"β
AI Summary completed! Total time: {total_time}\nπ Generated: {len(job.summary_text):,} characters",
|
| | job.summary_text,
|
| | gr.update(visible=True, value=summary_file) if summary_file else gr.update(visible=False),
|
| | f"Completed: {', '.join(job.original_files)}",
|
| | gr.update(visible=False),
|
| | stats_display
|
| | )
|
| |
|
| | elif job.status == 'failed':
|
| |
|
| | summary_job_state['auto_refresh_active'] = False
|
| | error_msg = job.error_message[:100] + "..." if job.error_message else "Unknown error"
|
| | total_time = format_processing_time(summary_job_state['start_time'])
|
| | return (
|
| | f"β AI Summary failed after {total_time}",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Error: {error_msg}",
|
| | gr.update(visible=False),
|
| | stats_display
|
| | )
|
| |
|
| | elif job.status == 'processing':
|
| |
|
| | return (
|
| | f"π€ AI analyzing and generating summary... ({processing_time} AI processing)\nπ Creating comprehensive analysis\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"AI Processing: {', '.join(job.original_files[:2])}{'...' if len(job.original_files) > 2 else ''}",
|
| | gr.update(visible=True, value="π AI generating summary"),
|
| | stats_display
|
| | )
|
| |
|
| | else:
|
| |
|
| | return (
|
| | f"β³ AI Summary queued... ({processing_time} waiting)\nπ‘ Auto-refreshing every 10 seconds...",
|
| | "",
|
| | gr.update(visible=False),
|
| | f"Queued: {', '.join(job.original_files[:2])}{'...' if len(job.original_files) > 2 else ''}",
|
| | gr.update(visible=True, value="π AI queued"),
|
| | stats_display
|
| | )
|
| |
|
| | except Exception as e:
|
| | print(f"β Error checking AI summary status: {str(e)}")
|
| | return (
|
| | f"Error checking AI summary status: {str(e)}",
|
| | "",
|
| | gr.update(visible=False),
|
| | "",
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | def should_auto_refresh(job_state, user):
|
| | """Check if auto-refresh should be active"""
|
| | if not user or not job_state or not job_state.get('auto_refresh_active', False):
|
| | return False
|
| |
|
| | if 'current_job_id' not in job_state:
|
| | return False
|
| |
|
| | job_id = job_state['current_job_id']
|
| |
|
| | try:
|
| | job = transcription_manager.get_job_status(job_id)
|
| |
|
| | if not job or job.user_id != user.user_id:
|
| | return False
|
| |
|
| | if job.status == 'failed':
|
| | return False
|
| | elif job.status == 'completed':
|
| | if job.transcript_text and job.transcript_text.strip():
|
| | return False
|
| | else:
|
| | return True
|
| | else:
|
| | return True
|
| |
|
| | except Exception as e:
|
| | print(f"β Error in should_auto_refresh: {str(e)}")
|
| | return True
|
| |
|
| | def should_auto_refresh_summary(summary_job_state, user):
|
| | """Check if AI summary auto-refresh should be active"""
|
| | if not user or not summary_job_state or not summary_job_state.get('auto_refresh_active', False):
|
| | return False
|
| |
|
| | if 'current_summary_job_id' not in summary_job_state:
|
| | return False
|
| |
|
| | job_id = summary_job_state['current_summary_job_id']
|
| |
|
| | try:
|
| | job = ai_summary_manager.get_summary_status(job_id)
|
| |
|
| | if not job or job.user_id != user.user_id:
|
| | return False
|
| |
|
| | if job.status in ['failed', 'completed']:
|
| | return False
|
| | else:
|
| | return True
|
| |
|
| | except Exception as e:
|
| | print(f"β Error in should_auto_refresh_summary: {str(e)}")
|
| | return True
|
| |
|
| | def auto_refresh_status(job_state, user):
|
| | """Auto-refresh function for transcription"""
|
| | if not user:
|
| | return (
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | if should_auto_refresh(job_state, user):
|
| | return check_current_job_status(job_state, user)
|
| | else:
|
| | return (
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | def auto_refresh_ai_summary(summary_job_state, user):
|
| | """Auto-refresh function for AI summary"""
|
| | if not user:
|
| | return (
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| | if should_auto_refresh_summary(summary_job_state, user):
|
| | return check_ai_summary_status(summary_job_state, user)
|
| | else:
|
| | return (
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(),
|
| | gr.update(visible=False),
|
| | gr.update()
|
| | )
|
| |
|
| |
|
| | def get_transcription_history_table(user, show_all=False):
|
| | """Get transcription history table"""
|
| | if not user:
|
| | return []
|
| |
|
| | try:
|
| | limit = 100 if show_all else 20
|
| | transcript_jobs = transcription_manager.get_user_history(user.user_id, limit=limit)
|
| |
|
| | table_data = []
|
| | for job in transcript_jobs:
|
| | try:
|
| | created_time = datetime.fromisoformat(job.created_at)
|
| | formatted_date = created_time.strftime("%Y-%m-%d %H:%M")
|
| | except:
|
| | formatted_date = job.created_at[:16]
|
| |
|
| | status_display = format_status(job.status)
|
| | time_display = format_processing_time(job.created_at, job.completed_at)
|
| | job_id_display = job.job_id[:8] + "..." if len(job.job_id) > 8 else job.job_id
|
| | language_display = ALLOWED_LANGS.get(job.language, job.language)
|
| |
|
| | if job.status == 'completed' and job.transcript_text:
|
| | download_status = "Available"
|
| | else:
|
| | download_status = status_display
|
| |
|
| | table_data.append([
|
| | formatted_date,
|
| | job.original_filename,
|
| | language_display,
|
| | status_display,
|
| | time_display,
|
| | job_id_display,
|
| | download_status
|
| | ])
|
| |
|
| | return table_data
|
| |
|
| | except Exception as e:
|
| | print(f"β Error loading transcription history: {str(e)}")
|
| | return []
|
| |
|
| | def get_ai_summary_history_table(user, show_all=False):
|
| | """Get AI summary history table"""
|
| | if not user:
|
| | return []
|
| |
|
| | try:
|
| | limit = 100 if show_all else 20
|
| | summary_jobs = ai_summary_manager.get_user_summary_history(user.user_id, limit=limit)
|
| |
|
| | table_data = []
|
| | for job in summary_jobs:
|
| | try:
|
| | created_time = datetime.fromisoformat(job.created_at)
|
| | formatted_date = created_time.strftime("%Y-%m-%d %H:%M")
|
| | except:
|
| | formatted_date = job.created_at[:16]
|
| |
|
| | status_display = format_status(job.status)
|
| | time_display = format_processing_time(job.created_at, job.completed_at)
|
| | job_id_display = job.job_id[:8] + "..." if len(job.job_id) > 8 else job.job_id
|
| |
|
| |
|
| | source_summary = f"{len(job.original_files)} sources"
|
| | if len(job.original_files) <= 2:
|
| | source_summary = ", ".join([f[:20] + "..." if len(f) > 20 else f for f in job.original_files])
|
| |
|
| | if job.status == 'completed' and job.summary_text:
|
| | download_status = "Available"
|
| | else:
|
| | download_status = status_display
|
| |
|
| | table_data.append([
|
| | formatted_date,
|
| | source_summary,
|
| | job.settings.get('output_language', 'Thai') if job.settings else 'Thai',
|
| | status_display,
|
| | time_display,
|
| | job_id_display,
|
| | download_status
|
| | ])
|
| |
|
| | return table_data
|
| |
|
| | except Exception as e:
|
| | print(f"β Error loading AI summary history: {str(e)}")
|
| | return []
|
| |
|
| | def refresh_transcription_history(user, show_all=False):
|
| | """Refresh transcription history and create download files"""
|
| | if not user:
|
| | return [], gr.update(), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
|
| |
|
| | try:
|
| | table_data = get_transcription_history_table(user, show_all)
|
| | stats_display = get_user_stats_display(user)
|
| |
|
| |
|
| | completed_jobs = transcription_manager.get_user_history(user.user_id, limit=50)
|
| | completed_transcripts = [
|
| | job for job in completed_jobs
|
| | if job.status == 'completed' and job.transcript_text
|
| | ]
|
| |
|
| |
|
| | download_updates = []
|
| | for i in range(5):
|
| | if i < len(completed_transcripts):
|
| | job = completed_transcripts[i]
|
| | try:
|
| | file_path = create_transcript_file(job.transcript_text, job.job_id)
|
| | label = f"π {job.original_filename} ({job.created_at[:10]})"
|
| | download_updates.append(gr.update(visible=True, value=file_path, label=label))
|
| | except Exception as e:
|
| | print(f"Error creating transcript download: {e}")
|
| | download_updates.append(gr.update(visible=False))
|
| | else:
|
| | download_updates.append(gr.update(visible=False))
|
| |
|
| | return [table_data, stats_display] + download_updates
|
| |
|
| | except Exception as e:
|
| | print(f"β Error refreshing transcription history: {str(e)}")
|
| | return [], gr.update(), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
|
| |
|
| | def refresh_ai_summary_history(user, show_all=False):
|
| | """Refresh AI summary history and create download files"""
|
| | if not user:
|
| | return [], gr.update(), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
|
| |
|
| | try:
|
| | table_data = get_ai_summary_history_table(user, show_all)
|
| | stats_display = get_user_stats_display(user)
|
| |
|
| |
|
| | completed_jobs = ai_summary_manager.get_user_summary_history(user.user_id, limit=50)
|
| | completed_summaries = [
|
| | job for job in completed_jobs
|
| | if job.status == 'completed' and job.summary_text
|
| | ]
|
| |
|
| |
|
| | download_updates = []
|
| | for i in range(5):
|
| | if i < len(completed_summaries):
|
| | job = completed_summaries[i]
|
| | try:
|
| | file_path = create_summary_file(job.summary_text, job.job_id)
|
| | source_name = job.original_files[0][:30] if job.original_files else "AI Summary"
|
| | label = f"π€ {source_name} ({job.created_at[:10]})"
|
| | download_updates.append(gr.update(visible=True, value=file_path, label=label))
|
| | except Exception as e:
|
| | print(f"Error creating summary download: {e}")
|
| | download_updates.append(gr.update(visible=False))
|
| | else:
|
| | download_updates.append(gr.update(visible=False))
|
| |
|
| | return [table_data, stats_display] + download_updates
|
| |
|
| | except Exception as e:
|
| | print(f"β Error refreshing AI summary history: {str(e)}")
|
| | return [], gr.update(), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
|
| |
|
| |
|
| | def export_user_data(user):
|
| | """Export comprehensive user data including summaries"""
|
| | if not user:
|
| | return "β Please log in to export your data", gr.update(visible=False)
|
| |
|
| | try:
|
| |
|
| | transcript_export = transcription_manager.export_user_data(user.user_id)
|
| |
|
| |
|
| | try:
|
| | summary_export = {
|
| | 'ai_summaries': [job.__dict__ for job in ai_summary_manager.get_user_summary_history(user.user_id, limit=1000)],
|
| | 'summary_stats': transcription_manager.get_user_summary_stats(user.user_id)
|
| | }
|
| | except:
|
| | summary_export = {'ai_summaries': [], 'summary_stats': {}}
|
| |
|
| |
|
| | combined_export = {
|
| | **transcript_export,
|
| | **summary_export,
|
| | 'export_type': 'comprehensive_azure_ai_service',
|
| | 'services': ['transcription', 'ai_summarization']
|
| | }
|
| |
|
| |
|
| | os.makedirs("temp", exist_ok=True)
|
| | filename = f"temp/user_data_export_{user.user_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
| |
|
| | with open(filename, "w", encoding="utf-8") as f:
|
| | json.dump(combined_export, f, indent=2, ensure_ascii=False, default=str)
|
| |
|
| | print(f"π¦ [{user.username}] Comprehensive data export created")
|
| | return "β
Your complete data (transcripts + AI summaries) has been exported successfully", gr.update(visible=True, value=filename, label="Download Your Complete Data Export")
|
| |
|
| | except Exception as e:
|
| | print(f"β Error exporting comprehensive user data: {str(e)}")
|
| | return f"β Export failed: {str(e)}", gr.update(visible=False)
|
| |
|
| | def update_marketing_consent(user, marketing_consent):
|
| | """Update user's marketing consent"""
|
| | if not user:
|
| | return "β Please log in to update consent"
|
| |
|
| | try:
|
| | success = transcription_manager.update_user_consent(user.user_id, marketing_consent)
|
| | if success:
|
| | user.marketing_consent = marketing_consent
|
| | print(f"π§ [{user.username}] Marketing consent updated: {marketing_consent}")
|
| | return f"β
Marketing consent updated successfully"
|
| | else:
|
| | return "β Failed to update consent"
|
| | except Exception as e:
|
| | return f"β Error: {str(e)}"
|
| |
|
| | def delete_user_account(user, confirmation_text):
|
| | """Delete user account and all data (transcripts + summaries)"""
|
| | if not user:
|
| | return "β Please log in to delete account", None, gr.update(visible=True), gr.update(visible=False)
|
| |
|
| | if confirmation_text != "DELETE MY ACCOUNT":
|
| | return "β Please type 'DELETE MY ACCOUNT' to confirm", user, gr.update(visible=False), gr.update(visible=True)
|
| |
|
| | try:
|
| |
|
| | success = transcription_manager.delete_user_account(user.user_id)
|
| |
|
| |
|
| | try:
|
| | transcription_manager.delete_user_summary_data(user.user_id)
|
| | except Exception as e:
|
| | print(f"β οΈ Warning: Could not delete AI summary data: {e}")
|
| |
|
| | if success:
|
| | print(f"ποΈ [{user.username}] Complete account deleted (transcripts + AI summaries)")
|
| | return "β
Your account and all data (transcripts + AI summaries) have been permanently deleted", None, gr.update(visible=True), gr.update(visible=False)
|
| | else:
|
| | return "β Failed to delete account", user, gr.update(visible=False), gr.update(visible=True)
|
| | except Exception as e:
|
| | return f"β Error: {str(e)}", user, gr.update(visible=False), gr.update(visible=True)
|
| |
|
| | def on_user_login(user):
|
| | """Update UI components when user logs in"""
|
| | if user:
|
| | return gr.update(value=user.marketing_consent)
|
| | else:
|
| | return gr.update(value=False)
|
| |
|
| | def create_transcript_file(transcript_text, job_id):
|
| | """Create a downloadable transcript file"""
|
| | os.makedirs("temp", exist_ok=True)
|
| | filename = f"temp/transcript_{job_id}.txt"
|
| | with open(filename, "w", encoding="utf-8") as f:
|
| | f.write(transcript_text)
|
| | return filename
|
| |
|
| | def create_summary_file(summary_text, job_id):
|
| | """Create a downloadable AI summary file"""
|
| | os.makedirs("temp", exist_ok=True)
|
| | filename = f"temp/ai_summary_{job_id}.txt"
|
| | with open(filename, "w", encoding="utf-8") as f:
|
| | f.write(summary_text)
|
| | return filename
|
| |
|
| |
|
| | enhanced_css = """
|
| | /* Main container styling */
|
| | .gradio-container {
|
| | background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
| | font-family: 'Segoe UI', system-ui, sans-serif;
|
| | color: #212529;
|
| | }
|
| |
|
| | /* Enhanced header styling */
|
| | .main-header {
|
| | background: linear-gradient(135deg, #007bff, #0056b3);
|
| | color: white;
|
| | border: none;
|
| | border-radius: 12px;
|
| | padding: 30px;
|
| | text-align: center;
|
| | margin-bottom: 20px;
|
| | box-shadow: 0 4px 12px rgba(0,123,255,0.3);
|
| | }
|
| |
|
| | .main-header h1 {
|
| | color: white;
|
| | margin-bottom: 10px;
|
| | font-size: 2.5em;
|
| | font-weight: 700;
|
| | text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
| | }
|
| |
|
| | .main-header p {
|
| | color: rgba(255,255,255,0.9);
|
| | font-size: 1.2em;
|
| | margin: 0;
|
| | }
|
| |
|
| | /* Card styling with enhanced AI theme */
|
| | .gr-box {
|
| | background: white;
|
| | border: 1px solid #dee2e6;
|
| | border-radius: 12px;
|
| | box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
| | padding: 25px;
|
| | margin: 10px 0;
|
| | transition: all 0.3s ease;
|
| | }
|
| |
|
| | .gr-box:hover {
|
| | box-shadow: 0 6px 16px rgba(0,0,0,0.12);
|
| | transform: translateY(-2px);
|
| | }
|
| |
|
| | /* AI-specific card styling */
|
| | .ai-summary-card {
|
| | background: linear-gradient(135deg, #f8f9ff, #e8f2ff);
|
| | border: 2px solid #007bff;
|
| | border-radius: 12px;
|
| | padding: 25px;
|
| | margin: 15px 0;
|
| | }
|
| |
|
| | /* Button styling with AI enhancements */
|
| | .gr-button {
|
| | background: linear-gradient(135deg, #007bff, #0056b3);
|
| | border: none;
|
| | border-radius: 8px;
|
| | color: white;
|
| | font-weight: 600;
|
| | padding: 14px 28px;
|
| | transition: all 0.3s ease;
|
| | box-shadow: 0 3px 6px rgba(0,123,255,0.3);
|
| | text-transform: uppercase;
|
| | letter-spacing: 0.5px;
|
| | }
|
| |
|
| | .gr-button:hover {
|
| | background: linear-gradient(135deg, #0056b3, #004085);
|
| | transform: translateY(-2px);
|
| | box-shadow: 0 5px 10px rgba(0,123,255,0.4);
|
| | }
|
| |
|
| | /* AI Summary specific buttons */
|
| | .ai-button {
|
| | background: linear-gradient(135deg, #28a745, #1e7e34);
|
| | }
|
| |
|
| | .ai-button:hover {
|
| | background: linear-gradient(135deg, #1e7e34, #155724);
|
| | }
|
| |
|
| | /* Status displays with enhanced styling */
|
| | .status-display {
|
| | background: linear-gradient(135deg, #e3f2fd, #bbdefb);
|
| | border-left: 4px solid #2196f3;
|
| | padding: 20px;
|
| | border-radius: 0 12px 12px 0;
|
| | margin: 15px 0;
|
| | font-family: 'Monaco', 'Consolas', monospace;
|
| | font-size: 14px;
|
| | line-height: 1.6;
|
| | }
|
| |
|
| | .ai-status-display {
|
| | background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
|
| | border-left: 4px solid #28a745;
|
| | }
|
| |
|
| | /* Auto-refresh indicators */
|
| | .auto-refresh-indicator {
|
| | background: linear-gradient(135deg, #fff3cd, #ffeaa7);
|
| | border: 2px solid #ffc107;
|
| | border-radius: 8px;
|
| | padding: 10px 16px;
|
| | font-size: 12px;
|
| | color: #856404;
|
| | text-align: center;
|
| | animation: pulse 2s infinite;
|
| | font-weight: 600;
|
| | }
|
| |
|
| | @keyframes pulse {
|
| | 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.4); }
|
| | 50% { opacity: 0.8; box-shadow: 0 0 0 10px rgba(255, 193, 7, 0); }
|
| | }
|
| |
|
| | /* User stats with enhanced design */
|
| | .user-stats {
|
| | background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
|
| | border: 2px solid #28a745;
|
| | border-radius: 8px;
|
| | padding: 12px 16px;
|
| | font-size: 13px;
|
| | color: #155724;
|
| | text-align: center;
|
| | font-weight: 600;
|
| | box-shadow: 0 2px 8px rgba(40,167,69,0.2);
|
| | }
|
| |
|
| | /* Enhanced input styling */
|
| | .gr-textbox, .gr-dropdown, .gr-file {
|
| | border: 2px solid #e9ecef;
|
| | border-radius: 10px;
|
| | background: white;
|
| | color: #212529;
|
| | transition: all 0.3s ease;
|
| | padding: 12px;
|
| | font-size: 14px;
|
| | }
|
| |
|
| | .gr-textbox:focus, .gr-dropdown:focus {
|
| | border-color: #007bff;
|
| | box-shadow: 0 0 0 4px rgba(0,123,255,0.1);
|
| | background: #f8f9ff;
|
| | }
|
| |
|
| | /* Tab styling with AI theme */
|
| | .tab-nav {
|
| | background: white;
|
| | border-bottom: 3px solid #007bff;
|
| | border-radius: 12px 12px 0 0;
|
| | box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| | }
|
| |
|
| | .tab-nav .tab-button {
|
| | padding: 15px 25px;
|
| | font-weight: 600;
|
| | transition: all 0.3s ease;
|
| | }
|
| |
|
| | .tab-nav .tab-button.selected {
|
| | background: linear-gradient(135deg, #007bff, #0056b3);
|
| | color: white;
|
| | }
|
| |
|
| | /* History table with enhanced design and black text */
|
| | .history-table {
|
| | background: white;
|
| | border: 2px solid #dee2e6;
|
| | border-radius: 12px;
|
| | font-size: 14px;
|
| | overflow: hidden;
|
| | color: #000000; /* Force black text */
|
| | }
|
| |
|
| | .history-table thead th {
|
| | background: linear-gradient(135deg, #343a40, #495057);
|
| | color: white;
|
| | font-weight: 700;
|
| | padding: 16px 12px;
|
| | text-align: center;
|
| | border: none;
|
| | }
|
| |
|
| | .history-table tbody tr {
|
| | transition: all 0.2s ease;
|
| | border-bottom: 1px solid #dee2e6;
|
| | color: #000000; /* Force black text for rows */
|
| | }
|
| |
|
| | .history-table tbody tr:hover {
|
| | background: linear-gradient(135deg, #f8f9ff, #e8f2ff);
|
| | transform: scale(1.01);
|
| | color: #000000; /* Ensure text stays black on hover */
|
| | }
|
| |
|
| | .history-table tbody td {
|
| | padding: 12px;
|
| | vertical-align: middle;
|
| | text-align: center;
|
| | border-right: 1px solid #dee2e6;
|
| | color: #000000 !important; /* Force black text with !important */
|
| | font-weight: 500;
|
| | }
|
| |
|
| | .history-table tbody td:last-child {
|
| | border-right: none;
|
| | }
|
| |
|
| | /* AI Summary specific elements */
|
| | .ai-content-sources {
|
| | background: linear-gradient(135deg, #fff8e1, #ffecb3);
|
| | border: 2px solid #ffa000;
|
| | border-radius: 12px;
|
| | padding: 20px;
|
| | margin: 15px 0;
|
| | }
|
| |
|
| | .ai-instructions {
|
| | background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
|
| | border: 2px solid #4caf50;
|
| | border-radius: 12px;
|
| | padding: 20px;
|
| | margin: 15px 0;
|
| | }
|
| |
|
| | .ai-results {
|
| | background: linear-gradient(135deg, #f3e5f5, #e1bee7);
|
| | border: 2px solid #9c27b0;
|
| | border-radius: 12px;
|
| | padding: 20px;
|
| | margin: 15px 0;
|
| | }
|
| |
|
| | /* Enhanced file upload areas */
|
| | .file-upload-area {
|
| | border: 3px dashed #007bff;
|
| | border-radius: 12px;
|
| | padding: 30px;
|
| | text-align: center;
|
| | background: linear-gradient(135deg, #f8f9ff, #e8f2ff);
|
| | transition: all 0.3s ease;
|
| | }
|
| |
|
| | .file-upload-area:hover {
|
| | border-color: #0056b3;
|
| | background: linear-gradient(135deg, #e8f2ff, #d3e8ff);
|
| | }
|
| |
|
| | /* Progress indicators */
|
| | .progress-indicator {
|
| | background: linear-gradient(90deg, #007bff, #28a745, #ffc107);
|
| | height: 4px;
|
| | border-radius: 2px;
|
| | animation: progress 2s linear infinite;
|
| | }
|
| |
|
| | @keyframes progress {
|
| | 0% { width: 0%; }
|
| | 50% { width: 70%; }
|
| | 100% { width: 100%; }
|
| | }
|
| |
|
| | /* Enhanced tooltips and help text */
|
| | .help-text {
|
| | color: #6c757d;
|
| | font-style: italic;
|
| | font-size: 12px;
|
| | margin-top: 5px;
|
| | }
|
| |
|
| | /* Responsive design improvements */
|
| | @media (max-width: 768px) {
|
| | .main-header h1 { font-size: 2em; }
|
| | .gr-button { padding: 10px 20px; font-size: 14px; }
|
| | .gr-box { padding: 15px; margin: 5px 0; }
|
| | }
|
| | """
|
| |
|
| |
|
| | with gr.Blocks(
|
| | theme=gr.themes.Soft(
|
| | primary_hue="blue",
|
| | secondary_hue="green",
|
| | neutral_hue="gray",
|
| | font=["system-ui", "sans-serif"]
|
| | ),
|
| | css=enhanced_css,
|
| | title="ποΈπ€ Azure-Powered AI Conference Service - Advanced Transcription & Intelligent Summarization"
|
| | ) as demo:
|
| |
|
| |
|
| | current_user = gr.State(None)
|
| | job_state = gr.State({})
|
| | summary_job_state = gr.State({})
|
| |
|
| |
|
| | with gr.Row():
|
| | gr.HTML("""
|
| | <div class="main-header">
|
| | <h1>ποΈπ€ Azure-Powered AI Conference Service</h1>
|
| | <p>Advanced AI-powered conference analysis with transcription, computer vision, and intelligent summarization using Azure AI Foundry</p>
|
| | </div>
|
| | """)
|
| |
|
| |
|
| | user_stats_display = gr.Textbox(
|
| | label="",
|
| | lines=1,
|
| | interactive=False,
|
| | show_label=False,
|
| | placeholder="π€ Please log in to view your comprehensive statistics...",
|
| | elem_classes=["user-stats"]
|
| | )
|
| |
|
| |
|
| | with gr.Column(visible=True, elem_classes=["auth-form"]) as auth_section:
|
| | gr.Markdown("## π Authentication Required")
|
| | gr.Markdown("Please log in or create an account to use the advanced AI-powered conference analysis service.")
|
| |
|
| | with gr.Tabs() as auth_tabs:
|
| |
|
| | with gr.Tab("π Login") as login_tab:
|
| | with gr.Column():
|
| | login_email = gr.Textbox(
|
| | label="Email or Username",
|
| | placeholder="Enter your email or username"
|
| | )
|
| | login_password = gr.Textbox(
|
| | label="Password",
|
| | type="password",
|
| | placeholder="Enter your password"
|
| | )
|
| |
|
| | with gr.Row():
|
| | login_btn = gr.Button("π Login", variant="primary", elem_classes=["auth-button"])
|
| |
|
| | login_status = gr.Textbox(
|
| | label="",
|
| | show_label=False,
|
| | interactive=False,
|
| | placeholder="Enter your credentials and click Login"
|
| | )
|
| |
|
| |
|
| | with gr.Tab("π Register") as register_tab:
|
| | with gr.Column():
|
| | reg_email = gr.Textbox(
|
| | label="Email",
|
| | placeholder="Enter your email address"
|
| | )
|
| | reg_username = gr.Textbox(
|
| | label="Username",
|
| | placeholder="Choose a username (3-30 characters, alphanumeric and underscore)"
|
| | )
|
| | reg_password = gr.Textbox(
|
| | label="Password",
|
| | type="password",
|
| | placeholder="Create a strong password (min 8 chars, mixed case, numbers)"
|
| | )
|
| | reg_confirm_password = gr.Textbox(
|
| | label="Confirm Password",
|
| | type="password",
|
| | placeholder="Confirm your password"
|
| | )
|
| |
|
| | gr.Markdown("### π Privacy & Data Consent")
|
| |
|
| | with gr.Column(elem_classes=["privacy-notice"]):
|
| | gr.Markdown("""
|
| | **Enhanced Privacy Notice**: By creating an account, you acknowledge that:
|
| | - Your data will be stored securely in user-separated Azure Blob Storage
|
| | - Transcriptions are processed using Azure Speech Services
|
| | - AI summaries are generated using Azure OpenAI with advanced privacy protection
|
| | - Computer vision analysis may be performed on uploaded images/videos
|
| | - You can export or delete all your data (transcripts + AI summaries) at any time
|
| | - We comply with GDPR and data protection regulations
|
| | """)
|
| |
|
| | gdpr_consent = gr.Checkbox(
|
| | label="I consent to the processing of my personal data as described in the Privacy Notice (Required)",
|
| | value=False
|
| | )
|
| | data_retention_consent = gr.Checkbox(
|
| | label="I agree to data retention for transcription and AI analysis service functionality (Required)",
|
| | value=False
|
| | )
|
| | marketing_consent = gr.Checkbox(
|
| | label="I consent to receiving marketing communications about new AI features (Optional)",
|
| | value=False
|
| | )
|
| |
|
| | with gr.Row():
|
| | register_btn = gr.Button("π Create Account", variant="primary", elem_classes=["auth-button"])
|
| |
|
| | register_status = gr.Textbox(
|
| | label="",
|
| | show_label=False,
|
| | interactive=False,
|
| | placeholder="Fill out the form and agree to the required consents to create your account"
|
| | )
|
| |
|
| | login_after_register = gr.Button(
|
| | "π Go to Login",
|
| | visible=False,
|
| | variant="secondary"
|
| | )
|
| |
|
| |
|
| | with gr.Column(visible=False) as main_app:
|
| |
|
| |
|
| | with gr.Row():
|
| | with gr.Column(scale=3):
|
| | pass
|
| | with gr.Column(scale=1):
|
| | logout_btn = gr.Button("π Logout", variant="secondary")
|
| |
|
| |
|
| | with gr.Tabs():
|
| |
|
| | with gr.Tab("ποΈ Transcribe"):
|
| | with gr.Row():
|
| |
|
| | with gr.Column(scale=1):
|
| | gr.Markdown("### π Upload File")
|
| |
|
| | file_upload = gr.File(
|
| | label="Audio or Video File",
|
| | type="filepath",
|
| | file_types=[
|
| | ".wav", ".mp3", ".ogg", ".opus", ".flac", ".wma", ".aac",
|
| | ".m4a", ".amr", ".webm", ".speex",
|
| | ".mp4", ".mov", ".avi", ".mkv", ".wmv", ".flv", ".3gp"
|
| | ],
|
| | elem_classes=["file-upload-area"]
|
| | )
|
| |
|
| | with gr.Row():
|
| | language = gr.Dropdown(
|
| | choices=[(v, k) for k, v in ALLOWED_LANGS.items()],
|
| | label="Language",
|
| | value="th-TH"
|
| | )
|
| | audio_format = gr.Dropdown(
|
| | choices=AUDIO_FORMATS,
|
| | value="wav",
|
| | label="Output Format"
|
| | )
|
| |
|
| | gr.Markdown("### βοΈ Advanced Settings")
|
| |
|
| | with gr.Row():
|
| | diarization_enabled = gr.Checkbox(
|
| | label="Speaker Identification",
|
| | value=True
|
| | )
|
| | speakers = gr.Slider(
|
| | minimum=1,
|
| | maximum=10,
|
| | step=1,
|
| | value=2,
|
| | label="Max Speakers"
|
| | )
|
| |
|
| | with gr.Row():
|
| | timestamps = gr.Checkbox(
|
| | label="Timestamps",
|
| | value=True
|
| | )
|
| | profanity = gr.Dropdown(
|
| | choices=["masked", "removed", "raw"],
|
| | value="masked",
|
| | label="Profanity Filter"
|
| | )
|
| |
|
| | with gr.Row():
|
| | punctuation = gr.Dropdown(
|
| | choices=["automatic", "dictated", "none"],
|
| | value="automatic",
|
| | label="Punctuation"
|
| | )
|
| | lexical = gr.Checkbox(
|
| | label="Lexical Form",
|
| | value=False
|
| | )
|
| |
|
| | submit_btn = gr.Button(
|
| | "π Start Transcription",
|
| | variant="primary",
|
| | size="lg"
|
| | )
|
| |
|
| |
|
| | with gr.Column(scale=1):
|
| | gr.Markdown("### π Status & Results")
|
| |
|
| |
|
| | auto_refresh_status_display = gr.Textbox(
|
| | label="",
|
| | lines=1,
|
| | interactive=False,
|
| | show_label=False,
|
| | visible=False,
|
| | elem_classes=["auto-refresh-indicator"]
|
| | )
|
| |
|
| | status_display = gr.Textbox(
|
| | label="",
|
| | lines=4,
|
| | interactive=False,
|
| | show_label=False,
|
| | placeholder="Upload a file and click 'Start Transcription' to begin...\nStatus will auto-refresh every 10 seconds during processing.\nYour data is stored in your private user folder for PDPA compliance.\nCompleted transcripts can be used for AI summarization.",
|
| | elem_classes=["status-display"]
|
| | )
|
| |
|
| | job_info = gr.Textbox(
|
| | label="",
|
| | lines=1,
|
| | interactive=False,
|
| | show_label=False,
|
| | placeholder=""
|
| | )
|
| |
|
| | with gr.Row():
|
| | refresh_btn = gr.Button(
|
| | "π Check Status",
|
| | variant="secondary"
|
| | )
|
| | stop_refresh_btn = gr.Button(
|
| | "βΉοΈ Stop Auto-Refresh",
|
| | variant="secondary"
|
| | )
|
| |
|
| | gr.Markdown("### π Transcript Results")
|
| |
|
| | transcript_output = gr.Textbox(
|
| | label="Transcript",
|
| | lines=12,
|
| | interactive=False,
|
| | placeholder="Your transcript with speaker identification and precise timestamps (HH:MM:SS) will appear here...\nThis transcript will be available for AI-powered summarization.",
|
| | elem_classes=["status-display"]
|
| | )
|
| |
|
| | download_file = gr.File(
|
| | label="Download Transcript",
|
| | interactive=False,
|
| | visible=False
|
| | )
|
| |
|
| |
|
| | with gr.Tab("π€ AI Summary Conference"):
|
| | gr.Markdown("### π― AI-Powered Conference Summarization")
|
| | gr.Markdown("*Generate intelligent summaries from transcripts, documents, audio/video files, and visual content using Azure AI Foundry*")
|
| |
|
| | with gr.Row():
|
| |
|
| | with gr.Column(scale=1, elem_classes=["ai-content-sources"]):
|
| | gr.Markdown("## π INPUT CONTENT")
|
| |
|
| |
|
| | gr.Markdown("#### Audio/Video Content")
|
| | with gr.Tabs():
|
| | with gr.Tab("π Existing Transcripts"):
|
| | available_transcripts = gr.Dropdown(
|
| | label="Select Transcript",
|
| | choices=[],
|
| | value=None,
|
| | multiselect=True
|
| | )
|
| | refresh_transcripts_btn = gr.Button(
|
| | "π Refresh Transcripts",
|
| | variant="secondary",
|
| | size="sm"
|
| | )
|
| |
|
| | with gr.Tab("π₯ New Audio/Video Files"):
|
| | new_audio_video_file = gr.File(
|
| | label="Upload Audio/Video File",
|
| | file_count="single",
|
| | file_types=[
|
| | ".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".3gp", ".wmv",
|
| | ".wav", ".mp3", ".ogg", ".opus", ".flac", ".wma", ".aac", ".m4a", ".amr", ".speex"
|
| | ],
|
| | elem_classes=["file-upload-area"]
|
| | )
|
| |
|
| |
|
| | gr.Markdown("#### Document/Image Files (OCR Processing)")
|
| | document_image_files = gr.File(
|
| | label="Upload Documents/Images for OCR Text Extraction",
|
| | file_count="multiple",
|
| | file_types=[
|
| | ".pdf", ".docx", ".doc", ".pptx", ".ppt", ".xlsx", ".xls", ".txt", ".csv", ".json",
|
| | ".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff", ".webp"
|
| | ],
|
| | elem_classes=["file-upload-area"]
|
| | )
|
| |
|
| | gr.HTML("""
|
| | <div class="help-text">
|
| | <p><strong>Documents:</strong> PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, TXT, CSV, JSON</p>
|
| | <p><strong>Images:</strong> JPG, JPEG, PNG, BMP, GIF, TIFF, WebP</p>
|
| | <p><strong>OCR Process:</strong> Extract text from documents and images for AI analysis</p>
|
| | </div>
|
| | """)
|
| |
|
| |
|
| | with gr.Column(scale=1, elem_classes=["ai-instructions"]):
|
| | gr.Markdown("## π§ AI Instructions")
|
| |
|
| | gr.Markdown("#### Instructions for AI")
|
| | ai_instructions = gr.Textbox(
|
| | label="",
|
| | lines=6,
|
| | placeholder="Describe the conference type, desired format, and any corrections...\n\nExample: 'ΰΈͺΰΈ£ΰΈΈΰΈΰΈΰΈ²ΰΈ£ΰΈΰΈ£ΰΈ°ΰΈΰΈΈΰΈ‘ΰΈ£ΰΈ²ΰΈ’ΰΉΰΈΰΈ£ΰΈ‘ΰΈ²ΰΈͺΰΈΰΈ΅ΰΉ ΰΉΰΈΰΈΰΈ±ΰΈͺΰΈΰΈ΅ΰΉΰΈΰΈ₯ΰΈΰΈ²ΰΈ£ΰΉΰΈΰΈ΄ΰΈ ΰΈΰΈ²ΰΈ£ΰΈΰΈ±ΰΈΰΈͺΰΈ΄ΰΈΰΉΰΈΰΈͺΰΈ³ΰΈΰΈ±ΰΈ ΰΉΰΈ₯ΰΈ°ΰΉΰΈΰΈΰΈΰΈ²ΰΈ ΰΈͺΰΈ£ΰΉΰΈ²ΰΈΰΈΰΈΰΈͺΰΈ£ΰΈΈΰΈΰΈΰΈΉΰΉΰΈΰΈ£ΰΈ΄ΰΈ«ΰΈ²ΰΈ£ΰΈΰΈ£ΰΉΰΈΰΈ‘ΰΈΰΈΈΰΈΰΈͺΰΈ³ΰΈΰΈ±ΰΈ'",
|
| | value="ΰΈͺΰΈ£ΰΈΈΰΈΰΉΰΈΰΈ·ΰΉΰΈΰΈ«ΰΈ²ΰΈΰΈ²ΰΈ£ΰΈΰΈ£ΰΈ°ΰΈΰΈΈΰΈ‘ΰΈ«ΰΈ£ΰΈ·ΰΈΰΉΰΈΰΈΰΈͺΰΈ²ΰΈ£ΰΈΰΈ΅ΰΉ ΰΉΰΈΰΈ’ΰΈΰΈ±ΰΈΰΈ£ΰΈΉΰΈΰΉΰΈΰΈΰΉΰΈΰΉΰΈΰΈ«ΰΈ±ΰΈ§ΰΈΰΉΰΈΰΉΰΈ₯ΰΈ°ΰΈ£ΰΈ²ΰΈ’ΰΈ₯ΰΈ°ΰΉΰΈΰΈ΅ΰΈ’ΰΈΰΈͺΰΈ³ΰΈΰΈ±ΰΈ ΰΈΰΈ£ΰΉΰΈΰΈ‘ΰΈ£ΰΈ°ΰΈΰΈΈΰΈΰΈ£ΰΈ°ΰΉΰΈΰΉΰΈΰΈΰΈ΅ΰΉΰΈΰΉΰΈΰΈΰΈΰΈ΄ΰΈΰΈΰΈ²ΰΈ‘",
|
| | show_label=False
|
| | )
|
| |
|
| | with gr.Row():
|
| | summary_format = gr.Dropdown(
|
| | choices=["ΰΈΰΈΰΈͺΰΈ£ΰΈΈΰΈΰΈΰΈΉΰΉΰΈΰΈ£ΰΈ΄ΰΈ«ΰΈ²ΰΈ£", "ΰΈ£ΰΈ²ΰΈ’ΰΈΰΈ²ΰΈΰΈΰΈ²ΰΈ£ΰΈΰΈ£ΰΈ°ΰΈΰΈΈΰΈ‘", "ΰΉΰΈΰΈΰΈΰΈ²ΰΈΰΉΰΈ₯ΰΈ°ΰΈ ΰΈ²ΰΈ£ΰΈΰΈ΄ΰΈ", "ΰΈΰΈ£ΰΈ°ΰΉΰΈΰΉΰΈΰΈͺΰΈ³ΰΈΰΈ±ΰΈ", "ΰΈΰΈ²ΰΈ£ΰΈ§ΰΈ΄ΰΉΰΈΰΈ£ΰΈ²ΰΈ°ΰΈ«ΰΉΰΉΰΈΰΈ΄ΰΈΰΈ₯ΰΈΆΰΈ"],
|
| | value="ΰΈΰΈΰΈͺΰΈ£ΰΈΈΰΈΰΈΰΈΉΰΉΰΈΰΈ£ΰΈ΄ΰΈ«ΰΈ²ΰΈ£",
|
| | label="Format"
|
| | )
|
| | output_language = gr.Dropdown(
|
| | choices=["Thai", "English", "Spanish", "French", "German", "Chinese", "Japanese"],
|
| | value="Thai",
|
| | label="Language"
|
| | )
|
| |
|
| | gr.Markdown("#### Focus Areas")
|
| | focus_areas = gr.Textbox(
|
| | label="",
|
| | placeholder="ΰΉΰΈΰΉΰΈ ΰΈΰΈ₯ΰΈΰΈ²ΰΈ£ΰΉΰΈΰΈ΄ΰΈ ΰΈΰΈ²ΰΈ£ΰΈΰΈ±ΰΈΰΈͺΰΈ΄ΰΈΰΉΰΈ ΰΈΰΈ²ΰΈ£ΰΈΰΈΉΰΈΰΈΰΈΈΰΈ’ΰΈΰΉΰΈ²ΰΈΰΉΰΈΰΈΰΈΰΈ΄ΰΈ",
|
| | show_label=False
|
| | )
|
| |
|
| | with gr.Row():
|
| | include_timestamps = gr.Checkbox(
|
| | label="ΰΉΰΈ§ΰΈ₯ΰΈ² (Timestamps)",
|
| | value=True
|
| | )
|
| | include_action_items = gr.Checkbox(
|
| | label="ΰΈ£ΰΈ²ΰΈ’ΰΈΰΈ²ΰΈ£ΰΈ ΰΈ²ΰΈ£ΰΈΰΈ΄ΰΈ",
|
| | value=True
|
| | )
|
| |
|
| | generate_summary_btn = gr.Button(
|
| | "π ΰΈͺΰΈ£ΰΉΰΈ²ΰΈ AI Summary",
|
| | variant="primary",
|
| | size="lg",
|
| | elem_classes=["ai-button"]
|
| | )
|
| |
|
| |
|
| | with gr.Column(scale=1, elem_classes=["ai-results"]):
|
| | gr.Markdown("## π Status & Results")
|
| |
|
| | ai_status_display = gr.Textbox(
|
| | label="",
|
| | lines=3,
|
| | interactive=False,
|
| | show_label=False,
|
| | placeholder="ΰΉΰΈ‘ΰΉΰΈ‘ΰΈ΅ΰΈΰΈ²ΰΈ AI Summary ΰΈΰΈ΅ΰΉΰΈΰΈ³ΰΈ₯ΰΈ±ΰΈΰΈΰΈ³ΰΉΰΈΰΈ΄ΰΈΰΈΰΈ²ΰΈ£",
|
| | elem_classes=["ai-status-display"]
|
| | )
|
| |
|
| |
|
| | ai_auto_refresh_status = gr.Textbox(
|
| | label="",
|
| | lines=1,
|
| | interactive=False,
|
| | show_label=False,
|
| | visible=False,
|
| | elem_classes=["auto-refresh-indicator"]
|
| | )
|
| |
|
| | ai_job_info = gr.Textbox(
|
| | label="",
|
| | lines=1,
|
| | interactive=False,
|
| | show_label=False,
|
| | placeholder=""
|
| | )
|
| |
|
| | check_ai_status_btn = gr.Button(
|
| | "π Check Status",
|
| | variant="secondary"
|
| | )
|
| |
|
| | gr.Markdown("#### AI Summary Results")
|
| | ai_summary_output = gr.Textbox(
|
| | label="",
|
| | lines=12,
|
| | interactive=False,
|
| | show_label=False,
|
| | placeholder="ΰΈΰΈ₯ΰΈ₯ΰΈ±ΰΈΰΈΰΉ AI Summary ΰΈΰΈ£ΰΉΰΈΰΈ‘ΰΈΰΉΰΈΰΈ‘ΰΈΉΰΈ₯ΰΉΰΈΰΈ΄ΰΈΰΈ₯ΰΈΆΰΈ ΰΈΰΈ£ΰΈ°ΰΉΰΈΰΉΰΈΰΈͺΰΈ³ΰΈΰΈ±ΰΈ ΰΉΰΈ₯ΰΈ°ΰΈΰΈ³ΰΉΰΈΰΈ°ΰΈΰΈ³ΰΈΰΈ΅ΰΉΰΈͺΰΈ²ΰΈ‘ΰΈ²ΰΈ£ΰΈΰΈΰΈ³ΰΉΰΈΰΈΰΈΰΈ΄ΰΈΰΈ±ΰΈΰΈ΄ΰΉΰΈΰΉΰΈΰΈ°ΰΉΰΈͺΰΈΰΈΰΈΰΈ΅ΰΉΰΈΰΈ΅ΰΉ...",
|
| | elem_classes=["ai-status-display"]
|
| | )
|
| |
|
| | ai_download_file = gr.File(
|
| | label="Download AI Summary",
|
| | interactive=False,
|
| | visible=False
|
| | )
|
| |
|
| |
|
| | with gr.Tab("π My History"):
|
| | gr.Markdown("### π Your Service History")
|
| | gr.Markdown("*View and download your transcription and AI summarization history (PDPA compliant - only your data)*")
|
| |
|
| |
|
| | with gr.Tabs():
|
| |
|
| | with gr.Tab("ποΈ Transcription History"):
|
| | with gr.Row():
|
| | refresh_transcription_history_btn = gr.Button(
|
| | "π Refresh Transcription History",
|
| | variant="primary"
|
| | )
|
| | show_all_transcriptions_checkbox = gr.Checkbox(
|
| | label="Show All Transcription Records (not just recent 20)",
|
| | value=False
|
| | )
|
| |
|
| | transcription_history_table = gr.Dataframe(
|
| | headers=["Date", "Filename", "Language", "Status", "Duration", "Job ID", "Download"],
|
| | datatype=["str", "str", "str", "str", "str", "str", "str"],
|
| | col_count=(7, "fixed"),
|
| | row_count=(20, "dynamic"),
|
| | wrap=True,
|
| | interactive=False,
|
| | elem_classes=["history-table"]
|
| | )
|
| |
|
| |
|
| | gr.Markdown("### π₯ Download Your Transcripts")
|
| | gr.Markdown("*Your available transcript downloads will appear below after refreshing*")
|
| |
|
| | with gr.Column():
|
| | transcript_download_1 = gr.File(label="", visible=False, interactive=False)
|
| | transcript_download_2 = gr.File(label="", visible=False, interactive=False)
|
| | transcript_download_3 = gr.File(label="", visible=False, interactive=False)
|
| | transcript_download_4 = gr.File(label="", visible=False, interactive=False)
|
| | transcript_download_5 = gr.File(label="", visible=False, interactive=False)
|
| |
|
| |
|
| | with gr.Tab("π€ AI Summary History"):
|
| | with gr.Row():
|
| | refresh_ai_summary_history_btn = gr.Button(
|
| | "π Refresh AI Summary History",
|
| | variant="primary"
|
| | )
|
| | show_all_summaries_checkbox = gr.Checkbox(
|
| | label="Show All AI Summary Records (not just recent 20)",
|
| | value=False
|
| | )
|
| |
|
| | ai_summary_history_table = gr.Dataframe(
|
| | headers=["Date", "Sources", "Language", "Status", "Duration", "Job ID", "Download"],
|
| | datatype=["str", "str", "str", "str", "str", "str", "str"],
|
| | col_count=(7, "fixed"),
|
| | row_count=(20, "dynamic"),
|
| | wrap=True,
|
| | interactive=False,
|
| | elem_classes=["history-table"]
|
| | )
|
| |
|
| |
|
| | gr.Markdown("### π₯ Download Your AI Summaries")
|
| | gr.Markdown("*Your available AI summary downloads will appear below after refreshing*")
|
| |
|
| | with gr.Column():
|
| | summary_download_1 = gr.File(label="", visible=False, interactive=False)
|
| | summary_download_2 = gr.File(label="", visible=False, interactive=False)
|
| | summary_download_3 = gr.File(label="", visible=False, interactive=False)
|
| | summary_download_4 = gr.File(label="", visible=False, interactive=False)
|
| | summary_download_5 = gr.File(label="", visible=False, interactive=False)
|
| |
|
| |
|
| | with gr.Tab("π Privacy & Data"):
|
| | gr.Markdown("### π Enhanced GDPR & Data Protection")
|
| | gr.Markdown("Manage your personal data and privacy settings for both transcription and AI services in compliance with data protection regulations.")
|
| |
|
| | with gr.Column(elem_classes=["pdpa-section"]):
|
| | gr.Markdown("#### π Complete Data Export")
|
| | gr.Markdown("Download all your personal data including transcriptions, AI summaries, account info, and usage statistics.")
|
| |
|
| | export_btn = gr.Button("π¦ Export My Complete Data", variant="primary")
|
| | export_status = gr.Textbox(
|
| | label="",
|
| | show_label=False,
|
| | interactive=False,
|
| | placeholder="Click 'Export My Complete Data' to download your comprehensive data archive (transcripts + AI summaries)"
|
| | )
|
| | export_file = gr.File(
|
| | label="Your Complete Data Export",
|
| | visible=False,
|
| | interactive=False
|
| | )
|
| |
|
| | with gr.Column(elem_classes=["pdpa-section"]):
|
| | gr.Markdown("#### π§ Marketing Consent")
|
| | gr.Markdown("Update your preferences for receiving marketing communications about new AI features.")
|
| |
|
| | marketing_consent_checkbox = gr.Checkbox(
|
| | label="I consent to receiving marketing communications about new AI features",
|
| | value=False
|
| | )
|
| | update_consent_btn = gr.Button("β
Update Consent", variant="secondary")
|
| | consent_status = gr.Textbox(
|
| | label="",
|
| | show_label=False,
|
| | interactive=False,
|
| | placeholder="Update your marketing consent preferences"
|
| | )
|
| |
|
| | with gr.Column(elem_classes=["pdpa-section"]):
|
| | gr.Markdown("#### β οΈ Complete Account Deletion")
|
| | gr.Markdown("""
|
| | **Warning**: This action is irreversible and will permanently delete:
|
| | - Your user account and profile
|
| | - All transcription history and files
|
| | - All AI summary history and results
|
| | - All data stored in Azure Blob Storage
|
| | - Usage statistics and preferences
|
| | - Any stored AI model interactions
|
| | """)
|
| |
|
| | deletion_confirmation = gr.Textbox(
|
| | label="Type 'DELETE MY ACCOUNT' to confirm",
|
| | placeholder="Type the exact phrase to confirm complete account deletion"
|
| | )
|
| | delete_account_btn = gr.Button(
|
| | "ποΈ Delete My Complete Account",
|
| | variant="stop",
|
| | elem_classes=["danger-button"]
|
| | )
|
| | deletion_status = gr.Textbox(
|
| | label="",
|
| | show_label=False,
|
| | interactive=False,
|
| | placeholder="Complete account deletion requires confirmation text"
|
| | )
|
| |
|
| |
|
| | transcript_timer = gr.Timer(10.0)
|
| | ai_timer = gr.Timer(10.0)
|
| |
|
| |
|
| |
|
| |
|
| | login_btn.click(
|
| | login_user,
|
| | inputs=[login_email, login_password],
|
| | outputs=[login_status, current_user, auth_section, main_app, user_stats_display]
|
| | ).then(
|
| | on_user_login,
|
| | inputs=[current_user],
|
| | outputs=[marketing_consent_checkbox]
|
| | ).then(
|
| | lambda user: ("", "") if user else (gr.update(), gr.update()),
|
| | inputs=[current_user],
|
| | outputs=[login_email, login_password]
|
| | ).then(
|
| | get_available_transcripts,
|
| | inputs=[current_user],
|
| | outputs=[available_transcripts]
|
| | )
|
| |
|
| | register_btn.click(
|
| | register_user,
|
| | inputs=[reg_email, reg_username, reg_password, reg_confirm_password,
|
| | gdpr_consent, data_retention_consent, marketing_consent],
|
| | outputs=[register_status, login_after_register]
|
| | ).then(
|
| | lambda status: ("", "", "", "", False, False, False) if "β
" in status else (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()),
|
| | inputs=[register_status],
|
| | outputs=[reg_email, reg_username, reg_password, reg_confirm_password, gdpr_consent, data_retention_consent, marketing_consent]
|
| | )
|
| |
|
| | login_after_register.click(
|
| | lambda: (gr.update(selected=0), ""),
|
| | outputs=[auth_tabs, register_status]
|
| | )
|
| |
|
| | logout_btn.click(
|
| | logout_user,
|
| | outputs=[current_user, login_status, auth_section, main_app, user_stats_display]
|
| | )
|
| |
|
| |
|
| | submit_btn.click(
|
| | submit_transcription,
|
| | inputs=[
|
| | file_upload, language, audio_format, diarization_enabled,
|
| | speakers, profanity, punctuation, timestamps, lexical, current_user
|
| | ],
|
| | outputs=[status_display, transcript_output, download_file, job_info, job_state, auto_refresh_status_display, user_stats_display]
|
| | )
|
| |
|
| | refresh_btn.click(
|
| | check_current_job_status,
|
| | inputs=[job_state, current_user],
|
| | outputs=[status_display, transcript_output, download_file, job_info, auto_refresh_status_display, user_stats_display]
|
| | )
|
| |
|
| |
|
| | refresh_transcripts_btn.click(
|
| | lambda user: gr.update(choices=[(job.original_filename + f" ({job.created_at[:16]})", job.job_id)
|
| | for job in transcription_manager.get_user_history(user.user_id, limit=50)
|
| | if job.status == 'completed' and job.transcript_text] if user else []),
|
| | inputs=[current_user],
|
| | outputs=[available_transcripts]
|
| | )
|
| |
|
| | generate_summary_btn.click(
|
| | submit_ai_summary_enhanced,
|
| | inputs=[
|
| | available_transcripts, new_audio_video_file, document_image_files,
|
| | ai_instructions, summary_format, output_language, focus_areas,
|
| | include_timestamps, include_action_items, current_user
|
| | ],
|
| | outputs=[ai_status_display, ai_summary_output, ai_download_file, ai_job_info, summary_job_state, ai_auto_refresh_status, user_stats_display]
|
| | )
|
| |
|
| | check_ai_status_btn.click(
|
| | check_ai_summary_status,
|
| | inputs=[summary_job_state, current_user],
|
| | outputs=[ai_status_display, ai_summary_output, ai_download_file, ai_job_info, ai_auto_refresh_status, user_stats_display]
|
| | )
|
| |
|
| |
|
| | transcript_timer.tick(
|
| | auto_refresh_status,
|
| | inputs=[job_state, current_user],
|
| | outputs=[status_display, transcript_output, download_file, job_info, auto_refresh_status_display, user_stats_display]
|
| | )
|
| |
|
| | ai_timer.tick(
|
| | auto_refresh_ai_summary,
|
| | inputs=[summary_job_state, current_user],
|
| | outputs=[ai_status_display, ai_summary_output, ai_download_file, ai_job_info, ai_auto_refresh_status, user_stats_display]
|
| | )
|
| |
|
| |
|
| | refresh_transcription_history_btn.click(
|
| | refresh_transcription_history,
|
| | inputs=[current_user, show_all_transcriptions_checkbox],
|
| | outputs=[transcription_history_table, user_stats_display, transcript_download_1, transcript_download_2, transcript_download_3, transcript_download_4, transcript_download_5]
|
| | )
|
| |
|
| | show_all_transcriptions_checkbox.change(
|
| | refresh_transcription_history,
|
| | inputs=[current_user, show_all_transcriptions_checkbox],
|
| | outputs=[transcription_history_table, user_stats_display, transcript_download_1, transcript_download_2, transcript_download_3, transcript_download_4, transcript_download_5]
|
| | )
|
| |
|
| | refresh_ai_summary_history_btn.click(
|
| | refresh_ai_summary_history,
|
| | inputs=[current_user, show_all_summaries_checkbox],
|
| | outputs=[ai_summary_history_table, user_stats_display, summary_download_1, summary_download_2, summary_download_3, summary_download_4, summary_download_5]
|
| | )
|
| |
|
| | show_all_summaries_checkbox.change(
|
| | refresh_ai_summary_history,
|
| | inputs=[current_user, show_all_summaries_checkbox],
|
| | outputs=[ai_summary_history_table, user_stats_display, summary_download_1, summary_download_2, summary_download_3, summary_download_4, summary_download_5]
|
| | )
|
| |
|
| |
|
| | export_btn.click(
|
| | export_user_data,
|
| | inputs=[current_user],
|
| | outputs=[export_status, export_file]
|
| | )
|
| |
|
| | update_consent_btn.click(
|
| | update_marketing_consent,
|
| | inputs=[current_user, marketing_consent_checkbox],
|
| | outputs=[consent_status]
|
| | )
|
| |
|
| | delete_account_btn.click(
|
| | delete_user_account,
|
| | inputs=[current_user, deletion_confirmation],
|
| | outputs=[deletion_status, current_user, auth_section, main_app]
|
| | )
|
| |
|
| |
|
| | diarization_enabled.change(
|
| | lambda enabled: gr.update(visible=enabled),
|
| | inputs=[diarization_enabled],
|
| | outputs=[speakers]
|
| | )
|
| |
|
| |
|
| | demo.load(
|
| | lambda: (
|
| | print("π Azure-Powered AI Conference Service Started..."),
|
| | get_user_stats_display(None)
|
| | )[1],
|
| | outputs=[user_stats_display]
|
| | )
|
| |
|
| |
|
| | with demo:
|
| | gr.HTML("""
|
| | <div style="background: linear-gradient(135deg, #ffffff, #f8f9fa); border: 2px solid #007bff; border-radius: 16px; padding: 25px; margin-top: 20px; color: #212529; box-shadow: 0 4px 12px rgba(0,123,255,0.1);">
|
| | <h3 style="color: #007bff; margin-top: 0; font-size: 1.5em;">π How to Use the Advanced AI Service</h3>
|
| | <ol style="line-height: 1.8; font-size: 14px;">
|
| | <li><strong>π Register/Login:</strong> Create an account or log in with existing credentials</li>
|
| | <li><strong>ποΈ Transcribe:</strong> Upload audio/video files for high-quality transcription with speaker identification</li>
|
| | <li><strong>π€ AI Analysis:</strong> Choose from 3 content types: existing transcripts, new audio/video, or OCR images</li>
|
| | <li><strong>π Monitor:</strong> Status auto-updates every 10 seconds with real-time progress</li>
|
| | <li><strong>π₯ Download:</strong> Get transcripts and AI summaries with comprehensive insights</li>
|
| | <li><strong>π Manage:</strong> Use Privacy & Data tab to export or delete all your data</li>
|
| | </ol>
|
| |
|
| | <h3 style="color: #007bff; font-size: 1.4em;">π Enhanced AI Summary Features</h3>
|
| | <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin: 15px 0;">
|
| | <div>
|
| | <p><strong>π Existing Transcripts:</strong> Select from your completed transcriptions</p>
|
| | <p><strong>π₯ New Audio/Video:</strong> Upload files for transcription + AI analysis</p>
|
| | </div>
|
| | <div>
|
| | <p><strong>πΌοΈ OCR Images:</strong> Extract text from images using computer vision</p>
|
| | <p><strong>πΉπ Thai Default:</strong> Optimized for Thai language processing</p>
|
| | </div>
|
| | <div>
|
| | <p><strong>π Separate History:</strong> Independent tracking for each service</p>
|
| | <p><strong>π Auto-Refresh:</strong> Real-time status updates for all processes</p>
|
| | </div>
|
| | </div>
|
| |
|
| | <h3 style="color: #007bff; font-size: 1.4em;">π΅ Enhanced File Support</h3>
|
| | <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 15px 0;">
|
| | <div>
|
| | <p><strong>πΉ Video:</strong> MP4, MOV, AVI, MKV, WMV, FLV, 3GP, WebM</p>
|
| | <p><strong>π΅ Audio:</strong> WAV, MP3, OGG, OPUS, FLAC, WMA, AAC, M4A</p>
|
| | </div>
|
| | <div>
|
| | <p><strong>π Documents:</strong> PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, CSV, TXT</p>
|
| | <p><strong>πΌοΈ Images:</strong> JPG, JPEG, PNG, BMP, GIF, TIFF, WebP (with OCR)</p>
|
| | </div>
|
| | </div>
|
| |
|
| | <h3 style="color: #007bff; font-size: 1.4em;">π‘ Pro Tips for Best Results</h3>
|
| | <div style="background: linear-gradient(135deg, #e8f4f8, #d4e8fc); border-left: 4px solid #007bff; padding: 15px; border-radius: 8px; margin: 15px 0;">
|
| | <ul style="line-height: 1.7; font-size: 13px; margin: 0;">
|
| | <li><strong>ποΈ Transcription:</strong> WAV files process fastest, enable speaker ID for meetings</li>
|
| | <li><strong>π€ AI Summaries:</strong> Provide detailed Thai instructions for better results</li>
|
| | <li><strong>π Use Tabs:</strong> Switch between content types easily with the new tab interface</li>
|
| | <li><strong>πΌοΈ OCR Processing:</strong> Upload clear images for best text extraction results</li>
|
| | <li><strong>π History:</strong> Use separate tabs to track transcription and AI summary history</li>
|
| | <li><strong>πΉπ Thai Language:</strong> Both services now default to Thai for optimal local use</li>
|
| | <li><strong>π Auto-Updates:</strong> Let the system update automatically during processing</li>
|
| | <li><strong>π Privacy:</strong> Export your data regularly and manage consent preferences</li>
|
| | </ul>
|
| | </div>
|
| |
|
| | <div style="text-align: center; margin-top: 20px; padding: 15px; background: linear-gradient(135deg, #007bff, #28a745); color: white; border-radius: 8px;">
|
| | <p style="margin: 0; font-weight: 600; font-size: 14px;">π Powered by Azure AI Foundry | π Enterprise-Grade Security | π GDPR Compliant | πΉπ Thai Language Optimized</p>
|
| | </div>
|
| | </div>
|
| | """)
|
| |
|
| | if __name__ == "__main__":
|
| | print("π Starting Advanced Azure-Powered AI Conference Service...")
|
| | demo.launch(
|
| | server_name="0.0.0.0",
|
| | server_port=7860,
|
| | share=False,
|
| | show_error=True
|
| | ) |