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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -52
app.py CHANGED
@@ -65,7 +65,7 @@ CSS = """
65
  }
66
  """
67
 
68
- # --- Helper Functions ---
69
  def safe_exec(code_string: str, local_vars: dict):
70
  output_buffer = io.StringIO()
71
  try:
@@ -78,7 +78,6 @@ def safe_exec(code_string: str, local_vars: dict):
78
  except Exception as e:
79
  return None, None, None, f"Execution Error: {str(e)}"
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:
@@ -111,23 +110,15 @@ def extract_dataset_metadata(df: pd.DataFrame):
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):
120
  if not api_key: return "Enter your Gemini API key to get suggestions.", *[gr.update(visible=False)]*5
121
  if not state_dict: return "Upload data first.", *[gr.update(visible=False)]*5
122
-
123
  metadata = state_dict['metadata']
124
  prompt = f"""
125
- Based on the following dataset metadata, generate 3 to 5 specific, actionable, and interesting analytical questions a user might want to ask. Frame them as questions.
126
- - **Columns:** {', '.join(metadata['columns'])}
127
- - **Numeric:** {', '.join(metadata['numeric_cols'])}
128
- - **Categorical:** {', '.join(metadata['categorical_cols'])}
129
- - **Datetime:** {', '.join(metadata['datetime_cols'])}
130
-
131
  Return ONLY a JSON list of strings. Example: ["What is the trend of sales over time?", "Which category has the highest average price?"]
132
  """
133
  try:
@@ -138,22 +129,18 @@ def get_ai_suggestions(state_dict, api_key):
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}"
155
  fig = None
156
-
157
  try:
158
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
159
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
@@ -162,12 +149,9 @@ def add_plot_to_dashboard(state_dict, x_col, y_col, plot_type):
162
  counts = df[x_col].value_counts().nlargest(20)
163
  fig = px.bar(counts, x=counts.index, y=counts.values, title=f"Top 20 Categories for {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}")
@@ -175,18 +159,15 @@ def add_plot_to_dashboard(state_dict, x_col, y_col, plot_type):
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):
183
  if not api_key:
184
- history.append((user_message, "I need a Gemini API key to function. Please provide it in the sidebar."))
185
  return history, *[gr.update(visible=False)] * 4
186
  if not state_dict:
187
  history.append((user_message, "Please upload a dataset first."))
188
  return history, *[gr.update(visible=False)] * 4
189
-
190
  history.append((user_message, None))
191
  metadata = state_dict['metadata']
192
  prompt = f"""
@@ -203,9 +184,7 @@ def respond_to_chat(user_message, history, state_dict, api_key):
203
  thought = response_json.get("thought", "Thinking...")
204
  code_to_run = response_json.get("code", "")
205
  explanation = response_json.get("explanation", "Here is the result.")
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
@@ -218,10 +197,9 @@ def respond_to_chat(user_message, history, state_dict, api_key):
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 ---
@@ -229,58 +207,105 @@ 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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  with gr.Row():
233
- # Sidebar
234
  with gr.Column(scale=1, elem_classes="sidebar"):
235
  gr.Markdown("## πŸš€ Phoenix UI")
236
- cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected")
237
- deep_dive_btn = gr.Button("πŸ” Deep Dive Builder")
238
- copilot_btn = gr.Button("πŸ€– AI Co-pilot")
239
  gr.Markdown("---")
240
- file_input = gr.File(label="πŸ“ Upload New CSV", file_types=[".csv"])
241
- status_output = gr.Markdown("Status: Awaiting data...")
242
  gr.Markdown("---")
243
- api_key_input = gr.Textbox(label="πŸ”‘ Gemini API Key", type="password", placeholder="Enter key here...")
244
- suggestion_btn = gr.Button("Get Smart Suggestions", variant="secondary")
245
 
246
- # Main Content Area
247
  with gr.Column(scale=4):
248
  with gr.Column(visible=True) as welcome_page:
249
  gr.Markdown("# Welcome to the AI Data Explorer (Phoenix UI)")
250
  gr.Markdown("Please **upload a CSV file** and **enter your Gemini API key** in the sidebar to begin.")
251
- gr.Image(value="workflow.png", label="Workflow", show_label=False, show_download_button=False, container=False)
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")
266
- y_col_dd = gr.Dropdown([], label="Y-Axis (for Scatter/Box)")
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],
@@ -296,7 +321,6 @@ def create_gradio_interface():
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
 
 
65
  }
66
  """
67
 
68
+ # --- Helper and Core Functions (Unchanged) ---
69
  def safe_exec(code_string: str, local_vars: dict):
70
  output_buffer = io.StringIO()
71
  try:
 
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
83
  try:
 
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:
 
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)
 
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)
 
 
155
  return state_dict, state_dict['dashboard_plots']
156
  except Exception as e:
157
  gr.Warning(f"Plotting Error: {e}")
 
159
 
160
  def clear_dashboard(state_dict):
161
  state_dict['dashboard_plots'] = []
 
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"""
 
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
 
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 ---
 
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],
 
321
  btn.click(handle_suggestion_click, inputs=[btn], outputs=[cockpit_page, deep_dive_page, copilot_page, chat_input]) \
322
  .then(lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")), outputs=nav_buttons)
323
 
 
324
  add_plot_btn.click(add_plot_to_dashboard, [global_state, x_col_dd, y_col_dd, plot_type_dd], [global_state, dashboard_gallery])
325
  clear_plots_btn.click(clear_dashboard, [global_state], [global_state, dashboard_gallery])
326