mgbam commited on
Commit
9940006
Β·
verified Β·
1 Parent(s): 486ca98

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -128
app.py CHANGED
@@ -30,133 +30,129 @@ class DataExplorerApp:
30
  """A professional-grade, AI-powered data exploration application."""
31
 
32
  def __init__(self):
33
- """Initializes the application state and builds the UI."""
34
- self.state: Dict[str, Any] = {}
35
- self.demo = self._create_layout()
36
- self._register_event_handlers()
37
 
38
- def _create_layout(self) -> gr.Blocks:
39
- """Defines all UI components and arranges them in the layout."""
 
 
 
40
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="Professional AI Data Explorer") as demo:
41
  # --- State Management ---
42
- self.state_var = gr.State({})
43
 
44
  # --- Component Definition ---
45
  # Sidebar
46
- self.cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected", elem_id="cockpit")
47
- self.deep_dive_btn = gr.Button("πŸ” Deep Dive Builder", elem_id="deep_dive")
48
- self.copilot_btn = gr.Button("πŸ€– Chief Data Scientist", elem_id="co-pilot")
49
- self.file_input = gr.File(label="πŸ“ Upload CSV File", file_types=[".csv"])
50
- self.status_output = gr.Markdown("Status: Awaiting data...")
51
- self.api_key_input = gr.Textbox(label="πŸ”‘ Gemini API Key", type="password", placeholder="Enter key to enable AI...")
52
- self.suggestion_btn = gr.Button("Get Smart Suggestions", variant="secondary", interactive=False)
53
 
54
  # Cockpit
55
- self.rows_stat = gr.Textbox("0", interactive=False, elem_classes="stat-card-value")
56
- self.cols_stat = gr.Textbox("0", interactive=False, elem_classes="stat-card-value")
57
- self.quality_stat = gr.Textbox("0%", interactive=False, elem_classes="stat-card-value")
58
- self.time_cols_stat = gr.Textbox("0", interactive=False, elem_classes="stat-card-value")
59
- self.suggestion_buttons = [gr.Button(visible=False) for _ in range(5)]
60
 
61
  # Deep Dive
62
- self.plot_type_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram')
63
- self.x_col_dd = gr.Dropdown([], label="X-Axis / Column", interactive=False)
64
- self.y_col_dd = gr.Dropdown([], label="Y-Axis (for Scatter/Box)", visible=False, interactive=False)
65
- self.add_plot_btn = gr.Button("Add to Dashboard", variant="primary", interactive=False)
66
- self.clear_plots_btn = gr.Button("Clear Dashboard")
67
- self.dashboard_gallery = gr.Gallery(label="πŸ“Š Your Custom Dashboard", height="auto", columns=2, preview=True)
68
 
69
  # Co-pilot
70
- self.chatbot = gr.Chatbot(height=500, label="Conversation", show_copy_button=True)
71
- self.copilot_explanation = gr.Markdown(visible=False, elem_classes="explanation-block")
72
- self.copilot_code = gr.Code(language="python", visible=False, label="Executed Code")
73
- self.copilot_plot = gr.Plot(visible=False, label="Generated Visualization")
74
- self.copilot_table = gr.Dataframe(visible=False, label="Generated Table", wrap=True)
75
- self.chat_input = gr.Textbox(label="Your Question", placeholder="e.g., 'What is the relationship between age and salary?'", scale=4)
76
- self.chat_submit_btn = gr.Button("Ask AI", variant="primary")
77
 
78
  # --- Layout Arrangement ---
79
  with gr.Row():
80
  with gr.Column(scale=1, elem_classes="sidebar"):
81
- gr.Markdown("## πŸš€ AI Explorer Pro")
82
- self.cockpit_btn; self.deep_dive_btn; self.copilot_btn; gr.Markdown("---")
83
- self.file_input; self.status_output; gr.Markdown("---"); self.api_key_input; self.suggestion_btn
84
  with gr.Column(scale=4):
85
- self.welcome_page = gr.Column(visible=True)
86
- with self.welcome_page:
87
  gr.Markdown("# Welcome to the AI Data Explorer Pro\n> Please **upload a CSV file** and **enter your Gemini API key** to begin your analysis.")
88
- self.cockpit_page = gr.Column(visible=False)
89
- with self.cockpit_page:
 
 
90
  gr.Markdown("## πŸ“Š Data Cockpit: At-a-Glance Overview")
