import gradio as gr from huggingface_hub import list_bucket_tree, download_bucket_files, login import pandas as pd import json import os import tempfile from dotenv import load_dotenv load_dotenv() BUCKET_NAME = os.getenv("BUCKET_NAME") HF_TOKEN = os.getenv("HF_TOKEN") if HF_TOKEN: login(token=HF_TOKEN) PAGE_SIZE = 5 def extract_customer_data(): try: if not BUCKET_NAME: print("❌ BUCKET_NAME missing") return pd.DataFrame() tree = list_bucket_tree( BUCKET_NAME, recursive=True, token=HF_TOKEN ) json_files = [ item.path for item in tree if item.path.endswith("cameras.json") ] if not json_files: return pd.DataFrame(columns=["customer_id", "customer_name", "customer_email"]) tmp_dir = tempfile.gettempdir() files_map = [ (f, os.path.join(tmp_dir, f.replace("/", "_"))) for f in json_files ] download_bucket_files( BUCKET_NAME, files=files_map, token=HF_TOKEN ) rows = [] for remote_path, local_path in files_map: parts = remote_path.split("/") customer_id = parts[1] if len(parts) > 1 else "unknown" try: with open(local_path, "r", encoding="utf-8") as f: data = json.load(f) for item in data: rows.append({ "customer_id": customer_id, "customer_name": item.get("customer_name"), "customer_email": item.get("customer_email") }) except Exception as e: print(f"File error {remote_path}: {e}") df = pd.DataFrame(rows) if not df.empty: df = df.drop_duplicates() return df except Exception as e: print("❌ BUCKET ERROR:", e) return pd.DataFrame(columns=["customer_id", "customer_name", "customer_email"]) def safe_load(): try: return extract_customer_data() except Exception as e: print("❌ SAFE LOAD ERROR:", e) return pd.DataFrame(columns=["customer_id", "customer_name", "customer_email"]) df_global = safe_load() def apply_search(df, email_query): if df is None or df.empty: return df if email_query: df = df[df["customer_email"].astype(str).str.contains(email_query, case=False, na=False)] return df def build_view(df, page, email_query): filtered = apply_search(df, email_query) total_pages = max(1, (len(filtered) - 1) // PAGE_SIZE + 1) page = max(0, min(page, total_pages - 1)) start = page * PAGE_SIZE end = start + PAGE_SIZE paged = filtered.iloc[start:end] status = f"Page {page+1}/{total_pages} | Total: {len(filtered)}" return paged, page, status with gr.Blocks() as app: gr.Markdown("# 🛰 Buck Tracker Customer Dashboard") state_df = gr.State(df_global) state_page = gr.State(0) search_box = gr.Textbox(label="🔍 Search Email") table = gr.Dataframe( value=build_view(df_global, 0, "")[0], interactive=False ) status = gr.Textbox(interactive=False) with gr.Row(): refresh_btn = gr.Button("🔄 Refresh") prev_btn = gr.Button("⬅️") next_btn = gr.Button("➡️") def refresh(email): df = safe_load() table, page, status_text = build_view(df, 0, email) return table, 0, status_text, df def next_page(df, page, email): return *build_view(df, page + 1, email), df def prev_page(df, page, email): return *build_view(df, page - 1, email), df search_box.change( fn=lambda df, q: (*build_view(df, 0, q), df), inputs=[state_df, search_box], outputs=[table, state_page, status, state_df] ) refresh_btn.click( fn=refresh, inputs=[search_box], outputs=[table, state_page, status, state_df] ) next_btn.click( fn=next_page, inputs=[state_df, state_page, search_box], outputs=[table, state_page, status, state_df] ) prev_btn.click( fn=prev_page, inputs=[state_df, state_page, search_box], outputs=[table, state_page, status, state_df] ) app.queue() app.launch()