Spaces:
Running
Running
| 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") |