91
  with gr.Row():
92
- with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Rows</div>"); self.rows_stat
93
- with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Columns</div>"); self.cols_stat
94
- with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Data Quality</div>"); self.quality_stat
95
- with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>"); self.time_cols_stat
96
- with gr.Accordion(label="✨ AI Smart Suggestions", open=True): [btn for btn in self.suggestion_buttons]
97
- self.deep_dive_page = gr.Column(visible=False)
98
- with self.deep_dive_page:
99
- gr.Markdown("## πŸ” Deep Dive: Manual Dashboard Builder"); gr.Markdown("Construct your own visualizations to investigate specific relationships.")
100
- with gr.Row(): self.plot_type_dd; self.x_col_dd; self.y_col_dd
101
- with gr.Row(): self.add_plot_btn; self.clear_plots_btn
102
- self.dashboard_gallery
103
- self.copilot_page = gr.Column(visible=False)
104
- with self.copilot_page:
105
- gr.Markdown("## πŸ€– Chief Data Scientist: Your AI Partner"); self.chatbot
106
- with gr.Accordion("AI's Detailed Response", open=True): self.copilot_explanation; self.copilot_code; self.copilot_plot; self.copilot_table
107
- with gr.Row(): self.chat_input; self.chat_submit_btn
108
- return demo
109
 
