Spaces:
Sleeping
Sleeping
Create ui/callbacks.py
Browse files- ui/callbacks.py +100 -0
ui/callbacks.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ui/callbacks.py
|
2 |
+
import gradio as gr
|
3 |
+
import pandas as pd
|
4 |
+
import logging
|
5 |
+
from threading import Thread
|
6 |
+
|
7 |
+
from core.analyzer import DataAnalyzer
|
8 |
+
from core.llm import GeminiNarrativeGenerator
|
9 |
+
from core.config import settings
|
10 |
+
from core.exceptions import APIKeyMissingError, DataProcessingError
|
11 |
+
from modules.clustering import perform_clustering
|
12 |
+
# ... other module imports
|
13 |
+
|
14 |
+
def register_callbacks(components):
|
15 |
+
"""Binds event handlers to the UI components."""
|
16 |
+
|
17 |
+
# --- Main Analysis Trigger ---
|
18 |
+
def run_full_analysis(file_obj):
|
19 |
+
# 1. Input Validation
|
20 |
+
if file_obj is None:
|
21 |
+
raise gr.Error("No file uploaded. Please upload a CSV or Excel file.")
|
22 |
+
if not settings.GOOGLE_API_KEY:
|
23 |
+
raise APIKeyMissingError("CRITICAL: GOOGLE_API_KEY not found in .env file.")
|
24 |
+
|
25 |
+
try:
|
26 |
+
# 2. Data Loading & Pre-processing
|
27 |
+
logging.info(f"Processing uploaded file: {file_obj.name}")
|
28 |
+
df = pd.read_csv(file_obj.name) if file_obj.name.endswith('.csv') else pd.read_excel(file_obj.name)
|
29 |
+
if len(df) > settings.MAX_UI_ROWS:
|
30 |
+
df = df.sample(n=settings.MAX_UI_ROWS, random_state=42)
|
31 |
+
|
32 |
+
# 3. Core Analysis
|
33 |
+
analyzer = DataAnalyzer(df)
|
34 |
+
meta = analyzer.metadata
|
35 |
+
missing_df, num_df, cat_df = analyzer.get_profiling_reports()
|
36 |
+
fig_types, fig_missing, fig_corr = analyzer.get_overview_visuals()
|
37 |
+
|
38 |
+
# 4. Asynchronous AI Narrative Generation
|
39 |
+
ai_report_queue = [""] # Use a mutable list to pass string by reference
|
40 |
+
def generate_ai_report_threaded(analyzer_instance):
|
41 |
+
narrative_generator = GeminiNarrativeGenerator(api_key=settings.GOOGLE_API_KEY)
|
42 |
+
ai_report_queue[0] = narrative_generator.generate_narrative(analyzer_instance)
|
43 |
+
|
44 |
+
thread = Thread(target=generate_ai_report_threaded, args=(analyzer,))
|
45 |
+
thread.start()
|
46 |
+
|
47 |
+
# 5. Prepare Initial UI Updates (Instantaneous)
|
48 |
+
updates = {
|
49 |
+
components["state_analyzer"]: analyzer,
|
50 |
+
components["ai_report_output"]: "⏳ Generating AI-powered report... This may take a moment.",
|
51 |
+
components["profile_missing_df"]: gr.update(value=missing_df),
|
52 |
+
components["profile_numeric_df"]: gr.update(value=num_df),
|
53 |
+
components["profile_categorical_df"]: gr.update(value=cat_df),
|
54 |
+
components["plot_types"]: gr.update(value=fig_types),
|
55 |
+
components["plot_missing"]: gr.update(value=fig_missing),
|
56 |
+
components["plot_correlation"]: gr.update(value=fig_corr),
|
57 |
+
# ... update dropdowns and visibility ...
|
58 |
+
components["dd_hist_col"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
|
59 |
+
components["dd_scatter_x"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
|
60 |
+
components["dd_scatter_y"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][1] if len(meta['numeric_cols']) > 1 else None),
|
61 |
+
components["dd_scatter_color"]: gr.update(choices=meta['columns']),
|
62 |
+
components["tab_timeseries"]: gr.update(visible=bool(meta['datetime_cols'])),
|
63 |
+
components["tab_text"]: gr.update(visible=bool(meta['text_cols'])),
|
64 |
+
components["tab_cluster"]: gr.update(visible=len(meta['numeric_cols']) > 1),
|
65 |
+
}
|
66 |
+
yield updates
|
67 |
+
|
68 |
+
# 6. Final UI Update (When AI report is ready)
|
69 |
+
thread.join() # Wait for the AI thread to finish
|
70 |
+
updates[components["ai_report_output"]] = ai_report_queue[0]
|
71 |
+
yield updates
|
72 |
+
|
73 |
+
except (DataProcessingError, APIKeyMissingError) as e:
|
74 |
+
logging.error(f"User-facing error: {e}", exc_info=True)
|
75 |
+
raise gr.Error(str(e))
|
76 |
+
except Exception as e:
|
77 |
+
logging.error(f"A critical unhandled error occurred: {e}", exc_info=True)
|
78 |
+
raise gr.Error(f"Analysis Failed! An unexpected error occurred: {str(e)}")
|
79 |
+
|
80 |
+
|
81 |
+
# Bind the main function
|
82 |
+
components["analyze_button"].click(
|
83 |
+
fn=run_full_analysis,
|
84 |
+
inputs=[components["upload_button"]],
|
85 |
+
outputs=list(components.values())
|
86 |
+
)
|
87 |
+
|
88 |
+
# --- Clustering Tab Callback ---
|
89 |
+
def update_clustering(analyzer, k):
|
90 |
+
if not analyzer: return gr.update(), gr.update(), gr.update()
|
91 |
+
fig_cluster, fig_elbow, summary = perform_clustering(analyzer.df, analyzer.metadata['numeric_cols'], k)
|
92 |
+
return fig_cluster, fig_elbow, summary
|
93 |
+
|
94 |
+
components["num_clusters"].change(
|
95 |
+
fn=update_clustering,
|
96 |
+
inputs=[components["state_analyzer"], components["num_clusters"]],
|
97 |
+
outputs=[components["plot_cluster"], components["plot_elbow"], components["md_cluster_summary"]]
|
98 |
+
)
|
99 |
+
|
100 |
+
# ... Register other callbacks for histogram, scatter, etc. in a similar fashion ...
|