mgbam commited on
Commit
1aae43a
Β·
verified Β·
1 Parent(s): 12ef31b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +45 -119
app.py CHANGED
@@ -67,12 +67,10 @@ CSS = """
67
 
68
  # --- Helper Functions ---
69
  def safe_exec(code_string: str, local_vars: dict):
70
- """Safely execute a string of Python code and capture its output."""
71
  output_buffer = io.StringIO()
72
  try:
73
  with redirect_stdout(output_buffer):
74
  exec(code_string, globals(), local_vars)
75
-
76
  stdout = output_buffer.getvalue()
77
  fig = local_vars.get('fig')
78
  result_df = local_vars.get('result_df')
@@ -82,38 +80,24 @@ def safe_exec(code_string: str, local_vars: dict):
82
 
83
  # --- Core Data Processing & State Management ---
84
  def load_and_process_file(file_obj, state_dict):
85
- """Loads a CSV, processes it, and updates the entire UI state."""
86
- if file_obj is None:
87
- return state_dict, "Please upload a file.", *[gr.update(visible=False)] * 4
88
  try:
89
  df = pd.read_csv(file_obj.name, low_memory=False)
90
  for col in df.select_dtypes(include=['object']).columns:
91
- try:
92
- df[col] = pd.to_datetime(df[col], errors='raise')
93
- except (ValueError, TypeError):
94
- continue
95
 
96
  metadata = extract_dataset_metadata(df)
97
- state_dict = {
98
- 'df': df,
99
- 'metadata': metadata,
100
- 'filename': os.path.basename(file_obj.name),
101
- 'dashboard_plots': []
102
- }
103
 
104
  status_msg = f"βœ… **{state_dict['filename']}** loaded successfully."
105
-
106
  cockpit_update = gr.update(visible=True)
107
- deep_dive_update = gr.update(visible=False)
108
- copilot_update = gr.update(visible=False)
109
  welcome_update = gr.update(visible=False)
110
-
111
  rows, cols = metadata['shape']
112
  quality = metadata['data_quality']
113
 
114
- return (state_dict, status_msg, welcome_update, cockpit_update, deep_dive_update, copilot_update,
115
- gr.update(value=f"{rows:,}"), gr.update(value=cols), gr.update(value=f"{quality}%"),
116
- gr.update(value=f"{len(metadata['datetime_cols'])}"),
117
  gr.update(choices=metadata['columns']), gr.update(choices=metadata['columns']), gr.update(choices=metadata['columns']))
118
  except Exception as e:
119
  return state_dict, f"❌ **Error:** {e}", *[gr.update()] * 11
@@ -124,22 +108,12 @@ def extract_dataset_metadata(df: pd.DataFrame):
124
  categorical_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
125
  datetime_cols = df.select_dtypes(include=['datetime64', 'datetime64[ns]']).columns.tolist()
126
  data_quality = round((df.notna().sum().sum() / (rows * cols)) * 100, 1) if rows * cols > 0 else 0
127
- return {
128
- 'shape': (rows, cols), 'columns': df.columns.tolist(),
129
- 'numeric_cols': numeric_cols, 'categorical_cols': categorical_cols,
130
- 'datetime_cols': datetime_cols, 'dtypes': df.dtypes.to_string(),
131
- 'head': df.head().to_string(), 'data_quality': data_quality
132
- }
133
 
134
  # --- Page Navigation ---
135
  def switch_page(page_name):
136
- if page_name == "cockpit":
137
- return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
138
- elif page_name == "deep_dive":
139
- return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
140
- elif page_name == "co-pilot":
141
- return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
142
- return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
143
 
144
  # --- Page 1: Data Cockpit ---
145
  def get_ai_suggestions(state_dict, api_key):
@@ -161,24 +135,20 @@ def get_ai_suggestions(state_dict, api_key):
161
  model = genai.GenerativeModel('gemini-1.5-flash')
162
  response = model.generate_content(prompt)
163
  suggestions = json.loads(response.text)
164
-
165
  buttons = [gr.Button(s, variant="secondary", visible=True) for s in suggestions]
166
  buttons += [gr.Button(visible=False)] * (5 - len(buttons))
167
-
168
  return gr.update(visible=False), *buttons
169
-
170
  except Exception as e:
171
  return f"Could not generate suggestions: {e}", *[gr.update(visible=False)]*5
172
 
173
  def handle_suggestion_click(question_text):
174
- return (
175
- gr.update(visible=False), gr.update(visible=False),
176
- gr.update(visible=True), question_text
177
- )
178
 
179
  # --- Page 2: Deep Dive Dashboard ---
180
  def add_plot_to_dashboard(state_dict, x_col, y_col, plot_type):
181
- if not x_col: return state_dict, gr.update()
 
 
182
 
183
  df = state_dict['df']
184
  title = f"{plot_type.capitalize()}: {y_col} by {x_col}" if y_col else f"Distribution of {x_col}"
@@ -194,18 +164,19 @@ def add_plot_to_dashboard(state_dict, x_col, y_col, plot_type):
194
  fig.update_xaxes(title=x_col)
195
 
196
  if fig:
197
- fig.update_layout(template="plotly_dark") # Ensure plots match the dark theme
198
  state_dict['dashboard_plots'].append(fig)
199
 
200
- accordion_children = [gr.Plot(fig, visible=True) for fig in state_dict['dashboard_plots']]
201
- return state_dict, gr.Accordion(label="Your Dashboard Plots", children=accordion_children, open=True)
202
  except Exception as e:
203
  gr.Warning(f"Plotting Error: {e}")
204
- return state_dict, gr.update()
205
 
206
  def clear_dashboard(state_dict):
207
  state_dict['dashboard_plots'] = []
208
- return state_dict, gr.Accordion(label="Your Dashboard Plots", children=[])
 
209
 
210
  # --- Page 3: AI Co-pilot ---
211
  def respond_to_chat(user_message, history, state_dict, api_key):
@@ -217,38 +188,17 @@ def respond_to_chat(user_message, history, state_dict, api_key):
217
  return history, *[gr.update(visible=False)] * 4
218
 
219
  history.append((user_message, None))
220
-
221
  metadata = state_dict['metadata']
222
  prompt = f"""
