mgbam commited on
Commit
0e9d8f9
Β·
verified Β·
1 Parent(s): c8e629f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +39 -45
app.py CHANGED
@@ -35,38 +35,28 @@ class DataExplorerApp:
35
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="AI Data Explorer Pro") as demo:
36
  state_var = gr.State({})
37
 
38
- # --- Component Definition (Define-Then-Place Pattern) ---
39
- # Sidebar
40
- ### CRITICAL FIX HERE ###
41
- # Buttons are defined individually with their value (text) to fix the "Run" bug.
42
  cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected", elem_id="cockpit")
43
  deep_dive_btn = gr.Button("πŸ” Deep Dive Builder", elem_id="deep_dive")
44
  copilot_btn = gr.Button("πŸ€– Chief Data Scientist", elem_id="co-pilot")
45
-
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, show_label=False, elem_classes="stat-card-value"), gr.Textbox("0", interactive=False, show_label=False, elem_classes="stat-card-value")
53
  quality_stat, time_cols_stat = gr.Textbox("0%", interactive=False, show_label=False, elem_classes="stat-card-value"), gr.Textbox("0", interactive=False, show_label=False, elem_classes="stat-card-value")
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, clear_plots_btn = gr.Button("Add to Dashboard", variant="primary", interactive=False), gr.Button("Clear Dashboard", interactive=False)
61
  dashboard_plots = [gr.Plot(visible=False) for _ in range(MAX_DASHBOARD_PLOTS)]
62
-
63
- # Co-pilot
64
  chatbot = gr.Chatbot(height=500, label="Conversation", show_copy_button=True, avatar_images=(None, "bot.png"))
65
  copilot_explanation, copilot_code = gr.Markdown(visible=False, elem_classes="explanation-block"), gr.Code(language="python", visible=False, label="Executed Code")
66
  copilot_plot, copilot_table = gr.Plot(visible=False, label="Generated Visualization"), gr.Dataframe(visible=False, label="Generated Table", wrap=True)
67
  chat_input, chat_submit_btn = gr.Textbox(label="Your Question", placeholder="e.g., 'What is the relationship between age and salary?'", scale=4), gr.Button("Ask AI", variant="primary", interactive=False)
68
 
69
- # --- Layout Arrangement ---
70
  with gr.Row():
71
  with gr.Column(scale=1, elem_classes="sidebar"):
72
  gr.Markdown("## πŸš€ AI Explorer Pro", elem_id="app-title"); cockpit_btn; deep_dive_btn; copilot_btn; gr.Markdown("---")
@@ -92,8 +82,9 @@ class DataExplorerApp:
92
  with gr.Accordion("AI's Detailed Response", open=True): copilot_explanation; copilot_code; copilot_plot; copilot_table
93
  with gr.Row(): chat_input; chat_submit_btn
94
 
95
- # --- Event Handlers Registration ---
96
  pages, nav_buttons = [welcome_page, cockpit_page, deep_dive_page, copilot_page], [cockpit_btn, deep_dive_btn, copilot_btn]
 
97
  for i, btn in enumerate(nav_buttons):