110
- def _register_event_handlers(self):
111
- """Connects UI components to their backend logic functions."""
112
- # Navigation
113
- nav_buttons = [self.cockpit_btn, self.deep_dive_btn, self.copilot_btn]
114
- pages = [self.cockpit_page, self.deep_dive_page, self.copilot_page]
115
- for i, btn in enumerate(nav_buttons):
116
- btn.click(
117
- lambda id=btn.elem_id: self._switch_page(id), outputs=pages
118
- ).then(
119
- lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons
120
- )
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- # File Upload
123
- self.file_input.upload(self.load_and_process_file, inputs=[self.file_input], outputs=[
124
- self.state_var, self.status_output, self.welcome_page, self.cockpit_page,
125
- self.rows_stat, self.cols_stat, self.quality_stat, self.time_cols_stat,
126
- self.x_col_dd, self.y_col_dd, self.add_plot_btn
127
- ]).then(lambda: self._switch_page("cockpit"), outputs=pages) \
128
- .then(lambda: [gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")], outputs=nav_buttons)
129
 
130
- # API Key Input
131
- self.api_key_input.change(lambda x: gr.update(interactive=bool(x)), inputs=[self.api_key_input], outputs=[self.suggestion_btn])
132
 
133
- # Deep Dive Page Logic
134
- self.plot_type_dd.change(self._update_plot_controls, inputs=[self.plot_type_dd], outputs=[self.y_col_dd])
135
- self.add_plot_btn.click(self.add_plot_to_dashboard, inputs=[self.state_var, self.x_col_dd, self.y_col_dd, self.plot_type_dd], outputs=[self.state_var, self.dashboard_gallery])
136
- self.clear_plots_btn.click(self.clear_dashboard, inputs=[self.state_var], outputs=[self.state_var, self.dashboard_gallery])
137
 
138
- # Co-pilot & Suggestions
139
- self.suggestion_btn.click(self.get_ai_suggestions, inputs=[self.state_var, self.api_key_input], outputs=self.suggestion_buttons)
140
- for btn in self.suggestion_buttons:
141
- btn.click(self.handle_suggestion_click, inputs=[btn], outputs=[self.cockpit_page, self.deep_dive_page, self.copilot_page, self.chat_input]) \
142
- .then(lambda: self._switch_page("co-pilot"), outputs=pages) \
143
- .then(lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")), outputs=nav_buttons)
 
 
144
 
145
- self.chat_submit_btn.click(self.respond_to_chat, [self.state_var, self.api_key_input, self.chat_input, self.chatbot], [self.chatbot, self.copilot_explanation, self.copilot_code, self.copilot_plot, self.copilot_table]).then(lambda: "", outputs=[self.chat_input])
146
- self.chat_input.submit(self.respond_to_chat, [self.state_var, self.api_key_input, self.chat_input, self.chatbot], [self.chatbot, self.copilot_explanation, self.copilot_code, self.copilot_plot, self.copilot_table]).then(lambda: "", outputs=[self.chat_input])
147
 
148
  def launch(self):
149
  """Launches the Gradio application."""
150
  self.demo.launch(debug=True)
151
 
152
  # --- Backend Logic Methods ---
153
-
154
  def _switch_page(self, page_id: str) -> Tuple[gr.update, ...]:
155
  return gr.update(visible=page_id=="cockpit"), gr.update(visible=page_id=="deep_dive"), gr.update(visible=page_id=="co-pilot")
156
 
157
  def _update_plot_controls(self, plot_type: str) -> gr.update:
158
- is_bivariate = plot_type in ['scatter', 'box']
159
- return gr.update(visible=is_bivariate)
160
 
161
  def load_and_process_file(self, file_obj: Any) -> Tuple[Any, ...]:
162
  try:
@@ -168,15 +164,13 @@ class DataExplorerApp:
168
  metadata = self._extract_dataset_metadata(df)
169
  state = {'df': df, 'metadata': metadata, 'dashboard_plots': []}
170
  status_msg = f"βœ… **{os.path.basename(file_obj.name)}** loaded."
171
-
172
  rows, cols, quality = metadata['shape'][0], metadata['shape'][1], metadata['data_quality']
173
 
174
  return (state, status_msg, gr.update(visible=False), gr.update(visible=True),
175
  f"{rows:,}", f"{cols}", f"{quality}%", f"{len(metadata['datetime_cols'])}",
176
  gr.update(choices=metadata['columns'], interactive=True), gr.update(choices=metadata['columns'], interactive=True), gr.update(interactive=True))
177
  except Exception as e:
178
- gr.Error(f"File Load Error: {e}")
179
- return {}, f"❌ Error: {e}", gr.update(visible=True), gr.update(visible=False), "0", "0", "0%", "0", gr.update(choices=[], interactive=False), gr.update(choices=[], interactive=False), gr.update(interactive=False)
180
 
181
  def _extract_dataset_metadata(self, df: pd.DataFrame) -> Dict[str, Any]:
182
  rows, cols = df.shape
@@ -187,12 +181,11 @@ class DataExplorerApp:
187
  'datetime_cols': df.select_dtypes(include=['datetime64', 'datetime64[ns]']).columns.tolist(),
188
  'dtypes_head': df.head().to_string()}
189
 
190
- def add_plot_to_dashboard(self, state: Dict, x_col: str, y_col: str, plot_type: str) -> Tuple[Dict, List]:
191
  if not x_col:
192
- gr.Warning("Please select at least an X-axis column.")
193
- return state, state.get('dashboard_plots', [])
194
  df = state['df']
195
- title = f"{plot_type.capitalize()}: {y_col} by {x_col}" if y_col else f"Distribution of {x_col}"
196
  try:
197
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
198
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
@@ -201,17 +194,13 @@ class DataExplorerApp:
201
  counts = df[x_col].value_counts().nlargest(20)
202
  fig = px.bar(counts, x=counts.index, y=counts.values, title=f"Top 20 Categories for {x_col}", labels={'index': x_col, 'y': 'Count'})
203
  if fig:
204
- fig.update_layout(template="plotly_dark")
205
- state['dashboard_plots'].append(fig)
206
- gr.Info(f"Added '{title}' to the dashboard.")
207
  return state, state['dashboard_plots']
208
  except Exception as e:
209
  gr.Error(f"Plotting Error: {e}"); return state, state.get('dashboard_plots', [])
210
 
211
  def clear_dashboard(self, state: Dict) -> Tuple[Dict, List]:
212
- state['dashboard_plots'] = []
213
- gr.Info("Dashboard cleared.")
214
- return state, []
215
 
216
  def get_ai_suggestions(self, state: Dict, api_key: str) -> List[gr.update]:
217
  if not api_key: gr.Warning("API Key is required for suggestions."); return [gr.update(visible=False)]*5
@@ -227,46 +216,38 @@ class DataExplorerApp:
227
  def handle_suggestion_click(self, question: str) -> Tuple[gr.update, ...]:
228
  return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), question
229
 
230
- def respond_to_chat(self, state: Dict, api_key: str, user_message: str, history: List) -> Tuple[List, ...]:
231
  if not api_key or not state:
232
- msg = "I need a Gemini API key and a dataset to work."
233
- history.append((user_message, msg)); return history, *[gr.update(visible=False)]*4
234
 
235
  history.append((user_message, "Thinking... πŸ€”")); yield history, *[gr.update(visible=False)]*4
236
 
237
- metadata = state['metadata']
238
- prompt = f"""You are 'Chief Data Scientist', an expert AI analyst. Your goal is to answer a user's question about a pandas DataFrame (`df`) by writing and executing Python code.
239
-
240
  **Instructions:**
241
- 1. **Analyze the Request:** Understand the user's intent, even if it's vague.
242
- 2. **Choose the Best Method:** Decide if the answer is a table (e.g., `df.describe()`), a single value, or a visualization. If a plot is needed, choose the BEST plot type (e.g., 'histogram' for distribution, 'scatter' for two numerics, 'bar' for categorical counts).
243
- 3. **Formulate a Plan:** Briefly explain your plan of attack.
244
- 4. **Write Code:** Generate the Python code. Use pandas (`pd`), numpy (`np`), and plotly express (`px`).
245
- - For plots, assign to `fig` and add `template='plotly_dark'`.
246
- - For tables, assign the final DataFrame to `result_df`.
247
- 5. **Provide Insights:** After the result, give a one or two-sentence INSIGHT. What does the result mean? What is the business implication?
248
- 6. **Respond ONLY with a single JSON object with keys: "plan", "code", "insight".**
249
-
250
- **DataFrame Metadata:** {metadata['dtypes_head']}
251
  **User Question:** "{user_message}"
252
  """
253
  try:
254
  genai.configure(api_key=api_key)
255
  response_json = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text.strip().replace("```json", "").replace("```", ""))
