import gradio as gr import pandas as pd import os from database import ( fetch_all_faq_metadata, fetch_all_podcast_metadata, add_faq_entry, update_faq_entry, delete_faq_entry, bulk_update_faqs, bulk_update_podcasts ) from utils import recalculate_all_embeddings from config import OPENAI_API_KEY from dotenv import load_dotenv # Load environment variables load_dotenv() # Basic Admin Credentials (MUST be set in Hugging Face Secrets or .env) ADMIN_USER = os.environ.get("ADMIN_USER", "admin") ADMIN_PASS = os.environ.get("ADMIN_PASS") if not ADMIN_PASS: raise ValueError("CRITICAL SECURITY ERROR: ADMIN_PASS environment variable is not set.") # Path to SQLite DB — override via DB_PATH env var if your file has a different name DB_PATH = os.environ.get("DB_PATH", "getscene_ai.sqlite") # ── Helper functions ────────────────────────────────────────────────────────── def get_faqs(): data = fetch_all_faq_metadata() return pd.DataFrame(data) def get_podcasts(): data = fetch_all_podcast_metadata() return pd.DataFrame(data) def handle_faq_upload(file): if file is None: return "No file uploaded." try: df = pd.read_csv(file.name) if file.name.endswith('.csv') else pd.read_excel(file.name) bulk_update_faqs(df.to_dict('records')) return f"Successfully uploaded {len(df)} FAQs. Don't forget to Sync & Embed!" except Exception as e: return f"Error: {e}" def handle_podcast_upload(file): if file is None: return "No file uploaded." try: df = pd.read_csv(file.name) if file.name.endswith('.csv') else pd.read_excel(file.name) bulk_update_podcasts(df.to_dict('records')) return f"Successfully uploaded {len(df)} Podcasts. Don't forget to Sync & Embed!" except Exception as e: return f"Error: {e}" def run_sync(): try: recalculate_all_embeddings() return "Sync Complete! All missing embeddings have been generated." except Exception as e: return f"Sync Failed: {e}" def prepare_download(): """Return the SQLite file path for Gradio to serve as a download.""" if not os.path.exists(DB_PATH): return None, f"❌ Database file not found at: `{DB_PATH}`. Check your DB_PATH env var." size_mb = os.path.getsize(DB_PATH) / (1024 * 1024) return DB_PATH, f"✅ Ready — `{os.path.basename(DB_PATH)}` ({size_mb:.2f} MB). Click the file below to save it." def check_login(username, password): if username == ADMIN_USER and password == ADMIN_PASS: return ( gr.update(visible=False), # Hide login panel gr.update(visible=True), # Show dashboard "" # Clear error ) else: return ( gr.update(visible=True), gr.update(visible=False), "❌ Invalid username or password." ) def logout(): return ( gr.update(visible=True), # Show login panel gr.update(visible=False), # Hide dashboard "", # Clear username field "" # Clear password field ) # ── UI ──────────────────────────────────────────────────────────────────────── with gr.Blocks(title="Get Scene Admin Dashboard", theme=gr.themes.Base()) as demo: # ── LOGIN PANEL ────────────────────────────────────────────── with gr.Column(visible=True, elem_id="login_panel") as login_panel: gr.Markdown("# Get Scene Admin\nPlease log in to continue.") login_user = gr.Textbox(label="Username", placeholder="Enter username") login_pass = gr.Textbox(label="Password", placeholder="Enter password", type="password") login_error = gr.Markdown("") login_btn = gr.Button("Log In", variant="primary") # ── DASHBOARD (hidden until login) ─────────────────────────── with gr.Column(visible=False) as dashboard: with gr.Row(): gr.Markdown("# Get Scene Admin Dashboard") logout_btn = gr.Button("Log Out", variant="stop", scale=0) gr.Markdown("Manage FAQs, Podcasts, and Knowledge Embeddings.") with gr.Tabs(): # ── Tab 1: FAQs ────────────────────────────────────── with gr.TabItem("Manage FAQs"): with gr.Row(): faq_df = gr.Dataframe( value=get_faqs, headers=["id", "question", "answer"], datatype=["number", "str", "str"], interactive=True, label="FAQ Database" ) with gr.Row(): with gr.Column(): gr.Markdown("### Add New FAQ") new_q = gr.Textbox(label="Question") new_a = gr.TextArea(label="Answer") add_btn = gr.Button("Add Entry", variant="primary") with gr.Column(): gr.Markdown("### Bulk Upload") faq_file = gr.File(label="Upload CSV/Excel (Columns: Question, Answer)") upload_faq_btn = gr.Button("Bulk Upload FAQs") faq_upload_status = gr.Textbox(label="Status", interactive=False) def add_and_refresh(q, a): add_faq_entry(q, a) return get_faqs(), "", "" add_btn.click(add_and_refresh, [new_q, new_a], [faq_df, new_q, new_a]) upload_faq_btn.click(handle_faq_upload, [faq_file], [faq_upload_status]) # ── Tab 2: Podcasts ────────────────────────────────── with gr.TabItem("Podcasts"): pod_df = gr.Dataframe( value=get_podcasts, headers=["id", "guest_name", "youtube_url", "summary"], datatype=["number", "str", "str", "str"], label="Podcast Episodes" ) gr.Markdown("### Bulk Upload Podcasts") pod_file = gr.File(label="Upload CSV/Excel (Columns: Guest Name, YouTube URL, Summary)") upload_pod_btn = gr.Button("Bulk Upload Podcasts") pod_upload_status = gr.Textbox(label="Status", interactive=False) upload_pod_btn.click(handle_podcast_upload, [pod_file], [pod_upload_status]) # ── Tab 3: Sync & Embed ────────────────────────────── with gr.TabItem("Sync & Embed"): gr.Markdown("### Recalculate Embeddings") gr.Markdown( "When you change text or upload new data, the 'embeddings' (AI understanding) " "must be recalculated for the chatbot to recognize the new information." ) sync_btn = gr.Button("🔄 Sync & Recalculate Embeddings", variant="primary", scale=2) sync_status = gr.Textbox(label="Sync Status", interactive=False) sync_btn.click(run_sync, None, [sync_status]) # ── Tab 4: Download Database ───────────────────────── with gr.TabItem("⬇️ Download Database"): gr.Markdown("### Download SQLite Database") gr.Markdown( "Click the button below to download the raw `database.db` SQLite file." ) download_btn = gr.Button("⬇️ Download database.db", variant="primary") download_status = gr.Markdown("") db_file_output = gr.File( label="📦 database.db — click to save", visible=False, interactive=False ) def on_download_click(): path, msg = prepare_download() if path: return gr.update(value=path, visible=True), msg else: return gr.update(visible=False), msg download_btn.click( on_download_click, inputs=None, outputs=[db_file_output, download_status] ) # ── WIRING ──────────────────────────────────────────────────── login_btn.click( check_login, inputs=[login_user, login_pass], outputs=[login_panel, dashboard, login_error] ) login_pass.submit( check_login, inputs=[login_user, login_pass], outputs=[login_panel, dashboard, login_error] ) logout_btn.click( logout, inputs=None, outputs=[login_panel, dashboard, login_user, login_pass] ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0")