98
  btn.click(lambda id=btn.elem_id: self._switch_page(id, pages), outputs=pages).then(
99
  lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
@@ -122,9 +113,8 @@ class DataExplorerApp:
122
 
123
  def launch(self): self.demo.launch(debug=True)
124
 
125
- # --- Backend Logic Methods ---
126
  def _switch_page(self, page_id: str, all_pages: List) -> List[gr.update]:
127
- visibility = {"cockpit": 1, "deep_dive": 2, "co-pilot": 3}
128
  return [gr.update(visible=i == visibility.get(page_id, 0)) for i in range(len(all_pages))]
129
 
130
  def _update_plot_controls(self, plot_type: str) -> gr.update:
@@ -136,12 +126,13 @@ class DataExplorerApp:
136
  metadata = self._extract_dataset_metadata(df)
137
  state = {'df': df, 'metadata': metadata, 'dashboard_plots': []}
138
  rows, cols, quality = metadata['shape'][0], metadata['shape'][1], metadata['data_quality']
139
- updates = (state, f"βœ… **{os.path.basename(file_obj.name)}** loaded.", *self._switch_page("cockpit", [0,1,2,3]),
140
- f"{rows:,}", f"{cols}", f"{quality}%", f"{len(metadata['datetime_cols'])}",
141
- gr.update(choices=metadata['columns'], interactive=True), gr.update(choices=metadata['columns'], interactive=True), gr.update(interactive=True))
142
- return updates
143
  except Exception as e:
144
- gr.Error(f"File Load Error: {e}"); return {}, f"❌ Error: {e}", *self._switch_page("welcome", [0,1,2,3]), "0", "0", "0%", "0", gr.update(choices=[], interactive=False), gr.update(choices=[], interactive=False), gr.update(interactive=False)
 
145
 
146
  def _extract_dataset_metadata(self, df: pd.DataFrame) -> Dict[str, Any]:
147
  rows, cols = df.shape
@@ -151,22 +142,21 @@ class DataExplorerApp:
151
  'dtypes_head': df.head(3).to_string(), 'data_quality': quality}
152
 
153
  def add_plot_to_dashboard(self, state: Dict, x_col: str, y_col: Optional[str], plot_type: str) -> List[Any]:
154
- if len(state.get('dashboard_plots', [])) >= MAX_DASHBOARD_PLOTS:
155
- gr.Warning(f"Dashboard is full. Max {MAX_DASHBOARD_PLOTS} plots."); return [state, gr.update(), *self._get_plot_updates(state)]
156
- if not x_col: gr.Warning("Please select an X-axis column."); return [state, gr.update(), *self._get_plot_updates(state)]
157
- df = state['df']
158
- 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}"
159
  try:
 
160
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
161
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
162
  elif plot_type == 'scatter': fig = px.scatter(df, x=x_col, y=y_col, title=title, trendline="ols")
163
- elif plot_type == 'bar':
164
- counts = df[x_col].value_counts().nlargest(20)
165
- fig = px.bar(counts, x=counts.index, y=counts.values, title=f"Top 20 Categories for {x_col}", labels={'index': x_col, 'y': 'Count'})
166
  if fig:
167
- fig.update_layout(template="plotly_dark"); state['dashboard_plots'].append(fig); gr.Info(f"Added '{title}' to dashboard.")
168
  return [state, gr.update(interactive=True), *self._get_plot_updates(state)]
169
- except Exception as e: gr.Error(f"Plotting Error: {e}"); return [state, gr.update(), *self._get_plot_updates(state)]
170
 
171
  def _get_plot_updates(self, state: Dict) -> List[gr.update]:
172
  plots = state.get('dashboard_plots', [])
@@ -178,7 +168,10 @@ class DataExplorerApp:
178
  def get_ai_suggestions(self, state: Dict, api_key: str) -> List[gr.update]:
179
  if not api_key: gr.Warning("API Key is required."); return [gr.update(visible=False)]*5
180
  if not state: gr.Warning("Please load data first."); return [gr.update(visible=False)]*5
181
- metadata = state['metadata']; prompt = f"From columns {metadata['columns']}, generate 4 impactful analytical questions. Return ONLY a JSON list of strings."
 
 
 
182
  try:
183
  genai.configure(api_key=api_key); suggestions = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text)
184
  return [gr.Button(s, visible=True) for s in suggestions] + [gr.Button(visible=False)] * (5 - len(suggestions))
@@ -194,23 +187,29 @@ class DataExplorerApp:
194
 
195
  history.append((user_message, "Thinking... πŸ€”")); yield history, *[gr.update(visible=False)]*4
196
 
197
- metadata, prompt = state['metadata'], f"""You are 'Chief Data Scientist', an expert AI analyst...
 
 
 
 
198
  Respond ONLY with a single JSON object with keys: "plan", "code", "insight".
199
- Metadata: {metadata['dtypes_head']}
200
  User Question: "{user_message}"
201
  """
202
  try:
203
  genai.configure(api_key=api_key); response_json = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text.strip().replace("```json", "").replace("```", ""))