256
- plan, code_to_run, insight = response_json.get("plan"), response_json.get("code"), response_json.get("insight")
 
257
 
258
- stdout, fig_result, df_result, error = self._safe_exec(code_to_run, {'df': state['df'], 'px': px, 'pd': pd, 'np': np})
259
-
260
  history[-1] = (user_message, f"**Plan:** {plan}")
261
-
262
  explanation = f"**Insight:** {insight}"
263
  if stdout: explanation += f"\n\n**Console Output:**\n```\n{stdout}\n```"
264
  if error: gr.Error(f"AI Code Execution Failed: {error}")
265
 
266
- yield (history, gr.update(visible=bool(explanation)), gr.update(visible=bool(code_to_run), value=code_to_run),
267
- gr.update(visible=bool(fig_result), value=fig_result), gr.update(visible=bool(df_result is not None), value=df_result))
268
  except Exception as e:
269
- history[-1] = (user_message, f"I'm sorry, I encountered an error. Please try rephrasing your question. (Error: {e})")
270
  yield history, *[gr.update(visible=False)]*4
271
 
272
  def _safe_exec(self, code_string: str, local_vars: Dict) -> Tuple[Any, ...]:
@@ -274,7 +255,7 @@ class DataExplorerApp:
274
  try:
275
  with redirect_stdout(output_buffer): exec(code_string, globals(), local_vars)
276
  return output_buffer.getvalue(), local_vars.get('fig'), local_vars.get('result_df'), None
277
- except Exception as e: return None, None, None, f"Execution Error: {str(e)}"
278
 
279
  if __name__ == "__main__":
280
  app = DataExplorerApp()
 
30
  """A professional-grade, AI-powered data exploration application."""
31
 
32
  def __init__(self):
33
+ """Initializes the application and builds the UI and event listeners."""
34
+ self.demo = self._build_ui()
 
 
35
 
36
+ def _build_ui(self) -> gr.Blocks:
37
+ """
38
+ Defines all UI components, arranges them in the layout,
39
+ and registers all event handlers within the same Blocks context.
40
+ """
41
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="Professional AI Data Explorer") as demo:
42
  # --- State Management ---
43
+ state_var = gr.State({})
44
 
45
  # --- Component Definition ---
46
  # Sidebar
47
+ cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected", elem_id="cockpit")
48
+ deep_dive_btn = gr.Button("πŸ” Deep Dive Builder", elem_id="deep_dive")
49
+ copilot_btn = gr.Button("πŸ€– Chief Data Scientist", elem_id="co-pilot")
50
+ file_input = gr.File(label="πŸ“ Upload CSV File", file_types=[".csv"])
51
+ status_output = gr.Markdown("Status: Awaiting data...")
52
+ api_key_input = gr.Textbox(label="πŸ”‘ Gemini API Key", type="password", placeholder="Enter key to enable AI...")
53
+ suggestion_btn = gr.Button("Get Smart Suggestions", variant="secondary", interactive=False)
54
 