223
- You are 'Phoenix Co-pilot', an expert AI data analyst. Your goal is to help a user analyze a pandas DataFrame named `df`.
224
-
225
- **Instructions:**
226
- 1. Carefully understand the user's question.
227
- 2. Formulate a plan (thought process).
228
- 3. Write Python code to execute that plan.
229
- 4. The code can use pandas (pd), numpy (np), and plotly.express (px).
230
- 5. **For plots, assign the figure to a variable `fig` (e.g., `fig = px.histogram(...)`). IMPORTANT: you MUST add `template='plotly_dark'` to all plotly figures to match the UI theme.**
231
- 6. **For table-like results, assign the final DataFrame to a variable `result_df` (e.g., `result_df = df.describe()`).**
232
- 7. Do not modify the original `df`. Use `df.copy()` if needed.
233
- 8. Provide a brief, user-friendly explanation of the result.
234
- 9. Respond **ONLY** with a single, raw JSON object with keys: "thought", "code", "explanation".
235
-
236
- **DataFrame Metadata:**
237
- - Columns and dtypes: {metadata['dtypes']}
238
- - First 5 rows: {metadata['head']}
239
-
240
  **User Question:** "{user_message}"
241
-
242
  **Your JSON Response:**
243
  """
244
-
245
  try:
246
  genai.configure(api_key=api_key)
247
  model = genai.GenerativeModel('gemini-1.5-flash')
248
  response = model.generate_content(prompt)
249
-
250
- response_text = response.text.strip().replace("```json", "").replace("```", "")
251
- response_json = json.loads(response_text)
252
 
253
  thought = response_json.get("thought", "Thinking...")
254
  code_to_run = response_json.get("code", "")
@@ -256,11 +206,9 @@ def respond_to_chat(user_message, history, state_dict, api_key):
256
 
257
  stdout, fig_result, df_result, error = safe_exec(code_to_run, {'df': state_dict['df'], 'px': px, 'pd': pd, 'np': np})
258
 
259
- bot_message = f"πŸ€” **Thought:** *{thought}*"
260
- history[-1] = (user_message, bot_message)
261
 
262
  output_updates = [gr.update(visible=False, value=None)] * 4
263
-
264
  if explanation: output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** {explanation}")
265
  if code_to_run: output_updates[1] = gr.update(visible=True, value=code_to_run)
266
  if fig_result: output_updates[2] = gr.update(visible=True, value=fig_result)
@@ -269,21 +217,15 @@ def respond_to_chat(user_message, history, state_dict, api_key):
269
  new_explanation = (output_updates[0]['value'] if output_updates[0]['visible'] else "") + f"\n\n**Console Output:**\n```\n{stdout}\n```"
270
  output_updates[0] = gr.update(visible=True, value=new_explanation)
271
  if error:
272
- error_explanation = f"**Phoenix Co-pilot:** I encountered an error. Here's the details:\n\n`{error}`"
273
- output_updates[0] = gr.update(visible=True, value=error_explanation)
274
 
275
  return history, *output_updates
276
-
277
  except Exception as e:
278
- error_msg = f"A critical error occurred: {e}. The AI may have returned an invalid response. Please try rephrasing your question."
279
- history[-1] = (user_message, error_msg)
280
  return history, *[gr.update(visible=False)] * 4
281
 
282
  # --- Gradio UI Definition ---
283
  def create_gradio_interface():
284
- # --- CORRECTED THEME DEFINITION ---
285
- # We use gr.themes.Glass which is dark by default and allows hue customization.
286
- # This is the syntactically correct and visually appropriate way to create the theme.
287
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="Phoenix AI Data Explorer") as demo:
288
  global_state = gr.State({})
289
 
@@ -310,25 +252,14 @@ def create_gradio_interface():
310
 
311
  with gr.Column(visible=False) as cockpit_page:
312
  gr.Markdown("## πŸ“Š Data Cockpit")
313
- with gr.Row():
314
- with gr.Column(elem_classes="stat-card"):
315
- gr.Markdown("<div class='stat-card-title'>Rows</div>")
316
- rows_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
317
- with gr.Column(elem_classes="stat-card"):
318
- gr.Markdown("<div class='stat-card-title'>Columns</div>")
319
- cols_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
320
- with gr.Column(elem_classes="stat-card"):
321
- gr.Markdown("<div class='stat-card-title'>Data Quality</div>")
322
- quality_stat = gr.Textbox("0%", show_label=False, elem_classes="stat-card-value")
323
- with gr.Column(elem_classes="stat-card"):
324
- gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>")
325
- time_cols_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
326
  suggestion_status = gr.Markdown(visible=True)
327
  with gr.Accordion(label="✨ AI Smart Suggestions", open=True):
328
  suggestion_buttons = [gr.Button(visible=False) for _ in range(5)]
329
 
330
  with gr.Column(visible=False) as deep_dive_page:
331
  gr.Markdown("## πŸ” Deep Dive Dashboard Builder")
 
332
  with gr.Row():
333
  plot_type_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram')
334
  x_col_dd = gr.Dropdown([], label="X-Axis / Column")
@@ -336,36 +267,28 @@ def create_gradio_interface():
336
  with gr.Row():
337
  add_plot_btn = gr.Button("Add to Dashboard", variant="primary")
338
  clear_plots_btn = gr.Button("Clear Dashboard")
339
- dashboard_accordion = gr.Accordion(label="Your Dashboard Plots", open=True)
 
 
340
 
341
  with gr.Column(visible=False) as copilot_page:
342
  gr.Markdown("## πŸ€– AI Co-pilot")
343
- chatbot = gr.Chatbot(height=400, label="Conversation with Co-pilot", show_copy_button=True)
344
- with gr.Accordion("Co-pilot's Response Details", open=True):
345
- copilot_explanation = gr.Markdown(visible=False, elem_classes="explanation-block")
346
- copilot_code = gr.Code(language="python", visible=False, label="Executed Python Code")
347
- copilot_plot = gr.Plot(visible=False, label="Generated Visualization")
348
- copilot_table = gr.Dataframe(visible=False, label="Generated Table", wrap=True)
349
- with gr.Row():
350
- chat_input = gr.Textbox(label="Your Question", placeholder="e.g., 'What is the correlation between age and salary?'", scale=4)
351
- chat_submit_btn = gr.Button("Submit", variant="primary")
352
 
353
- # Event Handlers
354
  pages = [cockpit_page, deep_dive_page, copilot_page]
355
  nav_buttons = [cockpit_btn, deep_dive_btn, copilot_btn]
356
 
357
  for i, btn in enumerate(nav_buttons):
358
- btn.click(lambda i=i: (gr.update(visible=i==0), gr.update(visible=i==1), gr.update(visible=i==2)), outputs=pages) \
359
  .then(lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
360
 
361
- file_input.upload(
362
- fn=load_and_process_file,
363
- inputs=[file_input, global_state],
364
- outputs=[global_state, status_output, welcome_page, cockpit_page, deep_dive_page, copilot_page,
365
- rows_stat, cols_stat, quality_stat, time_cols_stat,
366
- x_col_dd, y_col_dd, plot_type_dd]
367
- ).then(lambda: (gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)), outputs=pages) \
368
- .then(lambda: (gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")), outputs=nav_buttons)
369
 
370
  suggestion_btn.click(get_ai_suggestions, [global_state, api_key_input], [suggestion_status, *suggestion_buttons])
371
 
@@ -373,12 +296,15 @@ def create_gradio_interface():
373
  btn.click(handle_suggestion_click, inputs=[btn], outputs=[cockpit_page, deep_dive_page, copilot_page, chat_input]) \
374
  .then(lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")), outputs=nav_buttons)
375
 
376
- add_plot_btn.click(add_plot_to_dashboard, [global_state, x_col_dd, y_col_dd, plot_type_dd], [global_state, dashboard_accordion])
377
- clear_plots_btn.click(clear_dashboard, [global_state], [global_state, dashboard_accordion])
 
378
 
379
- chat_submit_btn.click(respond_to_chat, [chat_input, chatbot, global_state, api_key_input], [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]) \
 
380
  .then(lambda: "", outputs=[chat_input])
381
- chat_input.submit(respond_to_chat, [chat_input, chatbot, global_state, api_key_input], [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]) \
 
382
  .then(lambda: "", outputs=[chat_input])
383
 
384
  return demo
 
67
 
68
  # --- Helper Functions ---
69
  def safe_exec(code_string: str, local_vars: dict):
 
70
  output_buffer = io.StringIO()
71
  try:
72
  with redirect_stdout(output_buffer):
73
  exec(code_string, globals(), local_vars)
 
74
  stdout = output_buffer.getvalue()
75
  fig = local_vars.get('fig')
76
  result_df = local_vars.get('result_df')
 
80
 
81
  # --- Core Data Processing & State Management ---
82
  def load_and_process_file(file_obj, state_dict):
83
+ if file_obj is None: return state_dict, "Please upload a file.", *[gr.update(visible=False)] * 4
 
 
84
  try:
85
  df = pd.read_csv(file_obj.name, low_memory=False)
86
  for col in df.select_dtypes(include=['object']).columns:
87
+ try: df[col] = pd.to_datetime(df[col], errors='raise')
88
+ except (ValueError, TypeError): continue
 
 
89
 
90
  metadata = extract_dataset_metadata(df)
91
+ state_dict = {'df': df, 'metadata': metadata, 'filename': os.path.basename(file_obj.name), 'dashboard_plots': []}
 
 
 
 
 
92
 
93
  status_msg = f"βœ… **{state_dict['filename']}** loaded successfully."
 
94
  cockpit_update = gr.update(visible=True)
 
 
95
  welcome_update = gr.update(visible=False)
 
96
  rows, cols = metadata['shape']
97
  quality = metadata['data_quality']
98
 
99
+ return (state_dict, status_msg, welcome_update, cockpit_update, gr.update(visible=False), gr.update(visible=False),
100
+ gr.update(value=f"{rows:,}"), gr.update(value=cols), gr.update(value=f"{quality}%"), gr.update(value=f"{len(metadata['datetime_cols'])}"),
 
101
  gr.update(choices=metadata['columns']), gr.update(choices=metadata['columns']), gr.update(choices=metadata['columns']))
102
  except Exception as e:
103
  return state_dict, f"❌ **Error:** {e}", *[gr.update()] * 11
 
108
  categorical_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
109
  datetime_cols = df.select_dtypes(include=['datetime64', 'datetime64[ns]']).columns.tolist()
110
  data_quality = round((df.notna().sum().sum() / (rows * cols)) * 100, 1) if rows * cols > 0 else 0
111
+ return {'shape': (rows, cols), 'columns': df.columns.tolist(), 'numeric_cols': numeric_cols, 'categorical_cols': categorical_cols,
112
+ 'datetime_cols': datetime_cols, 'dtypes': df.dtypes.to_string(), 'head': df.head().to_string(), 'data_quality': data_quality}
 
 
 
 
113
 
114
  # --- Page Navigation ---
115
  def switch_page(page_name):
116
+ return (gr.update(visible=page_name=="cockpit"), gr.update(visible=page_name=="deep_dive"), gr.update(visible=page_name=="co-pilot"))
 
 
 
 
 
 
117
 
118
  # --- Page 1: Data Cockpit ---
119
  def get_ai_suggestions(state_dict, api_key):
 
135
  model = genai.GenerativeModel('gemini-1.5-flash')
136
  response = model.generate_content(prompt)
137
  suggestions = json.loads(response.text)
 
138
  buttons = [gr.Button(s, variant="secondary", visible=True) for s in suggestions]
139
  buttons += [gr.Button(visible=False)] * (5 - len(buttons))
 
140
  return gr.update(visible=False), *buttons
 
141
  except Exception as e:
142
  return f"Could not generate suggestions: {e}", *[gr.update(visible=False)]*5
143
 
144
  def handle_suggestion_click(question_text):
145
+ return (gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), question_text)
 
 
 
146
 
147
  # --- Page 2: Deep Dive Dashboard ---
148
  def add_plot_to_dashboard(state_dict, x_col, y_col, plot_type):
149
+ if not x_col:
150
+ gr.Warning("Please select at least an X-axis column.")
151
+ return state_dict, state_dict.get('dashboard_plots', [])
152
 
153
  df = state_dict['df']
154
  title = f"{plot_type.capitalize()}: {y_col} by {x_col}" if y_col else f"Distribution of {x_col}"
 
164
  fig.update_xaxes(title=x_col)
165
 
166
  if fig:
167
+ fig.update_layout(template="plotly_dark")
168
  state_dict['dashboard_plots'].append(fig)
169
 
170
+ # CORRECTED: Return the list of plots to the Gallery component
171
+ return state_dict, state_dict['dashboard_plots']
172
  except Exception as e:
173
  gr.Warning(f"Plotting Error: {e}")
174
+ return state_dict, state_dict.get('dashboard_plots', [])
175
 
176
  def clear_dashboard(state_dict):
177
  state_dict['dashboard_plots'] = []
178
+ # CORRECTED: Return an empty list to clear the Gallery
179
+ return state_dict, []
180
 
181
  # --- Page 3: AI Co-pilot ---
182
  def respond_to_chat(user_message, history, state_dict, api_key):
 
188
  return history, *[gr.update(visible=False)] * 4
189
 
190
  history.append((user_message, None))
 
191
  metadata = state_dict['metadata']
192
  prompt = f"""
