mgbam commited on
Commit
51fb89c
Β·
verified Β·
1 Parent(s): f72077b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +43 -64
app.py CHANGED
@@ -13,19 +13,22 @@ from contextlib import redirect_stdout
13
 
14
  # --- Configuration ---
15
  warnings.filterwarnings('ignore')
 
 
 
16
  CSS = """
17
- /* --- Phoenix UI Custom CSS --- */
18
  /* Stat Card Styling */
19
  .stat-card {
20
  border-radius: 12px !important;
21
  padding: 20px !important;
22
- background: #f7fafc; /* light gray background */
23
- border: 1px solid #e2e8f0;
24
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
25
  text-align: center;
26
  }
27
- .stat-card-title { font-size: 16px; font-weight: 500; color: #4a5568; margin-bottom: 8px; }
28
- .stat-card-value { font-size: 32px; font-weight: 700; color: #2d3748; }
29
 
30
  /* General Layout & Feel */
31
  .gradio-container { font-family: 'Inter', sans-serif; }
@@ -33,9 +36,9 @@ CSS = """
33
 
34
  /* Sidebar Styling */
35
  .sidebar {
36
- background-color: #f9fafb;
37
  padding: 15px;
38
- border-right: 1px solid #e5e7eb;
39
  min-height: 100vh;
40
  }