55
  # Cockpit
56
+ rows_stat, cols_stat = gr.Textbox("0", interactive=False, elem_classes="stat-card-value"), gr.Textbox("0", interactive=False, elem_classes="stat-card-value")
57
+ quality_stat, time_cols_stat = gr.Textbox("0%", interactive=False, elem_classes="stat-card-value"), gr.Textbox("0", interactive=False, elem_classes="stat-card-value")
58
+ suggestion_buttons = [gr.Button(visible=False) for _ in range(5)]
 
 
59
 
60
  # Deep Dive
61
+ plot_type_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram')
62
+ x_col_dd = gr.Dropdown([], label="X-Axis / Column", interactive=False)
63
+ y_col_dd = gr.Dropdown([], label="Y-Axis (for Scatter/Box)", visible=False, interactive=False)
64
+ add_plot_btn = gr.Button("Add to Dashboard", variant="primary", interactive=False)
65
+ clear_plots_btn = gr.Button("Clear Dashboard")
66
+ dashboard_gallery = gr.Gallery(label="πŸ“Š Your Custom Dashboard", height="auto", columns=2, preview=True)
67
 
68
  # Co-pilot
69
+ chatbot = gr.Chatbot(height=500, label="Conversation", show_copy_button=True)
70
+ copilot_explanation = gr.Markdown(visible=False, elem_classes="explanation-block")
71
+ copilot_code = gr.Code(language="python", visible=False, label="Executed Code")
72
+ copilot_plot = gr.Plot(visible=False, label="Generated Visualization")
73
+ copilot_table = gr.Dataframe(visible=False, label="Generated Table", wrap=True)
74
+ chat_input = gr.Textbox(label="Your Question", placeholder="e.g., 'What is the relationship between age and salary?'", scale=4)
75
+ chat_submit_btn = gr.Button("Ask AI", variant="primary")
76
 
77
  # --- Layout Arrangement ---
78
  with gr.Row():
79
  with gr.Column(scale=1, elem_classes="sidebar"):
80
+ gr.Markdown("## πŸš€ AI Explorer Pro"); cockpit_btn; deep_dive_btn; copilot_btn; gr.Markdown("---")
81
+ file_input; status_output; gr.Markdown("---"); api_key_input; suggestion_btn
 
82
  with gr.Column(scale=4):
83
+ welcome_page = gr.Column(visible=True)
84
+ with welcome_page:
85
  gr.Markdown("# Welcome to the AI Data Explorer Pro\n> Please **upload a CSV file** and **enter your Gemini API key** to begin your analysis.")
86
+ gr.Image("workflow.png", show_label=False, show_download_button=False, container=False)
87
+
88
+ cockpit_page = gr.Column(visible=False)
89
+ with cockpit_page:
90
  gr.Markdown("## πŸ“Š Data Cockpit: At-a-Glance Overview")
91
  with gr.Row():
92
+ with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Rows</div>"); rows_stat
93
+ with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Columns</div>"); cols_stat
94
+ with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Data Quality</div>"); quality_stat
95
+ with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>"); time_cols_stat
96
+ with gr.Accordion(label="✨ AI Smart Suggestions", open=True): [btn for btn in suggestion_buttons]
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ deep_dive_page = gr.Column(visible=False)
99
+ with deep_dive_page:
100
+ gr.Markdown("## πŸ” Deep Dive: Manual Dashboard Builder"); gr.Markdown("Construct your own visualizations to investigate specific relationships.")
101
+ with gr.Row(): plot_type_dd; x_col_dd; y_col_dd
102
+ with gr.Row(): add_plot_btn; clear_plots_btn
103
+ dashboard_gallery
104
+
105
+ copilot_page = gr.Column(visible=False)
106
+ with copilot_page:
107
+ gr.Markdown("## πŸ€– Chief Data Scientist: Your AI Partner"); chatbot
108
+ with gr.Accordion("AI's Detailed Response", open=True): copilot_explanation; copilot_code; copilot_plot; copilot_table
109
+ with gr.Row(): chat_input; chat_submit_btn
110
+
111
+ # --- Event Handlers Registration (inside the 'with' block) ---
112
+ pages = [cockpit_page, deep_dive_page, copilot_page]
113
+ nav_buttons = [cockpit_btn, deep_dive_btn, copilot_btn]
114
+
115
+ for i, btn in enumerate(nav_buttons):
116
+ btn.click(
117
+ lambda id=btn.elem_id: self._switch_page(id), outputs=pages
118
+ ).then(
119
+ lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons
120
+ )
121
 
