mgbam commited on
Commit
06fc0da
Β·
verified Β·
1 Parent(s): 5effbd3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -78
app.py CHANGED
@@ -14,7 +14,7 @@ warnings.filterwarnings('ignore')
14
 
15
  CSS = """
16
  /* --- Phoenix UI Professional Dark CSS --- */
17
- #app-title { text-align: center; font-weight: 800; font-size: 2.5rem; }
18
  .stat-card { border-radius: 12px !important; padding: 20px !important; background: #1f2937 !important; border: 1px solid #374151 !important; text-align: center; transition: all 0.3s ease; }
19
  .stat-card:hover { transform: translateY(-5px); box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); }
20
  .stat-card-title { font-size: 16px; font-weight: 500; color: #9ca3af !important; margin-bottom: 8px; }
@@ -25,62 +25,48 @@ CSS = """
25
  .sidebar .gr-button.selected { background-color: #4f46e5 !important; font-weight: 600 !important; color: white !important; }
26
  .explanation-block { background-color: #1e3a8a !important; border-left: 4px solid #3b82f6 !important; padding: 12px; color: #e5e7eb !important; border-radius: 4px; }
27
  """
 
28
 
29
  class 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
- """Defines, arranges, and connects all UI components and logic."""
38
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="AI Data Explorer Pro") as demo:
39
  state_var = gr.State({})
40
 
41
  # --- Component Definition ---
42
- # Sidebar
43
- cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected", elem_id="cockpit")
44
- deep_dive_btn = gr.Button("πŸ” Deep Dive Builder", elem_id="deep_dive")
45
- copilot_btn = gr.Button("πŸ€– Chief Data Scientist", elem_id="co-pilot")
46
- file_input = gr.File(label="πŸ“ Upload CSV File", file_types=[".csv"])
47
- status_output = gr.Markdown("Status: Awaiting data...")
48
  api_key_input = gr.Textbox(label="πŸ”‘ Gemini API Key", type="password", placeholder="Enter key to enable AI...")
49
  suggestion_btn = gr.Button("Get Smart Suggestions", variant="secondary", interactive=False)
50
 
51
- # Cockpit
52
  rows_stat, cols_stat = gr.Textbox("0", interactive=False, elem_classes="stat-card-value", show_label=False), gr.Textbox("0", interactive=False, elem_classes="stat-card-value", show_label=False)
53
  quality_stat, time_cols_stat = gr.Textbox("0%", interactive=False, elem_classes="stat-card-value", show_label=False), gr.Textbox("0", interactive=False, elem_classes="stat-card-value", show_label=False)
54
  suggestion_buttons = [gr.Button(visible=False) for _ in range(5)]
55
 
56
- # Deep Dive
57
  plot_type_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram')
58
  x_col_dd = gr.Dropdown([], label="X-Axis / Column", interactive=False)
59
  y_col_dd = gr.Dropdown([], label="Y-Axis (for Scatter/Box)", visible=False, interactive=False)
60
- add_plot_btn = gr.Button("Add to Dashboard", variant="primary", interactive=False)
61
- clear_plots_btn = gr.Button("Clear Dashboard")
62
- dashboard_gallery = gr.Gallery(label="πŸ“Š Your Custom Dashboard", height="auto", columns=[1, 2], preview=True)
63
-
64
- # Co-pilot
65
- chatbot = gr.Chatbot(height=500, label="Conversation", show_copy_button=True, avatar_images=("user.png", "bot.png"))
66
- copilot_explanation = gr.Markdown(visible=False, elem_classes="explanation-block")
67
- copilot_code = gr.Code(language="python", visible=False, label="Executed Code")
68
- copilot_plot = gr.Plot(visible=False, label="Generated Visualization")
69
- copilot_table = gr.Dataframe(visible=False, label="Generated Table", wrap=True)
70
- chat_input = gr.Textbox(label="Your Question", placeholder="e.g., 'What is the relationship between age and salary?'", scale=4)
71
- chat_submit_btn = gr.Button("Ask AI", variant="primary")
72
 
 
 
 
 
 
 
 
73
  # --- Layout Arrangement ---
74
  with gr.Row():
75
  with gr.Column(scale=1, elem_classes="sidebar"):
76
  gr.Markdown("## πŸš€ AI Explorer Pro", elem_id="app-title"); cockpit_btn; deep_dive_btn; copilot_btn; gr.Markdown("---")
77
  file_input; status_output; gr.Markdown("---"); api_key_input; suggestion_btn
78
  with gr.Column(scale=4):
79
- welcome_page = gr.Column(visible=True)
80
- with welcome_page:
81
- 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.")
82
-
83
- cockpit_page = gr.Column(visible=False)
84
  with cockpit_page:
85
  gr.Markdown("## πŸ“Š Data Cockpit: At-a-Glance Overview")
86
  with gr.Row():
@@ -89,60 +75,47 @@ class DataExplorerApp:
89
  with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Data Quality</div>"); quality_stat
90
  with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>"); time_cols_stat
91
  with gr.Accordion(label="✨ AI Smart Suggestions", open=True): [btn for btn in suggestion_buttons]
92
-
93
- deep_dive_page = gr.Column(visible=False)
94
  with deep_dive_page:
95
- gr.Markdown("## πŸ” Deep Dive: Manual Dashboard Builder"); gr.Markdown("Construct your own visualizations to investigate specific relationships.")
96
  with gr.Row(): plot_type_dd; x_col_dd; y_col_dd
97
  with gr.Row(): add_plot_btn; clear_plots_btn
98
- dashboard_gallery
99
-
100
- copilot_page = gr.Column(visible=False)
101
  with copilot_page:
102
  gr.Markdown("## πŸ€– Chief Data Scientist: Your AI Partner"); chatbot
103
  with gr.Accordion("AI's Detailed Response", open=True): copilot_explanation; copilot_code; copilot_plot; copilot_table
104
  with gr.Row(): chat_input; chat_submit_btn
105
 
106
  # --- Event Handlers Registration ---
107
- pages = [welcome_page, cockpit_page, deep_dive_page, copilot_page]
108
- nav_buttons = [cockpit_btn, deep_dive_btn, copilot_btn]
109
-
110
  for i, btn in enumerate(nav_buttons):
111
  btn.click(lambda id=btn.elem_id: self._switch_page(id, pages), outputs=pages).then(
112
  lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
113
 
114
  file_input.upload(self.load_and_process_file, inputs=[file_input], outputs=[
115
- state_var, status_output, welcome_page, cockpit_page,
116
- rows_stat, cols_stat, quality_stat, time_cols_stat,
117
- x_col_dd, y_col_dd, add_plot_btn
118
- ]).then(lambda: self._switch_page("cockpit", pages), outputs=pages).then(
119
  lambda: [gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")], outputs=nav_buttons)
120
 
121
  api_key_input.change(lambda x: gr.update(interactive=bool(x)), inputs=[api_key_input], outputs=[suggestion_btn])
122
  plot_type_dd.change(self._update_plot_controls, inputs=[plot_type_dd], outputs=[y_col_dd])
123
- 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])
124
- clear_plots_btn.click(self.clear_dashboard, inputs=[state_var], outputs=[state_var, dashboard_gallery])
125
  suggestion_btn.click(self.get_ai_suggestions, inputs=[state_var, api_key_input], outputs=suggestion_buttons)
126
 
127
  for btn in suggestion_buttons:
128
- btn.click(self.handle_suggestion_click, inputs=[btn], outputs=[welcome_page, cockpit_page, deep_dive_page, copilot_page, chat_input]) \
129
- .then(lambda: self._switch_page("co-pilot", pages), outputs=pages) \
130
- .then(lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")), outputs=nav_buttons)
131
 
132
  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])
133
  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])
134
-
135
  return demo
136
 
137
- def launch(self):
138
- self.demo.launch(debug=True)
139
 
140
  def _switch_page(self, page_id: str, all_pages: List) -> List[gr.update]:
141
- visibility_updates = [gr.update(visible=False)] * len(all_pages)
142
- if page_id == "cockpit": visibility_updates[1] = gr.update(visible=True)
143
- elif page_id == "deep_dive": visibility_updates[2] = gr.update(visible=True)
144
- elif page_id == "co-pilot": visibility_updates[3] = gr.update(visible=True)
145
- return visibility_updates
146
 
147
  def _update_plot_controls(self, plot_type: str) -> gr.update:
148
  return gr.update(visible=plot_type in ['scatter', 'box'])
@@ -162,39 +135,46 @@ class DataExplorerApp:
162
  def _extract_dataset_metadata(self, df: pd.DataFrame) -> Dict[str, Any]:
163
  rows, cols = df.shape
164
  quality = round((df.notna().sum().sum() / (rows * cols)) * 100, 1) if rows * cols > 0 else 0
165
- return {'shape': (rows, cols), 'columns': df.columns.tolist(),
166
- 'numeric_cols': df.select_dtypes(include=np.number).columns.tolist(),
167
- 'categorical_cols': df.select_dtypes(include=['object', 'category']).columns.tolist(),
168
- 'datetime_cols': df.select_dtypes(include=['datetime64', 'datetime64[ns]']).columns.tolist(),
169
- 'dtypes_head': df.head(3).to_string(),
170
- 'data_quality': quality} # <--- CRITICAL FIX: KEY ADDED HERE
171
-
172
- def add_plot_to_dashboard(self, state: Dict, x_col: str, y_col: Optional[str], plot_type: str) -> Tuple[Dict, List]:
173
- if not x_col: gr.Warning("Please select at least an X-axis column."); return state, state.get('dashboard_plots', [])
174
- df = state['df']
175
- 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}"
176
  try:
177
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
178
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
179
- elif plot_type == 'scatter': fig = px.scatter(df, x=x_col, y=y_col, title=title, trendline="ols", trendline_color_override="red")
180
  elif plot_type == 'bar':
181
  counts = df[x_col].value_counts().nlargest(20)
182
  fig = px.bar(counts, x=counts.index, y=counts.values, title=f"Top 20 Categories for {x_col}", labels={'index': x_col, 'y': 'Count'})
183
  if fig:
184
- fig.update_layout(template="plotly_dark"); state['dashboard_plots'].append(fig); gr.Info(f"Added '{title}' to the dashboard.")
185
- return state, state['dashboard_plots']
186
- except Exception as e: gr.Error(f"Plotting Error: {e}"); return state, state.get('dashboard_plots', [])
 
 
 
 
 
 
 
 
187
 
188
- def clear_dashboard(self, state: Dict) -> Tuple[Dict, List]:
189
- state['dashboard_plots'] = []; gr.Info("Dashboard cleared."); return state, []
190
 
191
  def get_ai_suggestions(self, state: Dict, api_key: str) -> List[gr.update]:
192
  if not api_key: gr.Warning("API Key is required."); return [gr.update(visible=False)]*5
193
  if not state: gr.Warning("Please load data first."); return [gr.update(visible=False)]*5
194
- metadata, prompt = state['metadata'], f"From columns {metadata['columns']}, generate 4 impactful analytical questions. Return ONLY a JSON list of strings."
 
 
195
  try:
196
- genai.configure(api_key=api_key)
197
- suggestions = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text)
198
  return [gr.Button(s, visible=True) for s in suggestions] + [gr.Button(visible=False)] * (5 - len(suggestions))
199
  except Exception as e: gr.Error(f"AI Suggestion Error: {e}"); return [gr.update(visible=False)]*5
200
 
@@ -208,11 +188,11 @@ class DataExplorerApp:
208
 
209
  history.append((user_message, "Thinking... πŸ€”")); yield history, *[gr.update(visible=False)]*4
210
 
211
- metadata, prompt = state['metadata'], 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.
212
  **Instructions:**
213
- 1. **Analyze:** Understand the user's intent. Infer the best plot type if not specified.
214
- 2. **Plan:** Briefly explain your plan of attack.
215
- 3. **Code:** Write Python code. Use `fig` for plots (with `template='plotly_dark'`) and `result_df` for tables.
216
  4. **Insight:** Provide a one-sentence business insight.
217
  5. **Respond ONLY with a single JSON object with keys: "plan", "code", "insight".**
218
  **Metadata:** {metadata['dtypes_head']}
 
14
 
15
  CSS = """
16
  /* --- Phoenix UI Professional Dark CSS --- */
17
+ #app-title { text-align: center; font-weight: 800; font-size: 2.5rem; color: #f9fafb; }
18
  .stat-card { border-radius: 12px !important; padding: 20px !important; background: #1f2937 !important; border: 1px solid #374151 !important; text-align: center; transition: all 0.3s ease; }
19
  .stat-card:hover { transform: translateY(-5px); box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); }
20
  .stat-card-title { font-size: 16px; font-weight: 500; color: #9ca3af !important; margin-bottom: 8px; }
 
25
  .sidebar .gr-button.selected { background-color: #4f46e5 !important; font-weight: 600 !important; color: white !important; }
26
  .explanation-block { background-color: #1e3a8a !important; border-left: 4px solid #3b82f6 !important; padding: 12px; color: #e5e7eb !important; border-radius: 4px; }
27
  """
28
+ MAX_DASHBOARD_PLOTS = 10
29
 
30
  class DataExplorerApp:
31
  """A professional-grade, AI-powered data exploration application."""
32
 
33
  def __init__(self):
 
34
  self.demo = self._build_ui()
35
 
36
  def _build_ui(self) -> gr.Blocks:
 
37
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="AI Data Explorer Pro") as demo:
38
  state_var = gr.State({})
39
 
40
  # --- Component Definition ---
41
+ cockpit_btn, deep_dive_btn, copilot_btn = [gr.Button(elem_id=id) for id in ["cockpit", "deep_dive", "co-pilot"]]
42
+ file_input, status_output = gr.File(label="πŸ“ Upload CSV File", file_types=[".csv"]), gr.Markdown("Status: Awaiting data...")
 
 
 
 
43
  api_key_input = gr.Textbox(label="πŸ”‘ Gemini API Key", type="password", placeholder="Enter key to enable AI...")
44
  suggestion_btn = gr.Button("Get Smart Suggestions", variant="secondary", interactive=False)
45
 
 
46
  rows_stat, cols_stat = gr.Textbox("0", interactive=False, elem_classes="stat-card-value", show_label=False), gr.Textbox("0", interactive=False, elem_classes="stat-card-value", show_label=False)
47
  quality_stat, time_cols_stat = gr.Textbox("0%", interactive=False, elem_classes="stat-card-value", show_label=False), gr.Textbox("0", interactive=False, elem_classes="stat-card-value", show_label=False)
48
  suggestion_buttons = [gr.Button(visible=False) for _ in range(5)]
49
 
 
50
  plot_type_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram')
51
  x_col_dd = gr.Dropdown([], label="X-Axis / Column", interactive=False)
52
  y_col_dd = gr.Dropdown([], label="Y-Axis (for Scatter/Box)", visible=False, interactive=False)
53
+ add_plot_btn, clear_plots_btn = gr.Button("Add to Dashboard", variant="primary", interactive=False), gr.Button("Clear Dashboard")
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ # CORRECTED: Use a dynamic set of Plot components, not Gallery
56
+ dashboard_plots = [gr.Plot(visible=False) for _ in range(MAX_DASHBOARD_PLOTS)]
57
+
58
+ chatbot, chat_input, chat_submit_btn = gr.Chatbot(height=500, label="Conversation", show_copy_button=True), gr.Textbox(label="Your Question", placeholder="e.g., 'What is the relationship between age and salary?'", scale=4), gr.Button("Ask AI", variant="primary")
59
+ copilot_explanation, copilot_code = gr.Markdown(visible=False, elem_classes="explanation-block"), gr.Code(language="python", visible=False, label="Executed Code")
60
+ copilot_plot, copilot_table = gr.Plot(visible=False, label="Generated Visualization"), gr.Dataframe(visible=False, label="Generated Table", wrap=True)
61
+
62
  # --- Layout Arrangement ---
63
  with gr.Row():
64
  with gr.Column(scale=1, elem_classes="sidebar"):
65
  gr.Markdown("## πŸš€ AI Explorer Pro", elem_id="app-title"); cockpit_btn; deep_dive_btn; copilot_btn; gr.Markdown("---")
66
  file_input; status_output; gr.Markdown("---"); api_key_input; suggestion_btn
67
  with gr.Column(scale=4):
68
+ welcome_page, cockpit_page, deep_dive_page, copilot_page = [gr.Column(visible=i==0) for i in range(4)]
69
+ with welcome_page: gr.Markdown("# Welcome to the AI Data Explorer Pro\n> Please **upload a CSV file** and **enter your Gemini API key** to begin.")
 
 
 
70
  with cockpit_page:
71
  gr.Markdown("## πŸ“Š Data Cockpit: At-a-Glance Overview")
72
  with gr.Row():
 
75
  with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Data Quality</div>"); quality_stat
76
  with gr.Column(elem_classes="stat-card"): gr.Markdown("<div class='stat-card-title'>Date/Time Cols</div>"); time_cols_stat
77
  with gr.Accordion(label="✨ AI Smart Suggestions", open=True): [btn for btn in suggestion_buttons]
 
 
78
  with deep_dive_page:
79
+ gr.Markdown("## πŸ” Deep Dive: Manual Dashboard Builder"); gr.Markdown("Construct visualizations to investigate specific relationships.")
80
  with gr.Row(): plot_type_dd; x_col_dd; y_col_dd
81
  with gr.Row(): add_plot_btn; clear_plots_btn
82
+ with gr.Column(): [plot for plot in dashboard_plots] # Place the plot holders
 
 
83
  with copilot_page:
84
  gr.Markdown("## πŸ€– Chief Data Scientist: Your AI Partner"); chatbot
85
  with gr.Accordion("AI's Detailed Response", open=True): copilot_explanation; copilot_code; copilot_plot; copilot_table
86
  with gr.Row(): chat_input; chat_submit_btn
87
 
88
  # --- Event Handlers Registration ---
89
+ pages, nav_buttons = [welcome_page, cockpit_page, deep_dive_page, copilot_page], [cockpit_btn, deep_dive_btn, copilot_btn]
 
 
90
  for i, btn in enumerate(nav_buttons):
91
  btn.click(lambda id=btn.elem_id: self._switch_page(id, pages), outputs=pages).then(
92
  lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
93
 
94
  file_input.upload(self.load_and_process_file, inputs=[file_input], outputs=[
95
+ state_var, status_output, welcome_page, cockpit_page, rows_stat, cols_stat, quality_stat, time_cols_stat,
96
+ x_col_dd, y_col_dd, add_plot_btn]).then(lambda: self._switch_page("cockpit", pages), outputs=pages).then(
 
 
97
  lambda: [gr.update(elem_classes="selected"), gr.update(elem_classes=""), gr.update(elem_classes="")], outputs=nav_buttons)
98
 
99
  api_key_input.change(lambda x: gr.update(interactive=bool(x)), inputs=[api_key_input], outputs=[suggestion_btn])
100
  plot_type_dd.change(self._update_plot_controls, inputs=[plot_type_dd], outputs=[y_col_dd])
101
+ 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_plots])
102
+ clear_plots_btn.click(self.clear_dashboard, inputs=[state_var], outputs=[state_var, *dashboard_plots])
103
  suggestion_btn.click(self.get_ai_suggestions, inputs=[state_var, api_key_input], outputs=suggestion_buttons)
104
 
105
  for btn in suggestion_buttons:
106
+ btn.click(self.handle_suggestion_click, inputs=[btn], outputs=[*pages, chat_input]).then(
107
+ lambda: self._switch_page("co-pilot", pages), outputs=pages).then(
108
+ lambda: (gr.update(elem_classes=""), gr.update(elem_classes=""), gr.update(elem_classes="selected")), outputs=nav_buttons)
109
 
110
  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])
111
  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])
 
112
  return demo
113
 
114
+ def launch(self): self.demo.launch(debug=True)
 
115
 
116
  def _switch_page(self, page_id: str, all_pages: List) -> List[gr.update]:
117
+ visibility = {"welcome":0, "cockpit":1, "deep_dive":2, "co-pilot":3}
118
+ return [gr.update(visible=i == visibility.get(page_id, 0)) for i in range(len(all_pages))]
 
 
 
119
 
120
  def _update_plot_controls(self, plot_type: str) -> gr.update:
121
  return gr.update(visible=plot_type in ['scatter', 'box'])
 
135
  def _extract_dataset_metadata(self, df: pd.DataFrame) -> Dict[str, Any]:
136
  rows, cols = df.shape
137
  quality = round((df.notna().sum().sum() / (rows * cols)) * 100, 1) if rows * cols > 0 else 0
138
+ return {'shape': (rows, cols), 'columns': df.columns.tolist(), 'numeric_cols': df.select_dtypes(include=np.number).columns.tolist(),
139
+ 'categorical_cols': df.select_dtypes(include=['object', 'category']).columns.tolist(), 'datetime_cols': df.select_dtypes(include=['datetime64', 'datetime64[ns]']).columns.tolist(),
140
+ 'dtypes_head': df.head(3).to_string(), 'data_quality': quality}
141
+
142
+ def add_plot_to_dashboard(self, state: Dict, x_col: str, y_col: Optional[str], plot_type: str) -> List[Any]:
143
+ if len(state.get('dashboard_plots', [])) >= MAX_DASHBOARD_PLOTS:
144
+ gr.Warning(f"Dashboard is full. Max {MAX_DASHBOARD_PLOTS} plots allowed."); return [state, *self._get_plot_updates(state)]
145
+ if not x_col: gr.Warning("Please select at least an X-axis column."); return [state, *self._get_plot_updates(state)]
146
+ df, title = state['df'], f"{plot_type.capitalize()}: {y_col} by {x_col}" if y_col and plot_type in ['box', 'scatter'] else f"Distribution of {x_col}"
 
 
147
  try:
148
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
149
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
150
+ elif plot_type == 'scatter': fig = px.scatter(df, x=x_col, y=y_col, title=title, trendline="ols")
151
  elif plot_type == 'bar':
152
  counts = df[x_col].value_counts().nlargest(20)
153
  fig = px.bar(counts, x=counts.index, y=counts.values, title=f"Top 20 Categories for {x_col}", labels={'index': x_col, 'y': 'Count'})
154
  if fig:
155
+ fig.update_layout(template="plotly_dark"); state['dashboard_plots'].append(fig); gr.Info(f"Added '{title}' to dashboard.")
156
+ return [state, *self._get_plot_updates(state)]
157
+ except Exception as e: gr.Error(f"Plotting Error: {e}"); return [state, *self._get_plot_updates(state)]
158
+
159
+ def _get_plot_updates(self, state: Dict) -> List[gr.update]:
160
+ plots = state.get('dashboard_plots', [])
161
+ updates = []
162
+ for i in range(MAX_DASHBOARD_PLOTS):
163
+ if i < len(plots): updates.append(gr.update(value=plots[i], visible=True))
164
+ else: updates.append(gr.update(value=None, visible=False))
165
+ return updates
166
 
167
+ def clear_dashboard(self, state: Dict) -> List[Any]:
168
+ state['dashboard_plots'] = []; gr.Info("Dashboard cleared."); return [state, *self._get_plot_updates(state)]
169
 
170
  def get_ai_suggestions(self, state: Dict, api_key: str) -> List[gr.update]:
171
  if not api_key: gr.Warning("API Key is required."); return [gr.update(visible=False)]*5
172
  if not state: gr.Warning("Please load data first."); return [gr.update(visible=False)]*5
173
+ # CORRECTED: metadata assignment
174
+ metadata = state['metadata']
175
+ prompt = f"From columns {metadata['columns']}, generate 4 impactful analytical questions. Return ONLY a JSON list of strings."
176
  try:
177
+ genai.configure(api_key=api_key); suggestions = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text)
 
178
  return [gr.Button(s, visible=True) for s in suggestions] + [gr.Button(visible=False)] * (5 - len(suggestions))
179
  except Exception as e: gr.Error(f"AI Suggestion Error: {e}"); return [gr.update(visible=False)]*5
180
 
 
188
 
189
  history.append((user_message, "Thinking... πŸ€”")); yield history, *[gr.update(visible=False)]*4
190
 
191
+ metadata, prompt = state['metadata'], f"""You are 'Chief Data Scientist', an expert AI analyst...
192
  **Instructions:**
193
+ 1. **Analyze:** Understand user intent. Infer best plot type.
194
+ 2. **Plan:** Briefly explain your plan.
195
+ 3. **Code:** Write Python code. Use `fig` for plots (`template='plotly_dark'`) and `result_df` for tables.
196
  4. **Insight:** Provide a one-sentence business insight.
197
  5. **Respond ONLY with a single JSON object with keys: "plan", "code", "insight".**
198
  **Metadata:** {metadata['dtypes_head']}