240 lines
7.4 KiB
Python
240 lines
7.4 KiB
Python
"""
|
|
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()
|