mgbam commited on
Commit
38629d1
Β·
verified Β·
1 Parent(s): 272b87c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -150
app.py CHANGED
@@ -69,14 +69,10 @@ CSS = """
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')
77
  return stdout, fig, result_df, None
78
- except Exception as e:
79
- return None, None, None, f"Execution Error: {str(e)}"
80
 
81
  def load_and_process_file(file_obj, state_dict):
82
  if file_obj is None: return state_dict, "Please upload a file.", *[gr.update(visible=False)] * 4
@@ -85,70 +81,52 @@ def load_and_process_file(file_obj, state_dict):
85
  for col in df.select_dtypes(include=['object']).columns:
86
  try: df[col] = pd.to_datetime(df[col], errors='raise')
87
  except (ValueError, TypeError): continue
88
-
89
  metadata = extract_dataset_metadata(df)
90
  state_dict = {'df': df, 'metadata': metadata, 'filename': os.path.basename(file_obj.name), 'dashboard_plots': []}
91
-
92
- status_msg = f"βœ… **{state_dict['filename']}** loaded successfully."
93
- cockpit_update = gr.update(visible=True)
94
- welcome_update = gr.update(visible=False)
95
- rows, cols = metadata['shape']
96
- quality = metadata['data_quality']
97
-
98
- return (state_dict, status_msg, welcome_update, cockpit_update, gr.update(visible=False), gr.update(visible=False),
99
  gr.update(value=f"{rows:,}"), gr.update(value=cols), gr.update(value=f"{quality}%"), gr.update(value=f"{len(metadata['datetime_cols'])}"),
100
  gr.update(choices=metadata['columns']), gr.update(choices=metadata['columns']), gr.update(choices=metadata['columns']))
101
- except Exception as e:
102
- return state_dict, f"❌ **Error:** {e}", *[gr.update()] * 11
103
 
104
  def extract_dataset_metadata(df: pd.DataFrame):
105
  rows, cols = df.shape
106
- numeric_cols = df.select_dtypes(include=np.number).columns.tolist()
107
- categorical_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
108
- datetime_cols = df.select_dtypes(include=['datetime64', 'datetime64[ns]']).columns.tolist()
109
- data_quality = round((df.notna().sum().sum() / (rows * cols)) * 100, 1) if rows * cols > 0 else 0
110
- return {'shape': (rows, cols), 'columns': df.columns.tolist(), 'numeric_cols': numeric_cols, 'categorical_cols': categorical_cols,
111
- 'datetime_cols': datetime_cols, 'dtypes': df.dtypes.to_string(), 'head': df.head().to_string(), 'data_quality': data_quality}
112
 
113
  def switch_page(page_name):
114
- return (gr.update(visible=page_name=="cockpit"), gr.update(visible=page_name=="deep_dive"), gr.update(visible=page_name=="co-pilot"))
115
 
116
  def get_ai_suggestions(state_dict, api_key):
117
- if not api_key: return "Enter your Gemini API key to get suggestions.", *[gr.update(visible=False)]*5
118
  if not state_dict: return "Upload data first.", *[gr.update(visible=False)]*5
119
- metadata = state_dict['metadata']
120
- prompt = f"""
121
- Based on the following dataset metadata, generate 3 to 5 specific, actionable, and interesting analytical questions...
122
- Return ONLY a JSON list of strings. Example: ["What is the trend of sales over time?", "Which category has the highest average price?"]
123
- """
124
  try:
125
  genai.configure(api_key=api_key)
126
  model = genai.GenerativeModel('gemini-1.5-flash')
127
- response = model.generate_content(prompt)
128
- suggestions = json.loads(response.text)
129
- buttons = [gr.Button(s, variant="secondary", visible=True) for s in suggestions]
130
- buttons += [gr.Button(visible=False)] * (5 - len(buttons))
131
  return gr.update(visible=False), *buttons
132
  except Exception as e: return f"Could not generate suggestions: {e}", *[gr.update(visible=False)]*5
133
 
134
  def handle_suggestion_click(question_text):
135
- return (gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), question_text)
136
 
137
  def add_plot_to_dashboard(state_dict, x_col, y_col, plot_type):
138
  if not x_col:
139
  gr.Warning("Please select at least an X-axis column.")
140
  return state_dict, state_dict.get('dashboard_plots', [])
141
- df = state_dict['df']
142
- title = f"{plot_type.capitalize()}: {y_col} by {x_col}" if y_col else f"Distribution of {x_col}"
143
- fig = None
144
  try:
145
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
146
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
147
  elif plot_type == 'scatter': fig = px.scatter(df, x=x_col, y=y_col, title=title, trendline="ols")
148
- elif plot_type == 'bar':
149
  counts = df[x_col].value_counts().nlargest(20)
150
- fig = px.bar(counts, x=counts.index, y=counts.values, title=f"Top 20 Categories for {x_col}")
151
- fig.update_xaxes(title=x_col)
152
  if fig:
153
  fig.update_layout(template="plotly_dark")
154
  state_dict['dashboard_plots'].append(fig)
@@ -162,31 +140,17 @@ def clear_dashboard(state_dict):
162
  return state_dict, []
163
 
164
  def respond_to_chat(user_message, history, state_dict, api_key):
165
- if not api_key:
166
- history.append((user_message, "I need a Gemini API key to function..."))
167
- return history, *[gr.update(visible=False)] * 4
168
- if not state_dict:
169
- history.append((user_message, "Please upload a dataset first."))
170
- return history, *[gr.update(visible=False)] * 4
171
- history.append((user_message, None))
172
- metadata = state_dict['metadata']
173
- prompt = f"""
174
- You are 'Phoenix Co-pilot', an expert AI data analyst... [Prompt remains the same]
175
- **User Question:** "{user_message}"
176
- **Your JSON Response:**
177
- """
178
  try:
179
  genai.configure(api_key=api_key)
180
- model = genai.GenerativeModel('gemini-1.5-flash')
181
- response = model.generate_content(prompt)
182
- response_json = json.loads(response.text.strip().replace("```json", "").replace("```", ""))
183
-
184
- thought = response_json.get("thought", "Thinking...")
185
- code_to_run = response_json.get("code", "")
186
- explanation = response_json.get("explanation", "Here is the result.")
187
  stdout, fig_result, df_result, error = safe_exec(code_to_run, {'df': state_dict['df'], 'px': px, 'pd': pd, 'np': np})
188
  history[-1] = (user_message, f"πŸ€” **Thought:** *{thought}*")
189
-
190
  output_updates = [gr.update(visible=False, value=None)] * 4
191
  if explanation: output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** {explanation}")
192
  if code_to_run: output_updates[1] = gr.update(visible=True, value=code_to_run)
@@ -195,123 +159,82 @@ def respond_to_chat(user_message, history, state_dict, api_key):
195
  if stdout:
196
  new_explanation = (output_updates[0]['value'] if output_updates[0]['visible'] else "") + f"\n\n**Console Output:**\n```\n{stdout}\n```"
197
  output_updates[0] = gr.update(visible=True, value=new_explanation)
198
- if error:
199
- output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** I encountered an error. Here's the details:\n\n`{error}`")
200
  return history, *output_updates
201
  except Exception as e:
202
- history[-1] = (user_message, f"A critical error occurred: {e}.")
203
- return history, *[gr.update(visible=False)] * 4
204
 
205
  # --- Gradio UI Definition ---
206
  def create_gradio_interface():
207
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="Phoenix AI Data Explorer") as demo:
208
  global_state = gr.State({})
209
 
210
- # --- CORRECTED: "Define-Then-Place" Pattern ---
211
- # 1. DEFINE all interactive components first.
 
 
 
 
 
 
 
212
 
213
- # Sidebar components
214
- cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected")
215
- deep_dive_btn = gr.Button("πŸ” Deep Dive Builder")
216
- copilot_btn = gr.Button("πŸ€– AI Co-pilot")
217
- file_input = gr.File(label="πŸ“ Upload New CSV", file_types=[".csv"])
218
- status_output = gr.Markdown("Status: Awaiting data...")
219
- api_key_input = gr.Textbox(label="πŸ”‘ Gemini API Key", type="password", placeholder="Enter key here...")
220
- suggestion_btn = gr.Button("Get Smart Suggestions", variant="secondary")
221
 
222
- # Cockpit page components
223
- rows_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value", interactive=False)
224
- cols_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value", interactive=False)
225
- quality_stat = gr.Textbox("0%", show_label=False, elem_classes="stat-card-value", interactive=False)
226
- time_cols_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value", interactive=False)
227
- suggestion_status = gr.Markdown(visible=True)
228
- suggestion_buttons = [gr.Button(visible=False) for _ in range(5)]
229
-
230
- # Deep Dive page components
231
- plot_type_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram')
232
- x_col_dd = gr.Dropdown([], label="X-Axis / Column")
233
- y_col_dd = gr.Dropdown([], label="Y-Axis (for Scatter/Box)")
234
- add_plot_btn = gr.Button("Add to Dashboard", variant="primary")
235
- clear_plots_btn = gr.Button("Clear Dashboard")
236
  dashboard_gallery = gr.Gallery(label="πŸ“Š Your Custom Dashboard", height="auto", columns=2, preview=True)
237
 
238
- # Co-pilot page components
239
  chatbot = gr.Chatbot(height=400, label="Conversation with Co-pilot", show_copy_button=True)
240
- copilot_explanation = gr.Markdown(visible=False, elem_classes="explanation-block")
241
- copilot_code = gr.Code(language="python", visible=False, label="Executed Python Code")
242
- copilot_plot = gr.Plot(visible=False, label="Generated Visualization")
243
- copilot_table = gr.Dataframe(visible=False, label="Generated Table", wrap=True)
244
- chat_input = gr.Textbox(label="Your Question", placeholder="e.g., 'What is the correlation between age and salary?'", scale=4)
245
- chat_submit_btn = gr.Button("Submit", variant="primary")
246
 
247
- # 2. PLACE the defined components into the layout.
248
  with gr.Row():
249
- # Sidebar Layout
250
  with gr.Column(scale=1, elem_classes="sidebar"):
251
- gr.Markdown("## πŸš€ Phoenix UI")
252
- cockpit_btn
253
- deep_dive_btn
254
- copilot_btn
255
- gr.Markdown("---")
256
- file_input
257
- status_output
258
- gr.Markdown("---")
259
- api_key_input
260
- suggestion_btn
261
-
262
- # Main Content Layout
263
  with gr.Column(scale=4):
264
  with gr.Column(visible=True) as welcome_page:
265
- gr.Markdown("# Welcome to the AI Data Explorer (Phoenix UI)")
266
- gr.Markdown("Please **upload a CSV file** and **enter your Gemini API key** in the sidebar to begin.")
267
  gr.Image(value="workflow.png", show_label=False, show_download_button=False, container=False)
268
-
269
  with gr.Column(visible=False) as cockpit_page:
270
  gr.Markdown("## πŸ“Š Data Cockpit")
271
  with gr.Row():
272
- with gr.Column(elem_classes="stat-card"):
273
- gr.Markdown("<div class='stat-card-title'>Rows</div>"); rows_stat
274
- with gr.Column(elem_classes="stat-card"):
275
- gr.Markdown("<div class='stat-card-title'>Columns</div>"); cols_stat
276
- with gr.Column(elem_classes="stat-card"):
277
- gr.Markdown("<div class='stat-card-title'>Data Quality</div>"); quality_stat
278
- with gr.Column(elem_classes="stat-card"):
279
- gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>"); time_cols_stat
280
- suggestion_status
281
- with gr.Accordion(label="✨ AI Smart Suggestions", open=True):
282
- for btn in suggestion_buttons:
283
- btn
284
-
285
  with gr.Column(visible=False) as deep_dive_page:
286
- gr.Markdown("## πŸ” Deep Dive Dashboard Builder")
287
- gr.Markdown("Create a custom dashboard by adding multiple plots to the gallery below.")
288
- with gr.Row():
289
- plot_type_dd; x_col_dd; y_col_dd
290
- with gr.Row():
291
- add_plot_btn; clear_plots_btn
292
  dashboard_gallery
293
-
294
  with gr.Column(visible=False) as copilot_page:
295
- gr.Markdown("## πŸ€– AI Co-pilot")
296
- chatbot
297
- with gr.Accordion("Co-pilot's Response Details", open=True):
298
- copilot_explanation; copilot_code; copilot_plot; copilot_table
299
- with gr.Row():
300
- chat_input; chat_submit_btn
301
 
302
- # 3. DEFINE all event handlers. Now all component variables are guaranteed to exist.
303
  pages = [cockpit_page, deep_dive_page, copilot_page]
304
  nav_buttons = [cockpit_btn, deep_dive_btn, copilot_btn]
305
 
 
306
  for i, btn in enumerate(nav_buttons):
307
- page_name = btn.value.lower().replace(" ", "_").split(" ")[-1]
308
- btn.click(lambda name=page_name: switch_page(name), outputs=pages) \
309
  .then(lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
310
 
311
  file_input.upload(load_and_process_file, [file_input, global_state],
312
  [global_state, status_output, welcome_page, cockpit_page, deep_dive_page, copilot_page,
313
- rows_stat, cols_stat, quality_stat, time_cols_stat,
314
- x_col_dd, y_col_dd, plot_type_dd]) \
315
  .then(lambda: switch_page("cockpit"), outputs=pages) \
316
  .then(lambda: [gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")], outputs=nav_buttons)
317
 
@@ -325,11 +248,9 @@ def create_gradio_interface():
325
  clear_plots_btn.click(clear_dashboard, [global_state], [global_state, dashboard_gallery])
326
 
327
  chat_submit_btn.click(respond_to_chat, [chat_input, chatbot, global_state, api_key_input],
328
- [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]) \
329
- .then(lambda: "", outputs=[chat_input])
330
  chat_input.submit(respond_to_chat, [chat_input, chatbot, global_state, api_key_input],
331
- [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]) \
332
- .then(lambda: "", outputs=[chat_input])
333
 
334
  return demo
335
 
 
69
  def safe_exec(code_string: str, local_vars: dict):
70
  output_buffer = io.StringIO()
71
  try:
72
+ with redirect_stdout(output_buffer): exec(code_string, globals(), local_vars)
73
+ stdout, fig, result_df = output_buffer.getvalue(), local_vars.get('fig'), local_vars.get('result_df')
 
 
 
74
  return stdout, fig, result_df, None
75
+ except Exception as e: return None, None, None, f"Execution Error: {str(e)}"
 
76
 
77
  def load_and_process_file(file_obj, state_dict):
78
  if file_obj is None: return state_dict, "Please upload a file.", *[gr.update(visible=False)] * 4
 
81
  for col in df.select_dtypes(include=['object']).columns:
82
  try: df[col] = pd.to_datetime(df[col], errors='raise')
83
  except (ValueError, TypeError): continue
 
84
  metadata = extract_dataset_metadata(df)
85
  state_dict = {'df': df, 'metadata': metadata, 'filename': os.path.basename(file_obj.name), 'dashboard_plots': []}
86
+ status_msg, welcome_update = f"βœ… **{state_dict['filename']}** loaded.", gr.update(visible=False)
87
+ rows, cols, quality = metadata['shape'][0], metadata['shape'][1], metadata['data_quality']
88
+ return (state_dict, status_msg, welcome_update, gr.update(visible=True), gr.update(visible=False), gr.update(visible=False),
 
 
 
 
 
89
  gr.update(value=f"{rows:,}"), gr.update(value=cols), gr.update(value=f"{quality}%"), gr.update(value=f"{len(metadata['datetime_cols'])}"),
90
  gr.update(choices=metadata['columns']), gr.update(choices=metadata['columns']), gr.update(choices=metadata['columns']))
91
+ except Exception as e: return state_dict, f"❌ **Error:** {e}", *[gr.update()] * 11
 
92
 
93
  def extract_dataset_metadata(df: pd.DataFrame):
94
  rows, cols = df.shape
95
+ numeric_cols, cat_cols, dt_cols = df.select_dtypes(include=np.number).columns.tolist(), df.select_dtypes(include=['object', 'category']).columns.tolist(), df.select_dtypes(include=['datetime64', 'datetime64[ns]']).columns.tolist()
96
+ quality = round((df.notna().sum().sum() / (rows * cols)) * 100, 1) if rows * cols > 0 else 0
97
+ return {'shape': (rows, cols), 'columns': df.columns.tolist(), 'numeric_cols': numeric_cols, 'categorical_cols': cat_cols,
98
+ 'datetime_cols': dt_cols, 'dtypes': df.dtypes.to_string(), 'head': df.head().to_string(), 'data_quality': quality}
 
 
99
 
100
  def switch_page(page_name):
101
+ return gr.update(visible=page_name=="cockpit"), gr.update(visible=page_name=="deep_dive"), gr.update(visible=page_name=="co-pilot")
102
 
103
  def get_ai_suggestions(state_dict, api_key):
104
+ if not api_key: return "Enter your Gemini API key...", *[gr.update(visible=False)]*5
105
  if not state_dict: return "Upload data first.", *[gr.update(visible=False)]*5
106
+ metadata, prompt = state_dict['metadata'], f"Based on metadata... generate 3-5 questions... Return ONLY JSON list of strings."
 
 
 
 
107
  try:
108
  genai.configure(api_key=api_key)
109
  model = genai.GenerativeModel('gemini-1.5-flash')
110
+ suggestions = json.loads(model.generate_content(prompt).text)
111
+ buttons = [gr.Button(s, variant="secondary", visible=True) for s in suggestions] + [gr.Button(visible=False)] * (5 - len(suggestions))
 
 
112
  return gr.update(visible=False), *buttons
113
  except Exception as e: return f"Could not generate suggestions: {e}", *[gr.update(visible=False)]*5
114
 
115
  def handle_suggestion_click(question_text):
116
+ return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), question_text
117
 
118
  def add_plot_to_dashboard(state_dict, x_col, y_col, plot_type):
119
  if not x_col:
120
  gr.Warning("Please select at least an X-axis column.")
121
  return state_dict, state_dict.get('dashboard_plots', [])
122
+ df, title = state_dict['df'], f"{plot_type.capitalize()}: {y_col} by {x_col}" if y_col else f"Distribution of {x_col}"
 
 
123
  try:
124
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
125
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
126
  elif plot_type == 'scatter': fig = px.scatter(df, x=x_col, y=y_col, title=title, trendline="ols")
127
+ elif plot_type == 'bar':
128
  counts = df[x_col].value_counts().nlargest(20)
129
+ fig = px.bar(counts, x=counts.index, y=counts.values, title=f"Top 20 Categories for {x_col}", labels={'index': x_col, 'y': 'Count'})
 
130
  if fig:
131
  fig.update_layout(template="plotly_dark")
132
  state_dict['dashboard_plots'].append(fig)
 
140
  return state_dict, []
141
 
142
  def respond_to_chat(user_message, history, state_dict, api_key):
143
+ if not api_key or not state_dict:
144
+ msg = "I need a Gemini API key and a dataset to work."
145
+ history.append((user_message, msg)); return history, *[gr.update(visible=False)]*4
146
+ history.append((user_message, None)); metadata = state_dict['metadata']
147
+ prompt = f"You are 'Phoenix Co-pilot'... IMPORTANT: add `template='plotly_dark'` to all figures... User Question: '{user_message}'"
 
 
 
 
 
 
 
 
148
  try:
149
  genai.configure(api_key=api_key)
150
+ response_json = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text.strip().replace("```json", "").replace("```", ""))
151
+ thought, code_to_run, explanation = response_json.get("thought", "Thinking..."), response_json.get("code", ""), response_json.get("explanation", "Here is the result.")
 
 
 
 
 
152
  stdout, fig_result, df_result, error = safe_exec(code_to_run, {'df': state_dict['df'], 'px': px, 'pd': pd, 'np': np})
153
  history[-1] = (user_message, f"πŸ€” **Thought:** *{thought}*")
 
154
  output_updates = [gr.update(visible=False, value=None)] * 4
155
  if explanation: output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** {explanation}")
156
  if code_to_run: output_updates[1] = gr.update(visible=True, value=code_to_run)
 
159
  if stdout:
160
  new_explanation = (output_updates[0]['value'] if output_updates[0]['visible'] else "") + f"\n\n**Console Output:**\n```\n{stdout}\n```"
161
  output_updates[0] = gr.update(visible=True, value=new_explanation)
162
+ if error: output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** I encountered an error:\n\n`{error}`")
 
163
  return history, *output_updates
164
  except Exception as e:
165
+ history[-1] = (user_message, f"A critical error occurred: {e}."); return history, *[gr.update(visible=False)]*4
 
166
 
167
  # --- Gradio UI Definition ---
168
  def create_gradio_interface():
169
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="Phoenix AI Data Explorer") as demo:
170
  global_state = gr.State({})
171
 
172
+ # "Define-Then-Place" Pattern
173
+ # 1. DEFINE all components
174
+ # Sidebar
175
+ # CORRECTED: Added elem_id for robust navigation
176
+ cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected", elem_id="cockpit")
177
+ deep_dive_btn = gr.Button("πŸ” Deep Dive Builder", elem_id="deep_dive")
178
+ copilot_btn = gr.Button("πŸ€– AI Co-pilot", elem_id="co-pilot")
179
+ file_input, status_output = gr.File(label="πŸ“ Upload New CSV", file_types=[".csv"]), gr.Markdown("Status: Awaiting data...")
180
+ api_key_input, suggestion_btn = gr.Textbox(label="πŸ”‘ Gemini API Key", type="password", placeholder="Enter key..."), gr.Button("Get Smart Suggestions", variant="secondary")
181
 
182
+ # Cockpit
183
+ rows_stat, cols_stat = gr.Textbox("0", show_label=False, interactive=False, elem_classes="stat-card-value"), gr.Textbox("0", show_label=False, interactive=False, elem_classes="stat-card-value")
184
+ quality_stat, time_cols_stat = gr.Textbox("0%", show_label=False, interactive=False, elem_classes="stat-card-value"), gr.Textbox("0", show_label=False, interactive=False, elem_classes="stat-card-value")
185
+ suggestion_status, suggestion_buttons = gr.Markdown(visible=True), [gr.Button(visible=False) for _ in range(5)]
 
 
 
 
186
 
187
+ # Deep Dive
188
+ plot_type_dd, x_col_dd, y_col_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram'), gr.Dropdown([], label="X-Axis"), gr.Dropdown([], label="Y-Axis")
189
+ add_plot_btn, clear_plots_btn = gr.Button("Add to Dashboard", variant="primary"), gr.Button("Clear Dashboard")
 
 
 
 
 
 
 
 
 
 
 
190
  dashboard_gallery = gr.Gallery(label="πŸ“Š Your Custom Dashboard", height="auto", columns=2, preview=True)
191
 
192
+ # Co-pilot
193
  chatbot = gr.Chatbot(height=400, label="Conversation with Co-pilot", show_copy_button=True)
194
+ copilot_explanation, copilot_code = gr.Markdown(visible=False, elem_classes="explanation-block"), gr.Code(language="python", visible=False, label="Executed Code")
195
+ copilot_plot, copilot_table = gr.Plot(visible=False, label="Generated Visualization"), gr.Dataframe(visible=False, label="Generated Table", wrap=True)
196
+ chat_input, chat_submit_btn = gr.Textbox(label="Your Question", placeholder="e.g., 'What is the correlation between age and salary?'", scale=4), gr.Button("Submit", variant="primary")
 
 
 
197
 
198
+ # 2. PLACE components into layout
199
  with gr.Row():
 
200
  with gr.Column(scale=1, elem_classes="sidebar"):
201
+ gr.Markdown("## πŸš€ Phoenix UI"); cockpit_btn; deep_dive_btn; copilot_btn; gr.Markdown("---")
202
+ file_input; status_output; gr.Markdown("---"); api_key_input; suggestion_btn
 
 
 
 
 
 
 
 
 
 
203
  with gr.Column(scale=4):
204
  with gr.Column(visible=True) as welcome_page:
205
+ gr.Markdown("# Welcome to the AI Data Explorer (Phoenix UI)\n Please **upload a CSV file** and **enter your Gemini API key** to begin.")
 
206
  gr.Image(value="workflow.png", show_label=False, show_download_button=False, container=False)
 
207
  with gr.Column(visible=False) as cockpit_page:
208
  gr.Markdown("## πŸ“Š Data Cockpit")
209
  with gr.Row():
210
+ with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Rows</div>"); rows_stat
211
+ with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Columns</div>"); cols_stat
212
+ with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Data Quality</div>"); quality_stat
213
+ with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>"); time_cols_stat
214
+ suggestion_status;
215
+ with gr.Accordion(label="✨ AI Smart Suggestions", open=True): [btn for btn in suggestion_buttons]
 
 
 
 
 
 
 
216
  with gr.Column(visible=False) as deep_dive_page:
217
+ gr.Markdown("## πŸ” Deep Dive Dashboard Builder"); gr.Markdown("Create a custom dashboard by adding plots to the gallery.")
218
+ with gr.Row(): plot_type_dd; x_col_dd; y_col_dd
219
+ with gr.Row(): add_plot_btn; clear_plots_btn
 
 
 
220
  dashboard_gallery
 
221
  with gr.Column(visible=False) as copilot_page:
222
+ gr.Markdown("## πŸ€– AI Co-pilot"); chatbot
223
+ with gr.Accordion("Co-pilot's Response Details", open=True): copilot_explanation; copilot_code; copilot_plot; copilot_table
224
+ with gr.Row(): chat_input; chat_submit_btn
 
 
 
225
 
226
+ # 3. DEFINE event handlers
227
  pages = [cockpit_page, deep_dive_page, copilot_page]
228
  nav_buttons = [cockpit_btn, deep_dive_btn, copilot_btn]
229
 
230
+ # CORRECTED: Navigation logic is now robust and reliable
231
  for i, btn in enumerate(nav_buttons):
232
+ btn.click(lambda id=btn.elem_id: switch_page(id), outputs=pages) \
 
233
  .then(lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
234
 
235
  file_input.upload(load_and_process_file, [file_input, global_state],
236
  [global_state, status_output, welcome_page, cockpit_page, deep_dive_page, copilot_page,
237
+ rows_stat, cols_stat, quality_stat, time_cols_stat, x_col_dd, y_col_dd, plot_type_dd]) \
 
238
  .then(lambda: switch_page("cockpit"), outputs=pages) \
239
  .then(lambda: [gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")], outputs=nav_buttons)
240
 
 
248
  clear_plots_btn.click(clear_dashboard, [global_state], [global_state, dashboard_gallery])
249
 
250
  chat_submit_btn.click(respond_to_chat, [chat_input, chatbot, global_state, api_key_input],
251
+ [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]).then(lambda: "", outputs=[chat_input])
 
252
  chat_input.submit(respond_to_chat, [chat_input, chatbot, global_state, api_key_input],
253
+ [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]).then(lambda: "", outputs=[chat_input])
 
254
 
255
  return demo
256