Spaces:
Sleeping
Sleeping
Update ui/callbacks.py
Browse files- ui/callbacks.py +74 -33
ui/callbacks.py
CHANGED
@@ -1,4 +1,13 @@
|
|
1 |
# ui/callbacks.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import gradio as gr
|
3 |
import pandas as pd
|
4 |
import logging
|
@@ -7,54 +16,83 @@ from threading import Thread
|
|
7 |
from core.analyzer import DataAnalyzer
|
8 |
from core.llm import GeminiNarrativeGenerator
|
9 |
from core.config import settings
|
10 |
-
from core.exceptions import
|
11 |
from modules.clustering import perform_clustering
|
12 |
-
|
|
|
|
|
|
|
|
|
13 |
|
14 |
def register_callbacks(components):
|
15 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
# --- Main Analysis Trigger ---
|
18 |
-
def run_full_analysis(file_obj):
|
19 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
try:
|
26 |
-
#
|
27 |
-
|
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 |
-
|
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 = [""]
|
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.
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
components["state_analyzer"]: analyzer,
|
50 |
-
components["ai_report_output"]: "⏳ Generating AI-powered report...
|
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),
|
@@ -63,29 +101,32 @@ def register_callbacks(components):
|
|
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
|
67 |
|
68 |
-
#
|
69 |
-
thread.join()
|
70 |
-
|
71 |
-
|
|
|
|
|
72 |
|
73 |
-
except
|
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 |
-
#
|
|
|
82 |
components["analyze_button"].click(
|
83 |
-
fn=run_full_analysis,
|
84 |
-
inputs=[components["upload_button"]],
|
85 |
-
outputs=
|
86 |
)
|
87 |
|
88 |
-
# ---
|
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)
|
@@ -96,5 +137,5 @@ def register_callbacks(components):
|
|
96 |
inputs=[components["state_analyzer"], components["num_clusters"]],
|
97 |
outputs=[components["plot_cluster"], components["plot_elbow"], components["md_cluster_summary"]]
|
98 |
)
|
99 |
-
|
100 |
-
#
|
|
|
1 |
# ui/callbacks.py
|
2 |
+
|
3 |
+
# -*- coding: utf-8 -*-
|
4 |
+
#
|
5 |
+
# PROJECT: CognitiveEDA v5.0 - The QuantumLeap Intelligence Platform
|
6 |
+
#
|
7 |
+
# DESCRIPTION: The "Controller" of the application. This module contains all
|
8 |
+
# the Gradio event handlers (callbacks) that connect the UI (view)
|
9 |
+
# to the core analysis engine (model).
|
10 |
+
|
11 |
import gradio as gr
|
12 |
import pandas as pd
|
13 |
import logging
|
|
|
16 |
from core.analyzer import DataAnalyzer
|
17 |
from core.llm import GeminiNarrativeGenerator
|
18 |
from core.config import settings
|
19 |
+
from core.exceptions import DataProcessingError
|
20 |
from modules.clustering import perform_clustering
|
21 |
+
from modules.text import generate_word_cloud
|
22 |
+
from modules.timeseries import analyze_time_series
|
23 |
+
# NOTE: The UI layout file would need to be updated to pass all the individual component
|
24 |
+
# references, which is done in the create_main_layout function. This callback assumes
|
25 |
+
# a dictionary of components is passed to it.
|
26 |
|
27 |
def register_callbacks(components):
|
28 |
+
"""
|
29 |
+
Binds all event handlers (callbacks) to the UI components.
|
30 |
+
|
31 |
+
Args:
|
32 |
+
components (dict): A dictionary mapping component names to their
|
33 |
+
Gradio component objects.
|
34 |
+
"""
|
35 |
|
36 |
# --- Main Analysis Trigger ---
|
37 |
+
def run_full_analysis(file_obj, progress=gr.Progress(track_tqdm=True)):
|
38 |
+
"""
|
39 |
+
The primary orchestration function triggered by the user. It loads data,
|
40 |
+
runs the standard analysis, and spawns a thread for the AI report.
|
41 |
+
"""
|
42 |
+
# 1. Input Validation (File)
|
43 |
if file_obj is None:
|
44 |
raise gr.Error("No file uploaded. Please upload a CSV or Excel file.")
|
45 |
+
|
46 |
+
# 2. Runtime Configuration Validation (API Key)
|
47 |
+
# This is the critical fix. We check for the key here, at the point of use,
|
48 |
+
# rather than letting it crash the app at startup.
|
49 |
+
progress(0, desc="Validating configuration...")
|
50 |
if not settings.GOOGLE_API_KEY:
|
51 |
+
logging.error("Analysis attempted without GOOGLE_API_KEY set.")
|
52 |
+
raise gr.Error(
|
53 |
+
"CRITICAL: GOOGLE_API_KEY is not configured. "
|
54 |
+
"The AI Strategy Report cannot be generated. Please add the key to your "
|
55 |
+
".env file (for local development) or as a platform secret (for deployed apps) and restart."
|
56 |
+
)
|
57 |
|
58 |
try:
|
59 |
+
# 3. Data Loading & Core Analysis
|
60 |
+
progress(0.1, desc="Loading and parsing data...")
|
61 |
df = pd.read_csv(file_obj.name) if file_obj.name.endswith('.csv') else pd.read_excel(file_obj.name)
|
62 |
if len(df) > settings.MAX_UI_ROWS:
|
63 |
df = df.sample(n=settings.MAX_UI_ROWS, random_state=42)
|
64 |
+
|
65 |
+
progress(0.3, desc="Instantiating analysis engine...")
|
66 |
analyzer = DataAnalyzer(df)
|
67 |
meta = analyzer.metadata
|
|
|
|
|
68 |
|
69 |
# 4. Asynchronous AI Narrative Generation
|
70 |
+
ai_report_queue = [""] # Use a mutable list to pass string by reference
|
71 |
def generate_ai_report_threaded(analyzer_instance):
|
72 |
narrative_generator = GeminiNarrativeGenerator(api_key=settings.GOOGLE_API_KEY)
|
73 |
ai_report_queue[0] = narrative_generator.generate_narrative(analyzer_instance)
|
74 |
|
75 |
thread = Thread(target=generate_ai_report_threaded, args=(analyzer,))
|
76 |
thread.start()
|
77 |
+
|
78 |
+
# 5. Generate Standard Reports and Visuals (runs immediately)
|
79 |
+
progress(0.5, desc="Generating data profiles...")
|
80 |
+
missing_df, num_df, cat_df = analyzer.get_profiling_reports()
|
81 |
+
|
82 |
+
progress(0.7, desc="Creating overview visualizations...")
|
83 |
+
fig_types, fig_missing, fig_corr = analyzer.get_overview_visuals()
|
84 |
+
|
85 |
+
# 6. Prepare and yield initial UI updates
|
86 |
+
progress(0.9, desc="Building initial dashboard...")
|
87 |
+
initial_updates = {
|
88 |
components["state_analyzer"]: analyzer,
|
89 |
+
components["ai_report_output"]: gr.update(value="⏳ Generating AI-powered report in the background... The main dashboard is ready now."),
|
90 |
components["profile_missing_df"]: gr.update(value=missing_df),
|
91 |
components["profile_numeric_df"]: gr.update(value=num_df),
|
92 |
components["profile_categorical_df"]: gr.update(value=cat_df),
|
93 |
components["plot_types"]: gr.update(value=fig_types),
|
94 |
components["plot_missing"]: gr.update(value=fig_missing),
|
95 |
components["plot_correlation"]: gr.update(value=fig_corr),
|
|
|
96 |
components["dd_hist_col"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
|
97 |
components["dd_scatter_x"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
|
98 |
components["dd_scatter_y"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][1] if len(meta['numeric_cols']) > 1 else None),
|
|
|
101 |
components["tab_text"]: gr.update(visible=bool(meta['text_cols'])),
|
102 |
components["tab_cluster"]: gr.update(visible=len(meta['numeric_cols']) > 1),
|
103 |
}
|
104 |
+
yield initial_updates
|
105 |
|
106 |
+
# 7. Wait for AI thread and yield final update
|
107 |
+
thread.join()
|
108 |
+
progress(1.0, desc="AI Report complete!")
|
109 |
+
final_updates = initial_updates.copy()
|
110 |
+
final_updates[components["ai_report_output"]] = ai_report_queue[0]
|
111 |
+
yield final_updates
|
112 |
|
113 |
+
except DataProcessingError as e:
|
114 |
+
logging.error(f"User-facing data processing error: {e}", exc_info=True)
|
115 |
raise gr.Error(str(e))
|
116 |
except Exception as e:
|
117 |
logging.error(f"A critical unhandled error occurred: {e}", exc_info=True)
|
118 |
raise gr.Error(f"Analysis Failed! An unexpected error occurred: {str(e)}")
|
119 |
|
120 |
+
# Bind the main analysis function
|
121 |
+
# Note: `outputs` must be a list of all components being updated.
|
122 |
+
output_component_list = list(components.values())
|
123 |
components["analyze_button"].click(
|
124 |
+
fn=run_full_analysis,
|
125 |
+
inputs=[components["upload_button"]],
|
126 |
+
outputs=output_component_list
|
127 |
)
|
128 |
|
129 |
+
# --- Other Interactive Callbacks ---
|
130 |
def update_clustering(analyzer, k):
|
131 |
if not analyzer: return gr.update(), gr.update(), gr.update()
|
132 |
fig_cluster, fig_elbow, summary = perform_clustering(analyzer.df, analyzer.metadata['numeric_cols'], k)
|
|
|
137 |
inputs=[components["state_analyzer"], components["num_clusters"]],
|
138 |
outputs=[components["plot_cluster"], components["plot_elbow"], components["md_cluster_summary"]]
|
139 |
)
|
140 |
+
|
141 |
+
# (Imagine other callbacks for scatter, histogram, etc., are registered here)
|