204
  plan, code, insight = response_json.get("plan"), response_json.get("code"), response_json.get("insight")
205
  stdout, fig, df_result, error = self._safe_exec(code, {'df': state['df'], 'px': px, 'pd': pd})
 
206
  history[-1] = (user_message, f"**Plan:** {plan}")
207
  explanation = f"**Insight:** {insight}"
208
  if stdout: explanation += f"\n\n**Console Output:**\n```\n{stdout}\n```"
209
  if error: gr.Error(f"AI Code Execution Failed: {error}")
 
210
  yield (history, gr.update(visible=bool(explanation), value=explanation), gr.update(visible=bool(code), value=code),
211
  gr.update(visible=bool(fig), value=fig), gr.update(visible=bool(df_result is not None), value=df_result))
212
  except Exception as e:
213
- history[-1] = (user_message, f"I encountered an error. Please rephrase your question. (Error: {e})")
214
  yield history, *[gr.update(visible=False)]*4
215
 
216
  def _safe_exec(self, code_string: str, local_vars: Dict) -> Tuple[Any, ...]:
@@ -221,17 +220,12 @@ class DataExplorerApp:
221
  except Exception as e: return None, None, None, str(e)
222
 
223
  if __name__ == "__main__":
224
- # Create dummy files for avatar images if they don't exist
225
  if not os.path.exists("bot.png"):
226
- from PIL import Image
227
- img = Image.new('RGB', (100, 100), color = 'darkblue')
228
- img.save('bot.png')
229
- if not os.path.exists("user.png"):
230
- from PIL import Image
231
- img = Image.new('RGB', (100, 100), color = 'gray')
232
- img.save('user.png')
233
- if not os.path.exists("workflow.png"):
234
- gr.Warning("workflow.png not found. The welcome screen image will be missing.")
235
-
236
  app = DataExplorerApp()
237
  app.launch()
 
35
  with gr.Blocks(theme=gr.themes.Glass(primary_hue="indigo", secondary_hue="blue"), css=CSS, title="AI Data Explorer Pro") as demo:
36
  state_var = gr.State({})
37
 
38
+ # Component Definition
 
 
 
39
  cockpit_btn = gr.Button("πŸ“Š Data Cockpit", elem_classes="selected", elem_id="cockpit")
40
  deep_dive_btn = gr.Button("πŸ” Deep Dive Builder", elem_id="deep_dive")
41
  copilot_btn = gr.Button("πŸ€– Chief Data Scientist", elem_id="co-pilot")
 
42
  file_input = gr.File(label="πŸ“ Upload CSV File", file_types=[".csv"])
43
  status_output = gr.Markdown("Status: Awaiting data...")
44
  api_key_input = gr.Textbox(label="πŸ”‘ Gemini API Key", type="password", placeholder="Enter key to enable AI...")
45
  suggestion_btn = gr.Button("Get Smart Suggestions", variant="secondary", interactive=False)
 
 
46
  rows_stat, cols_stat = gr.Textbox("0", interactive=False, show_label=False, elem_classes="stat-card-value"), gr.Textbox("0", interactive=False, show_label=False, elem_classes="stat-card-value")
47
  quality_stat, time_cols_stat = gr.Textbox("0%", interactive=False, show_label=False, elem_classes="stat-card-value"), gr.Textbox("0", interactive=False, show_label=False, elem_classes="stat-card-value")
48
  suggestion_buttons = [gr.Button(visible=False) for _ in range(5)]
 
 
49
  plot_type_dd = gr.Dropdown(['histogram', 'bar', 'scatter', 'box'], label="Plot Type", value='histogram')
50
  x_col_dd = gr.Dropdown([], label="X-Axis / Column", interactive=False)
51
  y_col_dd = gr.Dropdown([], label="Y-Axis (for Scatter/Box)", visible=False, interactive=False)
52
  add_plot_btn, clear_plots_btn = gr.Button("Add to Dashboard", variant="primary", interactive=False), gr.Button("Clear Dashboard", interactive=False)