41
  .sidebar .gr-button {
@@ -44,18 +47,23 @@ CSS = """
44
  background: none !important;
45
  border: none !important;
46
  box-shadow: none !important;
47
- color: #374151 !important;
48
  font-size: 16px !important;
49
  padding: 12px 10px !important;
50
  margin-bottom: 8px !important;
51
  border-radius: 8px !important;
52
  }
53
- .sidebar .gr-button:hover { background-color: #e5e7eb !important; }
54
- .sidebar .gr-button.selected { background-color: #d1d5db !important; font-weight: 600 !important; }
55
 
56
  /* AI Co-pilot Styling */
57
- .code-block { border: 1px solid #e5e7eb; border-radius: 8px; }
58
- .explanation-block { background-color: #f0f9ff; border-left: 4px solid #3b82f6; padding: 12px; }
 
 
 
 
 
59
  """
60
 
61
  # --- Helper Functions ---
@@ -140,8 +148,8 @@ def switch_page(page_name):
140
  # --- Page 1: Data Cockpit ---
141
  def get_ai_suggestions(state_dict, api_key):
142
  """Generates proactive analytical suggestions from the AI."""
143
- if not api_key: return "Enter your Gemini API key to get suggestions.", gr.update(visible=False)
144
- if not state_dict: return "Upload data first.", gr.update(visible=False)
145
 
146
  metadata = state_dict['metadata']
147
  prompt = f"""
@@ -159,9 +167,7 @@ def get_ai_suggestions(state_dict, api_key):
159
  response = model.generate_content(prompt)
160
  suggestions = json.loads(response.text)
161
 
162
- # Create a button for each suggestion
163
  buttons = [gr.Button(s, variant="secondary", visible=True) for s in suggestions]
164
- # Pad with hidden buttons to always have 5 outputs
165
  buttons += [gr.Button(visible=False)] * (5 - len(buttons))
166
 
167
  return gr.update(visible=False), *buttons
@@ -197,9 +203,9 @@ def add_plot_to_dashboard(state_dict, x_col, y_col, plot_type):
197
  fig.update_xaxes(title=x_col)
198
 
199
  if fig:
 
200
  state_dict['dashboard_plots'].append(fig)
201
 
202
- # Rebuild the accordion with all plots
203
  accordion_children = [gr.Plot(fig, visible=True) for fig in state_dict['dashboard_plots']]
204
  return state_dict, gr.Accordion(label="Your Dashboard Plots", children=accordion_children, open=True)
205
  except Exception as e:
@@ -232,7 +238,7 @@ def respond_to_chat(user_message, history, state_dict, api_key):
232
  2. Formulate a plan (thought process).
233
  3. Write Python code to execute that plan.
234
  4. The code can use pandas (pd), numpy (np), and plotly.express (px).
235
- 5. **For plots, assign the figure to a variable `fig` (e.g., `fig = px.histogram(...)`).**
236
  6. **For table-like results, assign the final DataFrame to a variable `result_df` (e.g., `result_df = df.describe()`).**
237
  7. Do not modify the original `df`. Use `df.copy()` if needed.
238
  8. Provide a brief, user-friendly explanation of the result.
@@ -264,15 +270,13 @@ def respond_to_chat(user_message, history, state_dict, api_key):
264
  bot_message = f"πŸ€” **Thought:** *{thought}*"
265
  history[-1] = (user_message, bot_message)
266
 
267
- # Prepare outputs, making them visible only if they contain content
268
- output_updates = [gr.update(visible=False, value=None)] * 4 # [explanation, code, plot, table]
269
 
270
  if explanation: output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** {explanation}")
271
  if code_to_run: output_updates[1] = gr.update(visible=True, value=code_to_run)
272
  if fig_result: output_updates[2] = gr.update(visible=True, value=fig_result)
273
  if df_result is not None: output_updates[3] = gr.update(visible=True, value=df_result)
274
  if stdout:
275
- # Append stdout to explanation if it exists
276
  new_explanation = (output_updates[0]['value'] if output_updates[0]['visible'] else "") + f"\n\n**Console Output:**\n```\n{stdout}\n```"
277
  output_updates[0] = gr.update(visible=True, value=new_explanation)
278
  if error:
@@ -288,14 +292,14 @@ def respond_to_chat(user_message, history, state_dict, api_key):
288
 
289
  # --- Gradio UI Definition ---
290
  def create_gradio_interface():
291
- with gr.Blocks(theme=gr.themes.Monochrome(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="Phoenix AI Data Explorer") as demo:
 
292
  global_state = gr.State({})
293
 
294
  with gr.Row():
295
  # --- Sidebar ---
296
  with gr.Column(scale=1, elem_classes="sidebar"):
297
- gr.Markdown("# πŸš€ Phoenix UI")
298
- gr.Markdown("AI Data Explorer")
299
 
300
  # Navigation buttons
301
  cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected")
@@ -316,7 +320,6 @@ def create_gradio_interface():
316
  with gr.Column(visible=True) as welcome_page:
317
  gr.Markdown("# Welcome to the AI Data Explorer (Phoenix UI)", elem_id="welcome-header")
318
  gr.Markdown("Please **upload a CSV file** and **enter your Gemini API key** in the sidebar to begin.")
319
- # CORRECTED: Uses a local file, 'workflow.png', which must be in the same directory.
320
  gr.Image(value="workflow.png", label="Workflow", show_label=False, show_download_button=False, container=False)
321
 
322
  # Page 1: Data Cockpit (Hidden initially)
@@ -324,16 +327,16 @@ def create_gradio_interface():
324
  gr.Markdown("## πŸ“Š Data Cockpit")
325
  with gr.Row():
326
  with gr.Column(elem_classes="stat-card"):
327
- gr.Markdown("<div class='stat-card-title'>Rows</div>", elem_classes="stat-card-content")
328
  rows_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
329
  with gr.Column(elem_classes="stat-card"):
330
- gr.Markdown("<div class='stat-card-title'>Columns</div>", elem_classes="stat-card-content")
331
  cols_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
332
  with gr.Column(elem_classes="stat-card"):
333
- gr.Markdown("<div class='stat-card-title'>Data Quality</div>", elem_classes="stat-card-content")
334
  quality_stat = gr.Textbox("0%", show_label=False, elem_classes="stat-card-value")
335
  with gr.Column(elem_classes="stat-card"):
336
- gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>", elem_classes="stat-card-content")
337
  time_cols_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
338
 
339
  suggestion_status = gr.Markdown(visible=True)
@@ -374,13 +377,8 @@ def create_gradio_interface():
374
  nav_buttons = [cockpit_btn, deep_dive_btn, copilot_btn]
375
 
376
  for i, btn in enumerate(nav_buttons):
377
- btn.click(
378
- lambda i=i: (gr.update(visible=i==0), gr.update(visible=i==1), gr.update(visible=i==2)),
379
- outputs=pages
380
- ).then(
381
- lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))],
382
- outputs=nav_buttons
383
- )
384
 
385
  file_input.upload(
386
  fn=load_and_process_file,
@@ -388,41 +386,22 @@ def create_gradio_interface():
388
  outputs=[global_state, status_output, welcome_page, cockpit_page, deep_dive_page, copilot_page,
389
  rows_stat, cols_stat, quality_stat, time_cols_stat,
390
  x_col_dd, y_col_dd, plot_type_dd]
391
- ).then(
392
- lambda: (gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)), outputs=pages
393
- ).then(
394
- lambda: (gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")), outputs=nav_buttons
395
- )
396
 
397
- suggestion_btn.click(
398
- get_ai_suggestions,
399
- [global_state, api_key_input],
400
- [suggestion_status, *suggestion_buttons]
401
- )
402
 
403
  for btn in suggestion_buttons:
404
- btn.click(
405
- handle_suggestion_click,
406
- inputs=[btn],
407
- outputs=[cockpit_page, deep_dive_page, copilot_page, chat_input]
408
- ).then(
409
- lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")),
410
- outputs=nav_buttons
411
- )
412
 
413
  add_plot_btn.click(add_plot_to_dashboard, [global_state, x_col_dd, y_col_dd, plot_type_dd], [global_state, dashboard_accordion])
414
  clear_plots_btn.click(clear_dashboard, [global_state], [global_state, dashboard_accordion])
415
 
416
- chat_submit_btn.click(
417
- respond_to_chat,
418
- [chat_input, chatbot, global_state, api_key_input],
419
- [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]
420
- ).then(lambda: "", outputs=[chat_input])
421
- chat_input.submit(
422
- respond_to_chat,
423
- [chat_input, chatbot, global_state, api_key_input],
424
- [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]
425
- ).then(lambda: "", outputs=[chat_input])
426
 
427
  return demo
428
 
 
13
 
14
  # --- Configuration ---
15
  warnings.filterwarnings('ignore')
16
+
17
+ # --- NEW: Expert-Crafted Dark Theme CSS ---
18
+ # This CSS is designed to work with a dark theme, ensuring all elements are visible and stylish.
19
  CSS = """
20
+ /* --- Phoenix UI Custom Dark CSS --- */
21
  /* Stat Card Styling */
22
  .stat-card {
23
  border-radius: 12px !important;
24
  padding: 20px !important;
25
+ background: #1f2937 !important; /* Dark blue-gray background */
26
+ border: 1px solid #374151 !important;
27
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
28
  text-align: center;
29
  }
30
+ .stat-card-title { font-size: 16px; font-weight: 500; color: #9ca3af !important; margin-bottom: 8px; }
31
+ .stat-card-value { font-size: 32px; font-weight: 700; color: #f9fafb !important; }
32
 
33
  /* General Layout & Feel */
34
  .gradio-container { font-family: 'Inter', sans-serif; }
 
36
 
37
  /* Sidebar Styling */
38
  .sidebar {
39
+ background-color: #111827 !important; /* Very dark blue-gray */
40
  padding: 15px;
41
+ border-right: 1px solid #374151 !important;
42
  min-height: 100vh;
43
  }
44
  .sidebar .gr-button {
 
47
  background: none !important;
48
  border: none !important;
49
  box-shadow: none !important;
50
+ color: #d1d5db !important; /* Light gray text for readability */
51
  font-size: 16px !important;
52
  padding: 12px 10px !important;
53
  margin-bottom: 8px !important;
54
  border-radius: 8px !important;
55
  }
56
+ .sidebar .gr-button:hover { background-color: #374151 !important; } /* Hover state */
57
+ .sidebar .gr-button.selected { background-color: #4f46e5 !important; font-weight: 600 !important; color: white !important; } /* Selected state with primary color */
58
 
59
  /* AI Co-pilot Styling */
60
+ .code-block { border: 1px solid #374151 !important; border-radius: 8px; }
61
+ .explanation-block {
62
+ background-color: #1e3a8a !important; /* Dark blue background */
63
+ border-left: 4px solid #3b82f6 !important; /* Brighter blue border */
64
+ padding: 12px;
65
+ color: #e5e7eb !important;
66
+ }
67
  """
68
 
69
  # --- Helper Functions ---
 
148
  # --- Page 1: Data Cockpit ---
149
  def get_ai_suggestions(state_dict, api_key):
150
  """Generates proactive analytical suggestions from the AI."""
151
+ if not api_key: return "Enter your Gemini API key to get suggestions.", *[gr.update(visible=False)]*5
152
+ if not state_dict: return "Upload data first.", *[gr.update(visible=False)]*5
153
 
154
  metadata = state_dict['metadata']
155
  prompt = f"""
 
167
  response = model.generate_content(prompt)
168
  suggestions = json.loads(response.text)
169
 
 
170
  buttons = [gr.Button(s, variant="secondary", visible=True) for s in suggestions]
 
171
  buttons += [gr.Button(visible=False)] * (5 - len(buttons))
172
 
173
  return gr.update(visible=False), *buttons
 
203
  fig.update_xaxes(title=x_col)
204
 
205
  if fig:
206
+ fig.update_layout(template="plotly_dark") # Ensure plots match the dark theme
207
  state_dict['dashboard_plots'].append(fig)
208
 
 
209
  accordion_children = [gr.Plot(fig, visible=True) for fig in state_dict['dashboard_plots']]
210
  return state_dict, gr.Accordion(label="Your Dashboard Plots", children=accordion_children, open=True)
211
  except Exception as e:
 
238
  2. Formulate a plan (thought process).
239
  3. Write Python code to execute that plan.
240
  4. The code can use pandas (pd), numpy (np), and plotly.express (px).
241
+ 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.**
242
  6. **For table-like results, assign the final DataFrame to a variable `result_df` (e.g., `result_df = df.describe()`).**
243
  7. Do not modify the original `df`. Use `df.copy()` if needed.
244
  8. Provide a brief, user-friendly explanation of the result.
 
270
  bot_message = f"πŸ€” **Thought:** *{thought}*"
271
  history[-1] = (user_message, bot_message)
272
 
273
+ output_updates = [gr.update(visible=False, value=None)] * 4
 
274
 
275
  if explanation: output_updates[0] = gr.update(visible=True, value=f"**Phoenix Co-pilot:** {explanation}")
276
  if code_to_run: output_updates[1] = gr.update(visible=True, value=code_to_run)
277
  if fig_result: output_updates[2] = gr.update(visible=True, value=fig_result)
278
  if df_result is not None: output_updates[3] = gr.update(visible=True, value=df_result)
279
  if stdout:
 
280
  new_explanation = (output_updates[0]['value'] if output_updates[0]['visible'] else "") + f"\n\n**Console Output:**\n```\n{stdout}\n```"
281
  output_updates[0] = gr.update(visible=True, value=new_explanation)
282
  if error:
 
292
 
293
  # --- Gradio UI Definition ---
294
  def create_gradio_interface():
295
+ # --- CORRECTED: Using a standard dark theme + custom dark CSS ---
296
+ with gr.Blocks(theme=gr.themes.Default(primary_hue="indigo", secondary_hue="blue").dark(), css=CSS, title="Phoenix AI Data Explorer") as demo:
297
  global_state = gr.State({})
298
 
299
  with gr.Row():
300
  # --- Sidebar ---
301
  with gr.Column(scale=1, elem_classes="sidebar"):
302
+ gr.Markdown("## πŸš€ Phoenix UI")
 
303
 
304
  # Navigation buttons
305
  cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected")
 
320
  with gr.Column(visible=True) as welcome_page:
321
  gr.Markdown("# Welcome to the AI Data Explorer (Phoenix UI)", elem_id="welcome-header")
322
  gr.Markdown("Please **upload a CSV file** and **enter your Gemini API key** in the sidebar to begin.")
 
323
  gr.Image(value="workflow.png", label="Workflow", show_label=False, show_download_button=False, container=False)
324
 
325
  # Page 1: Data Cockpit (Hidden initially)
 
327
  gr.Markdown("## πŸ“Š Data Cockpit")
328
  with gr.Row():
329
  with gr.Column(elem_classes="stat-card"):
330
+ gr.Markdown("<div class='stat-card-title'>Rows</div>")
331
  rows_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
332
  with gr.Column(elem_classes="stat-card"):
333
+ gr.Markdown("<div class='stat-card-title'>Columns</div>")
334
  cols_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
335
  with gr.Column(elem_classes="stat-card"):
336
+ gr.Markdown("<div class='stat-card-title'>Data Quality</div>")
337
  quality_stat = gr.Textbox("0%", show_label=False, elem_classes="stat-card-value")
338
  with gr.Column(elem_classes="stat-card"):
339
+ gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>")
340
  time_cols_stat = gr.Textbox("0", show_label=False, elem_classes="stat-card-value")
341
 
342
  suggestion_status = gr.Markdown(visible=True)
 
377
  nav_buttons = [cockpit_btn, deep_dive_btn, copilot_btn]
378
 
379
  for i, btn in enumerate(nav_buttons):
380
+ btn.click(lambda i=i: (gr.update(visible=i==0), gr.update(visible=i==1), gr.update(visible=i==2)), outputs=pages) \
381
+ .then(lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
 
 
 
 
 
382
 
383
  file_input.upload(
384
  fn=load_and_process_file,
 
386
  outputs=[global_state, status_output, welcome_page, cockpit_page, deep_dive_page, copilot_page,
387
  rows_stat, cols_stat, quality_stat, time_cols_stat,
388
  x_col_dd, y_col_dd, plot_type_dd]
389
+ ).then(lambda: (gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)), outputs=pages) \
390
+ .then(lambda: (gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")), outputs=nav_buttons)
 
 
 
391
 
392
+ suggestion_btn.click(get_ai_suggestions, [global_state, api_key_input], [suggestion_status, *suggestion_buttons])
 
 
 
 
393
 
394
  for btn in suggestion_buttons:
395
+ btn.click(handle_suggestion_click, inputs=[btn], outputs=[cockpit_page, deep_dive_page, copilot_page, chat_input]) \
396
+ .then(lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")), outputs=nav_buttons)
 
 
 
 
 
 
397
 
398
  add_plot_btn.click(add_plot_to_dashboard, [global_state, x_col_dd, y_col_dd, plot_type_dd], [global_state, dashboard_accordion])
399
  clear_plots_btn.click(clear_dashboard, [global_state], [global_state, dashboard_accordion])
400
 
401
+ chat_submit_btn.click(respond_to_chat, [chat_input, chatbot, global_state, api_key_input], [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]) \
402
+ .then(lambda: "", outputs=[chat_input])
403
+ chat_input.submit(respond_to_chat, [chat_input, chatbot, global_state, api_key_input], [chatbot, copilot_explanation, copilot_code, copilot_plot, copilot_table]) \
404
+ .then(lambda: "", outputs=[chat_input])
 
 
 
 
 
 
405
 
406
  return demo
407