122
+ file_input.upload(self.load_and_process_file, inputs=[file_input], outputs=[
123
+ state_var, status_output, welcome_page, cockpit_page,
124
+ rows_stat, cols_stat, quality_stat, time_cols_stat,
125
+ x_col_dd, y_col_dd, add_plot_btn
126
+ ]).then(lambda: self._switch_page("cockpit"), outputs=pages) \
127
+ .then(lambda: [gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")], outputs=nav_buttons)
 
128
 
129
+ api_key_input.change(lambda x: gr.update(interactive=bool(x)), inputs=[api_key_input], outputs=[suggestion_btn])
 
130
 
131
+ plot_type_dd.change(self._update_plot_controls, inputs=[plot_type_dd], outputs=[y_col_dd])
132
+ add_plot_btn.click(self.add_plot_to_dashboard, inputs=[state_var, x_col_dd, y_col_dd, plot_type_dd], outputs=[state_var, dashboard_gallery])
133
+ clear_plots_btn.click(self.clear_dashboard, inputs=[state_var], outputs=[state_var, dashboard_gallery])
 
134
 
135
+ suggestion_btn.click(self.get_ai_suggestions, inputs=[state_var, api_key_input], outputs=suggestion_buttons)
136
+ for btn in suggestion_buttons:
137
+ btn.click(self.handle_suggestion_click, inputs=[btn], outputs=[cockpit_page, deep_dive_page, copilot_page, chat_input]) \
138
+ .then(lambda: self._switch_page("co-pilot"), outputs=pages) \
139
+ .then(lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")), outputs=nav_buttons)
140
+
141
+ chat_submit_btn.click(self.respond_to_chat, [state_var, api_key_input, chat_input, chatbot], [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]).then(lambda: "", outputs=[chat_input])
142
+ chat_input.submit(self.respond_to_chat, [state_var, api_key_input, chat_input, chatbot], [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]).then(lambda: "", outputs=[chat_input])
143
 
144
+ return demo
 
145
 
146
  def launch(self):
147
  """Launches the Gradio application."""
148
  self.demo.launch(debug=True)
149
 
150
  # --- Backend Logic Methods ---
 
151
  def _switch_page(self, page_id: str) -> Tuple[gr.update, ...]:
152
  return gr.update(visible=page_id=="cockpit"), gr.update(visible=page_id=="deep_dive"), gr.update(visible=page_id=="co-pilot")
153
 
154
  def _update_plot_controls(self, plot_type: str) -> gr.update:
155
+ return gr.update(visible=plot_type in ['scatter', 'box'])
 
156
 
157
  def load_and_process_file(self, file_obj: Any) -> Tuple[Any, ...]:
158
  try:
 
164
  metadata = self._extract_dataset_metadata(df)
165
  state = {'df': df, 'metadata': metadata, 'dashboard_plots': []}
166
  status_msg = f"βœ… **{os.path.basename(file_obj.name)}** loaded."
 
167
  rows, cols, quality = metadata['shape'][0], metadata['shape'][1], metadata['data_quality']
168
 
169
  return (state, status_msg, gr.update(visible=False), gr.update(visible=True),
170
  f"{rows:,}", f"{cols}", f"{quality}%", f"{len(metadata['datetime_cols'])}",
171
  gr.update(choices=metadata['columns'], interactive=True), gr.update(choices=metadata['columns'], interactive=True), gr.update(interactive=True))
172
  except Exception as e:
173
+ gr.Error(f"File Load Error: {e}"); return {}, f"❌ Error: {e}", gr.update(visible=True), gr.update(visible=False), "0", "0", "0%", "0", gr.update(choices=[], interactive=False), gr.update(choices=[], interactive=False), gr.update(interactive=False)
 
174
 
175
  def _extract_dataset_metadata(self, df: pd.DataFrame) -> Dict[str, Any]:
176
  rows, cols = df.shape
 
181
  'datetime_cols': df.select_dtypes(include=['datetime64', 'datetime64[ns]']).columns.tolist(),
182
  'dtypes_head': df.head().to_string()}
183
 
184
+ def add_plot_to_dashboard(self, state: Dict, x_col: str, y_col: Optional[str], plot_type: str) -> Tuple[Dict, List]:
185
  if not x_col:
186
+ gr.Warning("Please select at least an X-axis column."); return state, state.get('dashboard_plots', [])
 
187
  df = state['df']
188
+ title = f"{plot_type.capitalize()}: {y_col} by {x_col}" if y_col and plot_type in ['box', 'scatter'] else f"Distribution of {x_col}"
189
  try:
190
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
191
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
 
194
  counts = df[x_col].value_counts().nlargest(20)
195
  fig = px.bar(counts, x=counts.index, y=counts.values, title=f"Top 20 Categories for {x_col}", labels={'index': x_col, 'y': 'Count'})
196
  if fig:
197
+ fig.update_layout(template="plotly_dark"); state['dashboard_plots'].append(fig); gr.Info(f"Added '{title}' to the dashboard.")
 
 
198
  return state, state['dashboard_plots']
199
  except Exception as e:
200
  gr.Error(f"Plotting Error: {e}"); return state, state.get('dashboard_plots', [])
201
 
202
  def clear_dashboard(self, state: Dict) -> Tuple[Dict, List]:
203
+ state['dashboard_plots'] = []; gr.Info("Dashboard cleared."); return state, []
 
 
204
 
205
  def get_ai_suggestions(self, state: Dict, api_key: str) -> List[gr.update]:
206
  if not api_key: gr.Warning("API Key is required for suggestions."); return [gr.update(visible=False)]*5
 
216
  def handle_suggestion_click(self, question: str) -> Tuple[gr.update, ...]:
217
  return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), question
218
 
219
+ def respond_to_chat(self, state: Dict, api_key: str, user_message: str, history: List) -> Any:
220
  if not api_key or not state:
221
+ msg = "I need a Gemini API key and a dataset to work."; history.append((user_message, msg)); return history, *[gr.update(visible=False)]*4
 
222
 
223
  history.append((user_message, "Thinking... πŸ€”")); yield history, *[gr.update(visible=False)]*4
224
 
225
+ metadata, prompt = state['metadata'], f"""You are 'Chief Data Scientist', an expert AI analyst...
 
 
226
  **Instructions:**
227
+ 1. **Analyze:** Understand the user's intent.
228
+ 2. **Method:** Choose the best method (table, value, or plot). Infer the best plot type.
229
+ 3. **Plan:** Briefly explain your plan.
230
+ 4. **Code:** Write Python code. Use `fig` for plots (with `template='plotly_dark'`) and `result_df` for tables.
231
+ 5. **Insight:** Provide a one-sentence business insight.
232
+ 6. **Respond ONLY with a single JSON object with keys: "plan", "code", "insight".**
233
+ **Metadata:** {metadata['dtypes_head']}
 
 
 
234
  **User Question:** "{user_message}"
235
  """
236
  try:
237
  genai.configure(api_key=api_key)
238
  response_json = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text.strip().replace("```json", "").replace("```", ""))