53
  dashboard_plots = [gr.Plot(visible=False) for _ in range(MAX_DASHBOARD_PLOTS)]
 
 
54
  chatbot = gr.Chatbot(height=500, label="Conversation", show_copy_button=True, avatar_images=(None, "bot.png"))
55
  copilot_explanation, copilot_code = gr.Markdown(visible=False, elem_classes="explanation-block"), gr.Code(language="python", visible=False, label="Executed Code")
56
  copilot_plot, copilot_table = gr.Plot(visible=False, label="Generated Visualization"), gr.Dataframe(visible=False, label="Generated Table", wrap=True)
57
  chat_input, chat_submit_btn = gr.Textbox(label="Your Question", placeholder="e.g., 'What is the relationship between age and salary?'", scale=4), gr.Button("Ask AI", variant="primary", interactive=False)
58
 
59
+ # Layout Arrangement
60
  with gr.Row():
61
  with gr.Column(scale=1, elem_classes="sidebar"):
62
  gr.Markdown("## πŸš€ AI Explorer Pro", elem_id="app-title"); cockpit_btn; deep_dive_btn; copilot_btn; gr.Markdown("---")
 
82
  with gr.Accordion("AI's Detailed Response", open=True): copilot_explanation; copilot_code; copilot_plot; copilot_table
83
  with gr.Row(): chat_input; chat_submit_btn
84
 
85
+ # Event Handlers Registration
86
  pages, nav_buttons = [welcome_page, cockpit_page, deep_dive_page, copilot_page], [cockpit_btn, deep_dive_btn, copilot_btn]
87
+
88
  for i, btn in enumerate(nav_buttons):
89
  btn.click(lambda id=btn.elem_id: self._switch_page(id, pages), outputs=pages).then(
90
  lambda i=i: [gr.update(elem_classes="selected" if j==i else "") for j in range(len(nav_buttons))], outputs=nav_buttons)
 
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:
 
126
  metadata = self._extract_dataset_metadata(df)
127
  state = {'df': df, 'metadata': metadata, 'dashboard_plots': []}
128
  rows, cols, quality = metadata['shape'][0], metadata['shape'][1], metadata['data_quality']
129
+ page_updates = self._switch_page("cockpit", [0,1,2,3])
130
+ return (state, f"βœ… **{os.path.basename(file_obj.name)}** loaded.", *page_updates,
131
+ f"{rows:,}", f"{cols}", f"{quality}%", f"{len(metadata['datetime_cols'])}",
132
+ gr.update(choices=metadata['columns'], interactive=True), gr.update(choices=metadata['columns'], interactive=True), gr.update(interactive=True))
133
  except Exception as e:
134
+ gr.Error(f"File Load Error: {e}"); page_updates = self._switch_page("welcome", [0,1,2,3]);
135
+ return {}, f"❌ Error: {e}", *page_updates, "0", "0", "0%", "0", gr.update(choices=[], interactive=False), gr.update(choices=[], interactive=False), gr.update(interactive=False)
136
 
137
  def _extract_dataset_metadata(self, df: pd.DataFrame) -> Dict[str, Any]:
138
  rows, cols = df.shape
 
142
  'dtypes_head': df.head(3).to_string(), 'data_quality': quality}
143
 
144
  def add_plot_to_dashboard(self, state: Dict, x_col: str, y_col: Optional[str], plot_type: str) -> List[Any]:
