Spaces:
Sleeping
Sleeping
# ui/callbacks.py | |
import gradio as gr | |
import pandas as pd | |
import logging | |
from threading import Thread | |
from core.analyzer import DataAnalyzer | |
from core.llm import GeminiNarrativeGenerator | |
from core.config import settings | |
from core.exceptions import APIKeyMissingError, DataProcessingError | |
from modules.clustering import perform_clustering | |
# ... other module imports | |
def register_callbacks(components): | |
"""Binds event handlers to the UI components.""" | |
# --- Main Analysis Trigger --- | |
def run_full_analysis(file_obj): | |
# 1. Input Validation | |
if file_obj is None: | |
raise gr.Error("No file uploaded. Please upload a CSV or Excel file.") | |
if not settings.GOOGLE_API_KEY: | |
raise APIKeyMissingError("CRITICAL: GOOGLE_API_KEY not found in .env file.") | |
try: | |
# 2. Data Loading & Pre-processing | |
logging.info(f"Processing uploaded file: {file_obj.name}") | |
df = pd.read_csv(file_obj.name) if file_obj.name.endswith('.csv') else pd.read_excel(file_obj.name) | |
if len(df) > settings.MAX_UI_ROWS: | |
df = df.sample(n=settings.MAX_UI_ROWS, random_state=42) | |
# 3. Core Analysis | |
analyzer = DataAnalyzer(df) | |
meta = analyzer.metadata | |
missing_df, num_df, cat_df = analyzer.get_profiling_reports() | |
fig_types, fig_missing, fig_corr = analyzer.get_overview_visuals() | |
# 4. Asynchronous AI Narrative Generation | |
ai_report_queue = [""] # Use a mutable list to pass string by reference | |
def generate_ai_report_threaded(analyzer_instance): | |
narrative_generator = GeminiNarrativeGenerator(api_key=settings.GOOGLE_API_KEY) | |
ai_report_queue[0] = narrative_generator.generate_narrative(analyzer_instance) | |
thread = Thread(target=generate_ai_report_threaded, args=(analyzer,)) | |
thread.start() | |
# 5. Prepare Initial UI Updates (Instantaneous) | |
updates = { | |
components["state_analyzer"]: analyzer, | |
components["ai_report_output"]: "⏳ Generating AI-powered report... This may take a moment.", | |
components["profile_missing_df"]: gr.update(value=missing_df), | |
components["profile_numeric_df"]: gr.update(value=num_df), | |
components["profile_categorical_df"]: gr.update(value=cat_df), | |
components["plot_types"]: gr.update(value=fig_types), | |
components["plot_missing"]: gr.update(value=fig_missing), | |
components["plot_correlation"]: gr.update(value=fig_corr), | |
# ... update dropdowns and visibility ... | |
components["dd_hist_col"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None), | |
components["dd_scatter_x"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None), | |
components["dd_scatter_y"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][1] if len(meta['numeric_cols']) > 1 else None), | |
components["dd_scatter_color"]: gr.update(choices=meta['columns']), | |
components["tab_timeseries"]: gr.update(visible=bool(meta['datetime_cols'])), | |
components["tab_text"]: gr.update(visible=bool(meta['text_cols'])), | |
components["tab_cluster"]: gr.update(visible=len(meta['numeric_cols']) > 1), | |
} | |
yield updates | |
# 6. Final UI Update (When AI report is ready) | |
thread.join() # Wait for the AI thread to finish | |
updates[components["ai_report_output"]] = ai_report_queue[0] | |
yield updates | |
except (DataProcessingError, APIKeyMissingError) as e: | |
logging.error(f"User-facing error: {e}", exc_info=True) | |
raise gr.Error(str(e)) | |
except Exception as e: | |
logging.error(f"A critical unhandled error occurred: {e}", exc_info=True) | |
raise gr.Error(f"Analysis Failed! An unexpected error occurred: {str(e)}") | |
# Bind the main function | |
components["analyze_button"].click( | |
fn=run_full_analysis, | |
inputs=[components["upload_button"]], | |
outputs=list(components.values()) | |
) | |
# --- Clustering Tab Callback --- | |
def update_clustering(analyzer, k): | |
if not analyzer: return gr.update(), gr.update(), gr.update() | |
fig_cluster, fig_elbow, summary = perform_clustering(analyzer.df, analyzer.metadata['numeric_cols'], k) | |
return fig_cluster, fig_elbow, summary | |
components["num_clusters"].change( | |
fn=update_clustering, | |
inputs=[components["state_analyzer"], components["num_clusters"]], | |
outputs=[components["plot_cluster"], components["plot_elbow"], components["md_cluster_summary"]] | |
) | |
# ... Register other callbacks for histogram, scatter, etc. in a similar fashion ... |