""" app.py ────────────────────────────────────────────────────────────────────────────── VoiceVerse Pro — Streamlit Orchestrator (2026 Stable Build) This file is intentionally thin. Its only jobs are: 1. Configure the Streamlit page 2. Inject global CSS 3. Render the header and stage tracker 4. Delegate every pipeline stage to its own UI module 5. Provide a debug panel for development All business logic lives in modules/ All UI rendering logic lives in ui/ Pipeline flow: ui.sidebar → SidebarConfig ui.stage_upload → PipelineState.stage 0 → 1 ui.stage_retrieve → PipelineState.stage 1 → 2 ui.stage_generate → PipelineState.stage 2 → 3 ui.stage_audio → PipelineState.stage 3 → 4 """ import logging import os import sys # ── Path fix: ensure the project root is on sys.path regardless of where # `streamlit run` is invoked from. Without this, `import ui` and # `import modules` fail when the CWD is not the project root. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import streamlit as st from dotenv import load_dotenv load_dotenv() # loads .env → os.environ before any module reads env vars from ui import ( get_pipeline_state, inject_css, render_header, render_stage_tracker, render_mode_selector, sidebar, stage_upload, stage_retrieve, stage_generate, stage_audio, ) # ── Logging ─────────────────────────────────────────────────────────────────── logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", ) # ── Page config (must be first Streamlit call) ──────────────────────────────── st.set_page_config( page_title="VoiceVerse Pro", page_icon="🎙️", layout="wide", initial_sidebar_state="expanded", ) # ── Global styles ───────────────────────────────────────────────────────────── inject_css() # ── Session state ───────────────────────────────────────────────────────────── state = get_pipeline_state() # ── Sidebar → typed SidebarConfig ──────────────────────────────────────────── config = sidebar.render(current_stage=state.stage) # ── Header + stage tracker ──────────────────────────────────────────────────── render_header() render_stage_tracker(state.stage) # ── Mode selector (prominent main-area toggle) ──────────────────────────────── selected_mode = render_mode_selector() # Sync the session-state selection back into config so all stages see it from ui.state import OutputMode config.output_mode = OutputMode(selected_mode) # ── Two-column layout ───────────────────────────────────────────────────────── col_left, col_right = st.columns([1, 1.3], gap="large") with col_left: stage_upload.render(state, config) stage_retrieve.render(state, config) with col_right: stage_generate.render(state, config) stage_audio.render(state, config) # ── Debug panel ─────────────────────────────────────────────────────────────── st.divider() with st.expander("🛠️ Debug & System Info", expanded=False): import sys, platform st.markdown( f"**Python:** `{sys.version}` \n" f"**Platform:** `{platform.platform()}` \n" f"**Pipeline Stage:** `{state.stage}` \n" f"**Chunks Indexed:** `{state.total_chunks}` \n" f"**Context Retrieved:** `{state.has_context}` \n" f"**Script Generated:** `{state.has_script}` \n" f"**Audio Ready:** `{state.has_audio}`" ) if st.button("🔄 Reset All State"): for k in list(st.session_state.keys()): del st.session_state[k] st.rerun()