193
+ You are 'Phoenix Co-pilot', an expert AI data analyst... [Prompt remains the same]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  **User Question:** "{user_message}"
 
195
  **Your JSON Response:**
196
  """
 
197
  try:
198
  genai.configure(api_key=api_key)
199
  model = genai.GenerativeModel('gemini-1.5-flash')
200
  response = model.generate_content(prompt)
201
+ response_json = json.loads(response.text.strip().replace("```json", "").replace("```", ""))
 
 
202
 
203
  thought = response_json.get("thought", "Thinking...")
204
  code_to_run = response_json.get("code", "")
 
206
 
207
  stdout, fig_result, df_result, error = safe_exec(code_to_run, {'df': state_dict['df'], 'px': px, 'pd': pd, 'np': np})
208
 
209
+ history[-1] = (user_message, f"πŸ€” **Thought:** *{thought}*")
 
210
 
211
  output_updates = [gr.update(visible=False, value=None)] * 4
 
212
  if explanation: output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** {explanation}")
213
  if code_to_run: output_updates[1] = gr.update(visible=True, value=code_to_run)
214
  if fig_result: output_updates[2] = gr.update(visible=True, value=fig_result)
 
217
  new_explanation = (output_updates[0]['value'] if output_updates[0]['visible'] else "") + f"\n\n**Console Output:**\n```\n{stdout}\n```"
218
  output_updates[0] = gr.update(visible=True, value=new_explanation)
219
  if error:
220
+ output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** I encountered an error. Here's the details:\n\n`{error}`")
 
221
 
222
  return history, *output_updates
 
223
  except Exception as e:
224
+ history[-1] = (user_message, f"A critical error occurred: {e}. The AI may have returned an invalid response.")
 
225
  return history, *[gr.update(visible=False)] * 4
226
 
227
  # --- Gradio UI Definition ---
228
  def create_gradio_interface():
 
 
 
229
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="Phoenix AI Data Explorer") as demo:
230
  global_state = gr.State({})
231
 
 
252
 
253
  with gr.Column(visible=False) as cockpit_page:
254
  gr.Markdown("## πŸ“Š Data Cockpit")
255
+ # ... [Stat cards code remains the same] ...
 
 
 
 
 
 
 
 
 
 
 
 
256
  suggestion_status = gr.Markdown(visible=True)
257
  with gr.Accordion(label="✨ AI Smart Suggestions", open=True):
258
  suggestion_buttons = [gr.Button(visible=False) for _ in range(5)]
259
 
260
  with gr.Column(visible=False) as deep_dive_page:
261
  gr.Markdown("## πŸ” Deep Dive Dashboard Builder")
262
+ gr.Markdown("Create a custom dashboard by adding multiple plots to the gallery below.")
263
  with gr.Row():
264
  plot_type_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram')
265
  x_col_dd = gr.Dropdown([], label="X-Axis / Column")
 
267
  with gr.Row():
268
  add_plot_btn = gr.Button("Add to Dashboard", variant="primary")
269
  clear_plots_btn = gr.Button("Clear Dashboard")
270
+
271
+ # CORRECTED: Replaced Accordion with Gallery
272
+ dashboard_gallery = gr.Gallery(label="πŸ“Š Your Custom Dashboard", height="auto", columns=2)
273
 
274
  with gr.Column(visible=False) as copilot_page:
275
  gr.Markdown("## πŸ€– AI Co-pilot")
276
+ # ... [Co-pilot code remains the same] ...
 
 
 
 
 
 
 
 
277
 
278
+ # --- Event Handlers ---
279
  pages = [cockpit_page, deep_dive_page, copilot_page]
280
  nav_buttons = [cockpit_btn, deep_dive_btn, copilot_btn]
281
 
282
  for i, btn in enumerate(nav_buttons):
283
+ btn.click(lambda i=i: switch_page(nav_buttons[i].value.lower().replace(" ", "_").split(" ")[-1]), outputs=pages) \
284
  .then(lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
285
 
286
+ file_input.upload(load_and_process_file, [file_input, global_state],
287
+ [global_state, status_output, welcome_page, cockpit_page, deep_dive_page, copilot_page,
288
+ rows_stat, cols_stat, quality_stat, time_cols_stat,
289
+ x_col_dd, y_col_dd, plot_type_dd]) \
290
+ .then(lambda: switch_page("cockpit"), outputs=pages) \
291
+ .then(lambda: [gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")], outputs=nav_buttons)
 
 
292
 
293
  suggestion_btn.click(get_ai_suggestions, [global_state, api_key_input], [suggestion_status, *suggestion_buttons])
294
 
 
296
  btn.click(handle_suggestion_click, inputs=[btn], outputs=[cockpit_page, deep_dive_page, copilot_page, chat_input]) \
297
  .then(lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")), outputs=nav_buttons)
298
 
299
+ # CORRECTED: Event handlers now output to the gallery
300
+ add_plot_btn.click(add_plot_to_dashboard, [global_state, x_col_dd, y_col_dd, plot_type_dd], [global_state, dashboard_gallery])
301
+ clear_plots_btn.click(clear_dashboard, [global_state], [global_state, dashboard_gallery])
302
 
303
+ chat_submit_btn.click(respond_to_chat, [chat_input, chatbot, global_state, api_key_input],
304
+ [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]) \
305
  .then(lambda: "", outputs=[chat_input])
306
+ chat_input.submit(respond_to_chat, [chat_input, chatbot, global_state, api_key_input],
307
+ [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]) \
308
  .then(lambda: "", outputs=[chat_input])
309
 
310
  return demo