""" ScrAIbe Web GUI (Gradio) - Async Mode ------------------------------------- Runs the Web GUI that: - Accepts audio uploads - Enqueues transcription jobs asynchronously via Celery - Backend worker: - Transcribes (with diarization) - Optionally summarizes - Emails the user: - Immediately: confirmation + queue position - On success: transcript + JSON (+ summary if requested) - On error: error details This is the default entrypoint when running in Docker. """ import os import logging import shutil from datetime import datetime import gradio as gr from .misc import setup_logging logger = logging.getLogger("scraibe.webui") def load_config(): """ Load configuration from misc/config.yaml if present. Primary runtime configuration is via environment variables. """ config_path = os.getenv("SCRAIBE_CONFIG", "/app/src/misc/config.yaml") config = {} if os.path.exists(config_path): try: import yaml with open(config_path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) or {} except Exception as e: logger.warning("Failed to load config from %s: %s", config_path, e) return config def create_app(): """ Create and launch the Gradio Web GUI (async mode). """ # Logging log_level = os.getenv("LOG_LEVEL", "INFO") setup_logging(level=log_level) # Load config (branding, layout, etc.) config = load_config() layout_cfg = config.get("layout", {}) launch_cfg = config.get("launch", {}) logger.info("Starting ScrAIbe Web GUI (async mode).") # Ensure upload directory exists upload_dir = os.getenv("SCRAIBE_UPLOAD_DIR", "/tmp/scraibe_uploads") os.makedirs(upload_dir, exist_ok=True) # Load header/footer HTML if present header_path = layout_cfg.get("header", "/app/src/misc/header.html") footer_path = layout_cfg.get("footer", "/app/src/misc/footer.html") header_html = "" footer_html = "" if header_path and os.path.exists(header_path): with open(header_path, "r", encoding="utf-8") as f: header_html = f.read() if footer_path and os.path.exists(footer_path): with open(footer_path, "r", encoding="utf-8") as f: footer_html = f.read() # Build Gradio interface with gr.Blocks( title="A.P.Strom Transcription", ) as app: # Header if header_html: gr.HTML(header_html) with gr.Row(): with gr.Column(scale=2): audio_input = gr.Audio( label="Upload or record audio", type="filepath", ) with gr.Row(): task_choice = gr.Radio( choices=[ ("Transcribe", "transcribe"), ("Transcript & Summarize", "transcript_and_summarize"), ], value="transcribe", label="Task", ) with gr.Row(): language_input = gr.Textbox( label="Language (optional)", placeholder="e.g., english, german", ) num_speakers_input = gr.Number( label="Number of speakers (optional)", precision=0, ) # Email is required in async mode email_to = gr.Textbox( label="Your email address (required)", placeholder="e.g. your.name@example.com", ) email_cc = gr.Textbox( label="CC (optional, comma-separated)", placeholder="e.g. manager@example.com", ) submit_btn = gr.Button("Submit for transcription", variant="primary") with gr.Column(scale=3): status_text = gr.Textbox( label="Status", lines=6, interactive=False, ) # Footer if footer_html: gr.HTML(footer_html) # Events def on_task_change(value): # No special UI changes needed; both modes handled in backend return task_choice.change( fn=on_task_change, inputs=[task_choice], outputs=[], ) def on_submit( audio, task, language, num_speakers, email_to_val, email_cc_val, ): if not audio: return "Please upload or record audio." email_to_val = (email_to_val or "").strip() if not email_to_val: return "Please enter your email address." # Copy uploaded file to a stable location try: ext = os.path.splitext(audio)[1] or ".wav" ts = datetime.utcnow().strftime("%Y%m%d%H%M%S%f") new_name = f"upload_{ts}{ext}" dest_path = os.path.join(upload_dir, new_name) shutil.copy2(audio, dest_path) except Exception as e: logger.error("Error copying uploaded file: %s", e) return f"Error saving your file: {e}" # Import Celery task try: from .tasks import process_transcription_task except ImportError: return ( "Error: async processing is not available (Celery not configured)." ) # Enqueue transcription job try: task_result = process_transcription_task.delay( audio_path=dest_path, task_type=task, language=language or None, num_speakers=int(num_speakers) if num_speakers else None, email_to=email_to_val, email_cc=email_cc_val or None, include_summary=(task == "transcript_and_summarize"), ) except Exception as e: logger.error("Error enqueuing job: %s", e) return f"Error submitting your file: {e}" return ( "Your audio file has been received and added to the queue.\n" "We have sent a confirmation email to you.\n" "You will receive another email with your transcript (and summary, if requested) " "once processing is complete.\n" f"Job ID: {task_result.id}" ) submit_btn.click( fn=on_submit, inputs=[ audio_input, task_choice, language_input, num_speakers_input, email_to, email_cc, ], outputs=[status_text], ) # Launch options server_name = launch_cfg.get("server_name", os.getenv("GRADIO_SERVER_NAME", "0.0.0.0")) server_port = launch_cfg.get("server_port", 7860) favicon_path = launch_cfg.get("favicon_path", "/app/src/misc/logo.png") app.launch( server_name=str(server_name), server_port=int(server_port), favicon_path=favicon_path if os.path.exists(favicon_path) else None, css="body { font-family: Arial, sans-serif; }", ) if __name__ == "__main__": create_app()