145
+ dashboard_plots = state.get('dashboard_plots', [])
146
+ if len(dashboard_plots) >= MAX_DASHBOARD_PLOTS:
147
+ gr.Warning(f"Dashboard is full. Max {MAX_DASHBOARD_PLOTS} plots."); return [state, gr.update(interactive=True), *self._get_plot_updates(state)]
148
+ if not x_col: gr.Warning("Please select an X-axis column."); return [state, gr.update(interactive=True), *self._get_plot_updates(state)]
149
+ df, title = state.get('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}"
150
  try:
151
+ fig=None;
152
  if plot_type == 'histogram': fig = px.histogram(df, x=x_col, title=title)
153
  elif plot_type == 'box': fig = px.box(df, x=x_col, y=y_col, title=title)
154
  elif plot_type == 'scatter': fig = px.scatter(df, x=x_col, y=y_col, title=title, trendline="ols")
155
+ elif plot_type == 'bar': fig = px.bar(df[x_col].value_counts().nlargest(20), title=f"Top 20 for {x_col}")
 
 
156
  if fig:
157
+ fig.update_layout(template="plotly_dark"); dashboard_plots.append(fig); gr.Info(f"Added '{title}' to dashboard.")
158
  return [state, gr.update(interactive=True), *self._get_plot_updates(state)]
159
+ except Exception as e: gr.Error(f"Plotting Error: {e}"); return [state, gr.update(interactive=True), *self._get_plot_updates(state)]
160
 
161
  def _get_plot_updates(self, state: Dict) -> List[gr.update]:
162
  plots = state.get('dashboard_plots', [])
 
168
  def get_ai_suggestions(self, state: Dict, api_key: str) -> List[gr.update]:
169
  if not api_key: gr.Warning("API Key is required."); return [gr.update(visible=False)]*5
170
  if not state: gr.Warning("Please load data first."); return [gr.update(visible=False)]*5
171
+ # CORRECTED: Defensive and correct assignment
172
+ metadata = state.get('metadata', {})
173
+ columns = metadata.get('columns', [])
174
+ prompt = f"From columns {columns}, generate 4 impactful analytical questions. Return ONLY a JSON list of strings."
175
  try:
176
  genai.configure(api_key=api_key); suggestions = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text)
177
  return [gr.Button(s, visible=True) for s in suggestions] + [gr.Button(visible=False)] * (5 - len(suggestions))
 
187
 
188
  history.append((user_message, "Thinking... πŸ€”")); yield history, *[gr.update(visible=False)]*4
189
 
190
+ # CRITICAL FIX: Correctly and safely get metadata before using it.
191
+ metadata = state.get('metadata', {})
192
+ dtypes_head = metadata.get('dtypes_head', 'No metadata available.')
193
+
194
+ prompt = f"""You are 'Chief Data Scientist', an expert AI analyst...
195
  Respond ONLY with a single JSON object with keys: "plan", "code", "insight".
196
+ Metadata: {dtypes_head}
197
  User Question: "{user_message}"
198
  """
199
  try:
200
  genai.configure(api_key=api_key); response_json = json.loads(genai.GenerativeModel('gemini-1.5-flash').generate_content(prompt).text.strip().replace("```json", "").replace("```", ""))
201
  plan, code, insight = response_json.get("plan"), response_json.get("code"), response_json.get("insight")
202
  stdout, fig, df_result, error = self._safe_exec(code, {'df': state['df'], 'px': px, 'pd': pd})
203
+
204
  history[-1] = (user_message, f"**Plan:** {plan}")
205
  explanation = f"**Insight:** {insight}"
206
  if stdout: explanation += f"\n\n**Console Output:**\n```\n{stdout}\n```"
207
  if error: gr.Error(f"AI Code Execution Failed: {error}")
208
+
209
  yield (history, gr.update(visible=bool(explanation), value=explanation), gr.update(visible=bool(code), value=code),
210
  gr.update(visible=bool(fig), value=fig), gr.update(visible=bool(df_result is not None), value=df_result))
211
  except Exception as e:
212
+ history[-1] = (user_message, f"I encountered an error processing the AI response. Please rephrase your question. (Error: {e})")
213
  yield history, *[gr.update(visible=False)]*4
214
 
215
  def _safe_exec(self, code_string: str, local_vars: Dict) -> Tuple[Any, ...]:
 
220
  except Exception as e: return None, None, None, str(e)
221
 
222
  if __name__ == "__main__":
 
223
  if not os.path.exists("bot.png"):
224
+ try:
225
+ from PIL import Image
226
+ Image.new('RGB', (1, 1)).save('bot.png')
227
+ except ImportError:
228
+ print("Pillow not installed, cannot create dummy bot.png. Please create it manually.")
229
+
 
 
 
 
230
  app = DataExplorerApp()
231
  app.launch()