239
+ plan, code, insight = response_json.get("plan"), response_json.get("code"), response_json.get("insight")
240
+ stdout, fig, df_result, error = self._safe_exec(code, {'df': state['df'], 'px': px, 'pd': pd})
241
 
 
 
242
  history[-1] = (user_message, f"**Plan:** {plan}")
 
243
  explanation = f"**Insight:** {insight}"
244
  if stdout: explanation += f"\n\n**Console Output:**\n```\n{stdout}\n```"
245
  if error: gr.Error(f"AI Code Execution Failed: {error}")
246
 
247
+ yield (history, gr.update(visible=bool(explanation), value=explanation), gr.update(visible=bool(code), value=code),
248
+ gr.update(visible=bool(fig), value=fig), gr.update(visible=bool(df_result is not None), value=df_result))
249
  except Exception as e:
250
+ history[-1] = (user_message, f"I encountered an error. Please rephrase your question. (Error: {e})")
251
  yield history, *[gr.update(visible=False)]*4
252
 
253
  def _safe_exec(self, code_string: str, local_vars: Dict) -> Tuple[Any, ...]:
 
255
  try:
256
  with redirect_stdout(output_buffer): exec(code_string, globals(), local_vars)
257
  return output_buffer.getvalue(), local_vars.get('fig'), local_vars.get('result_df'), None
258
+ except Exception as e: return None, None, None, str(e)
259
 
260
  if __name__ == "__main__":
261
  app = DataExplorerApp()