dimoZ commited on
Commit
b6c5a88
·
verified ·
1 Parent(s): 9833959

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +284 -602
app.py CHANGED
@@ -10,11 +10,28 @@ import streamlit.components.v1 as components
10
  from datetime import datetime, date
11
  import io
12
  import base64
 
13
 
14
  # ------------------------------
15
- # Custom JSON Encoder for Timestamps
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  # ------------------------------
17
  class CustomJSONEncoder(json.JSONEncoder):
 
18
  def default(self, obj):
19
  if isinstance(obj, (datetime, date, pd.Timestamp)):
20
  return obj.isoformat()
@@ -28,634 +45,299 @@ class CustomJSONEncoder(json.JSONEncoder):
28
  return None
29
  return super().default(obj)
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  # ------------------------------
32
- # Page Configuration
33
  # ------------------------------
34
- st.set_page_config(
35
- page_title="AI Excel BI Dashboard",
36
- page_icon="📊",
37
- layout="wide",
38
- initial_sidebar_state="expanded"
39
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
- # Initialize session state
42
- if 'api_configured' not in st.session_state:
43
- st.session_state['api_configured'] = False
44
- if 'dark_mode' not in st.session_state:
45
- st.session_state['dark_mode'] = True # Default to dark mode
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  # ------------------------------
48
- # Sidebar: API Key Setup
49
  # ------------------------------
50
- with st.sidebar:
51
- st.header("⚙️ Configuration")
52
- st.markdown("---")
53
-
54
- api_key = st.text_input(
55
- "🔑 Gemini API Key",
56
- type="password",
57
- help="Enter your Google Gemini API key"
58
- )
59
-
60
- if api_key:
61
- try:
62
- client = genai.Client(api_key=api_key)
63
- st.success("✅ API Key Configured")
64
- st.session_state['api_configured'] = True
65
- except Exception as e:
66
- st.error(f"❌ Invalid API Key: {e}")
67
- client = None
68
- st.session_state['api_configured'] = False
69
- else:
70
- client = None
71
-
72
- st.markdown("---")
73
-
74
- st.subheader("ℹ️ About")
75
- st.info("""
76
- This AI-powered dashboard:
77
- - Analyzes Excel/CSV data
78
- - Generates intelligent visualizations
79
- - Creates interactive HTML dashboards
80
- - Provides business insights
81
- - Detects company/brand data
82
- """)
83
-
84
- st.markdown("---")
85
- st.caption("Powered by Google Gemini AI")
86
 
87
- # Apply dark mode styling (always on by default)
88
  st.markdown("""
89
  <style>
90
- .stApp {
91
- background-color: #0e1117;
92
- color: #fafafa;
93
- }
94
  </style>
95
  """, unsafe_allow_html=True)
96
 
97
  # ------------------------------
98
- # Main Area: Dashboard
99
  # ------------------------------
100
- st.title("📊 AI-Powered Business Intelligence Dashboard")
101
- st.markdown("Upload your data file and let AI create professional insights!")
102
-
103
- # Check if API key is configured
104
- if not api_key or not client:
105
- st.warning("⚠️ Please enter your Gemini API Key in the sidebar to continue.")
106
- st.stop()
 
 
 
 
107
 
108
- # ------------------------------
109
- # File Upload Section
110
- # ------------------------------
111
- st.markdown("---")
112
- uploaded_file = st.file_uploader(
113
- "📂 Upload Your Data File",
114
- type=["csv", "xlsx"],
115
- help="Supports CSV and Excel files"
116
- )
117
 
118
- if uploaded_file:
119
  try:
120
- # Load dataset
121
- with st.spinner("Loading data..."):
122
- if uploaded_file.name.endswith(".csv"):
 
 
 
 
 
 
 
 
 
 
 
123
  df = pd.read_csv(uploaded_file)
124
  else:
125
  df = pd.read_excel(uploaded_file)
126
-
127
- st.success(f"✅ File '{uploaded_file.name}' uploaded successfully!")
128
-
129
- # ------------------------------
130
- # Enhanced Data Overview Section
131
- # ------------------------------
132
- st.markdown("---")
133
- st.subheader("📋 Comprehensive Data Overview")
134
-
135
- # Basic Metrics
136
- col1, col2, col3, col4, col5 = st.columns(5)
137
- with col1:
138
- st.metric("Total Rows", f"{df.shape[0]:,}")
139
- with col2:
140
- st.metric("Total Columns", df.shape[1])
141
- with col3:
142
- st.metric("Numeric Columns", len(df.select_dtypes(include=['number']).columns))
143
- with col4:
144
- st.metric("Categorical Columns", len(df.select_dtypes(include=['object']).columns))
145
- with col5:
146
- missing_pct = (df.isnull().sum().sum() / (df.shape[0] * df.shape[1]) * 100)
147
- st.metric("Missing Data", f"{missing_pct:.1f}%")
148
-
149
- # Detailed Data Analysis
150
- with st.expander("🔍 View Detailed Data Analysis", expanded=True):
151
- tab1, tab2, tab3 = st.tabs(["📊 Data Preview", "📈 Statistics", "⚠️ Data Quality"])
152
 
153
- with tab1:
154
- st.dataframe(df.head(15), use_container_width=True)
155
 
156
- with tab2:
157
- # Statistical Summary
158
- st.markdown("**Statistical Summary**")
159
- numeric_cols = df.select_dtypes(include=['number']).columns
160
- if len(numeric_cols) > 0:
161
- stats_df = df[numeric_cols].describe()
162
- st.dataframe(stats_df, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  else:
164
- st.info("No numeric columns found for statistical analysis")
165
-
166
- # Categorical Summary
167
- cat_cols = df.select_dtypes(include=['object']).columns
168
- if len(cat_cols) > 0:
169
- st.markdown("**Categorical Summary**")
170
- cat_summary = pd.DataFrame({
171
- 'Column': cat_cols,
172
- 'Unique Values': [df[col].nunique() for col in cat_cols],
173
- 'Most Frequent': [df[col].mode()[0] if len(df[col].mode()) > 0 else 'N/A' for col in cat_cols],
174
- 'Frequency': [df[col].value_counts().iloc[0] if len(df[col]) > 0 else 0 for col in cat_cols]
175
- })
176
- st.dataframe(cat_summary, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
- with tab3:
179
- # Data Quality Metrics
180
- quality_data = []
181
- for col in df.columns:
182
- missing = df[col].isnull().sum()
183
- missing_pct = (missing / len(df)) * 100
184
-
185
- # Check for blank spaces in string columns
186
- blank_spaces = 0
187
- if df[col].dtype == 'object':
188
- blank_spaces = df[col].astype(str).str.strip().eq('').sum()
189
-
190
- # Standard deviation for numeric columns
191
- std_dev = df[col].std() if df[col].dtype in ['int64', 'float64'] else None
192
-
193
- quality_data.append({
194
- 'Column': col,
195
- 'Data Type': str(df[col].dtype),
196
- 'Missing Values': missing,
197
- 'Missing %': f"{missing_pct:.2f}%",
198
- 'Blank Spaces': blank_spaces,
199
- 'Std Deviation': f"{std_dev:.2f}" if std_dev is not None else 'N/A',
200
- 'Unique Values': df[col].nunique()
201
- })
202
 
203
- quality_df = pd.DataFrame(quality_data)
204
- st.dataframe(quality_df, use_container_width=True)
 
 
 
 
 
205
 
206
- # Highlight issues
207
- total_missing = df.isnull().sum().sum()
208
- if total_missing > 0:
209
- st.warning(f"⚠️ Found {total_missing:,} missing values across the dataset")
210
- else:
211
- st.success("✅ No missing values detected")
212
-
213
- # ------------------------------
214
- # AI Analysis Section
215
- # ------------------------------
216
- st.markdown("---")
217
- st.subheader("🤖 AI-Generated Dashboard")
218
-
219
- col_btn1, col_btn2 = st.columns(2)
220
-
221
- with col_btn1:
222
- generate_charts = st.button("📈 Generate Charts & Insights", type="primary", use_container_width=True)
223
-
224
- with col_btn2:
225
- generate_interactive = st.button("🎨 Generate Interactive HTML Dashboard", type="secondary", use_container_width=True)
226
-
227
- # Add Presentation Maker Button
228
- st.markdown("")
229
- generate_presentation = st.button("🎤 Generate AI Presentation (PPT)", use_container_width=True)
230
-
231
- # ------------------------------
232
- # Generate Charts and Insights (Collage View)
233
- # ------------------------------
234
- if generate_charts:
235
- with st.spinner("AI is analyzing your data..."):
236
- try:
237
- # Prepare schema with proper serialization
238
- sample_data = df.head(3).copy()
239
- for col in sample_data.columns:
240
- if sample_data[col].dtype == 'datetime64[ns]' or isinstance(sample_data[col].iloc[0], pd.Timestamp):
241
- sample_data[col] = sample_data[col].astype(str)
242
-
243
- schema = {
244
- "columns": {col: str(df[col].dtype) for col in df.columns},
245
- "sample": sample_data.to_dict(),
246
- "shape": {"rows": int(df.shape[0]), "columns": int(df.shape[1])},
247
- "numeric_columns": [col for col in df.select_dtypes(include=['number']).columns.tolist()],
248
- "categorical_columns": [col for col in df.select_dtypes(include=['object']).columns.tolist()]
249
- }
250
-
251
- prompt = f"""
252
- You are a business intelligence and data visualization expert.
253
-
254
- Dataset Information:
255
- {json.dumps(schema, indent=2, cls=CustomJSONEncoder)}
256
-
257
- Analyze this dataset and determine:
258
- 1. Is this company/business data? (sales, revenue, employees, products, etc.)
259
- 2. What industry or domain does it belong to? (retail, finance, healthcare, entertainment, etc.)
260
- 3. What are the key metrics and KPIs?
261
-
262
- Then respond with ONLY a valid JSON object (no markdown, no explanations) with this exact structure:
263
- {{
264
- "domain": "industry name (e.g., retail, finance, entertainment, generic)",
265
- "is_company_data": true/false,
266
- "charts": [
267
- {{"type": "bar", "x": "column_name", "y": "column_name", "title": "Descriptive Chart Title"}},
268
- {{"type": "line", "x": "column_name", "y": "column_name", "title": "Descriptive Chart Title"}},
269
- {{"type": "scatter", "x": "column_name", "y": "column_name", "title": "Descriptive Chart Title"}},
270
- {{"type": "pie", "column": "column_name", "title": "Descriptive Chart Title"}}
271
- ],
272
- "insights": [
273
- "First business insight about the data",
274
- "Second business insight about the data",
275
- "Third business insight about the data"
276
- ]
277
- }}
278
-
279
- Chart types available: bar, line, scatter, histogram, pie
280
- Generate 4-6 charts that would be most insightful for this data domain.
281
- """
282
-
283
- # Call Gemini API
284
- response = client.models.generate_content(
285
- model="gemini-2.0-flash-exp",
286
- contents=[prompt]
287
- )
288
-
289
- # Parse response
290
- response_text = response.text.strip()
291
- if response_text.startswith("```"):
292
- response_text = response_text.split("```")[1]
293
- if response_text.startswith("json"):
294
- response_text = response_text[4:]
295
-
296
- chart_plan = json.loads(response_text)
297
-
298
- # Store in session state
299
- st.session_state['chart_plan'] = chart_plan
300
- st.session_state['df'] = df
301
-
302
- except Exception as e:
303
- st.error(f"❌ Error generating dashboard: {e}")
304
- st.exception(e)
305
-
306
- # ------------------------------
307
- # Display Charts in Collage View
308
- # ------------------------------
309
- if 'chart_plan' in st.session_state:
310
- chart_plan = st.session_state['chart_plan']
311
- df = st.session_state['df']
312
-
313
- st.markdown("---")
314
- st.markdown("### 📈 Visualizations Collage")
315
- st.markdown(f"**Dashboard Title:** {uploaded_file.name.split('.')[0].replace('_', ' ').title()}")
316
- st.markdown("**Detailed Charts & Graphs** - Comprehensive visual analysis with proper labels and insights")
317
-
318
- charts = chart_plan.get("charts", [])
319
-
320
- # Create matplotlib figure with all charts
321
- num_charts = len(charts)
322
- cols_per_row = 3
323
- rows = (num_charts + cols_per_row - 1) // cols_per_row
324
-
325
- fig = plt.figure(figsize=(20, 5 * rows))
326
-
327
- for idx, chart in enumerate(charts, 1):
328
- try:
329
- chart_type = chart.get("type")
330
- title = chart.get("title", f"Chart {idx}")
331
-
332
- ax = fig.add_subplot(rows, cols_per_row, idx)
333
-
334
- if chart_type == "bar" and "x" in chart and "y" in chart:
335
- grouped_data = df.groupby(chart["x"])[chart["y"]].sum()
336
- # Limit to top 15 categories for readability
337
- if len(grouped_data) > 15:
338
- grouped_data = grouped_data.nlargest(15)
339
- sns.barplot(x=grouped_data.values, y=grouped_data.index, ax=ax, palette='Blues_d')
340
- ax.set_xlabel(chart["y"], fontsize=10)
341
- ax.set_ylabel(chart["x"], fontsize=10)
342
-
343
- elif chart_type == "line" and "x" in chart and "y" in chart:
344
- # Sample data if too many points
345
- plot_df = df.copy()
346
- if len(plot_df) > 100:
347
- plot_df = plot_df.sample(100).sort_values(by=chart["x"])
348
- sns.lineplot(data=plot_df, x=chart["x"], y=chart["y"], ax=ax, marker='o', color='green', linewidth=2)
349
- ax.set_xlabel(chart["x"], fontsize=10)
350
- ax.set_ylabel(chart["y"], fontsize=10)
351
- plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right', fontsize=8)
352
-
353
- elif chart_type == "scatter" and "x" in chart and "y" in chart:
354
- sns.scatterplot(data=df, x=chart["x"], y=chart["y"], ax=ax, color='coral', s=50, alpha=0.6)
355
- ax.set_xlabel(chart["x"], fontsize=10)
356
- ax.set_ylabel(chart["y"], fontsize=10)
357
 
358
- elif chart_type == "histogram" and "x" in chart:
359
- sns.histplot(df[chart["x"]].dropna(), bins=20, kde=True, ax=ax, color='purple', alpha=0.7)
360
- ax.set_xlabel(chart["x"], fontsize=10)
361
- ax.set_ylabel("Frequency", fontsize=10)
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
- elif chart_type == "pie" and "column" in chart:
364
- data = df[chart["column"]].value_counts().head(5)
365
- colors = sns.color_palette("pastel")
366
- ax.pie(data.values, labels=data.index, autopct='%1.1f%%', startangle=90, colors=colors)
367
-
368
- ax.set_title(title, fontsize=11, fontweight='bold', pad=10)
369
-
370
- except Exception as chart_error:
371
- ax.text(0.5, 0.5, f'Error: {str(chart_error)}', ha='center', va='center')
372
- ax.set_title(title, fontsize=11)
373
-
374
- plt.tight_layout()
375
- st.pyplot(fig)
376
- plt.close()
377
-
378
- # Display Insights
379
- st.markdown("---")
380
- st.markdown("### 💡 Business Insights")
381
-
382
- insights = chart_plan.get("insights", [])
383
- for idx, insight in enumerate(insights, 1):
384
- st.markdown(f"**{idx}.** {insight}")
385
-
386
- # ------------------------------
387
- # Generate Interactive HTML Dashboard (Professional Power BI Style)
388
- # ------------------------------
389
- if generate_interactive:
390
- with st.spinner("Generating professional interactive dashboard..."):
391
- try:
392
- # Detect domain and company info
393
- domain = st.session_state.get('chart_plan', {}).get('domain', 'general')
394
- is_company = st.session_state.get('chart_plan', {}).get('is_company_data', False)
395
-
396
- # Get file name for dashboard title
397
- dashboard_title = uploaded_file.name.split('.')[0].replace('_', ' ').title()
398
-
399
- # Prepare data with proper serialization
400
- sample_data = df.head(20).copy()
401
- for col in sample_data.columns:
402
- if sample_data[col].dtype == 'datetime64[ns]' or isinstance(sample_data[col].iloc[0], pd.Timestamp):
403
- sample_data[col] = sample_data[col].astype(str)
404
-
405
- stats_dict = {}
406
- for col in df.select_dtypes(include=['number']).columns:
407
- stats_dict[col] = {
408
- 'mean': float(df[col].mean()),
409
- 'median': float(df[col].median()),
410
- 'std': float(df[col].std()),
411
- 'min': float(df[col].min()),
412
- 'max': float(df[col].max())
413
- }
414
-
415
- html_prompt = f"""
416
- Create a COMPLETE, self-contained, professional Power BI-style HTML dashboard.
417
-
418
- Dataset Context:
419
- - Dashboard Title: {dashboard_title}
420
- - Domain: {domain}
421
- - Is Company Data: {is_company}
422
- - Columns: {', '.join(df.columns.tolist())}
423
- - Rows: {df.shape[0]}
424
- - Sample Data: {json.dumps(sample_data.to_dict('records')[:10], cls=CustomJSONEncoder)}
425
- - Statistics: {json.dumps(stats_dict, cls=CustomJSONEncoder)}
426
-
427
- CRITICAL Requirements for Handling Large Data:
428
- 1. For bar charts with many categories (>15), show only TOP 15 values and add "...and X more" text
429
- 2. For time series/date data, aggregate by week or month, never show individual dates
430
- 3. Use responsive chart heights (max 300px per chart)
431
- 4. Implement proper overflow handling with max-height and scrolling only if necessary
432
- 5. For dates on x-axis: rotate labels 45deg, use abbreviated format (MMM-YY), show every Nth label
433
-
434
- Dashboard Design:
435
- 1. Use Chart.js from CDN: https://cdn.jsdelivr.net/npm/chart.js
436
- 2. Dynamic color scheme based on domain/data characteristics:
437
- - Finance: Blue (#1e3a8a) to Navy gradient with gold accents
438
- - Retail/Sales: Orange (#ea580c) to Green (#16a34a) gradient
439
- - Healthcare: Teal (#0d9488) to Blue (#0284c7) gradient
440
- - Entertainment/Movies: Purple (#7c3aed) to Magenta (#db2777) gradient
441
- - Technology: Cyan (#06b6d4) to Blue (#3b82f6) gradient
442
- - Generic: Professional Blue (#2563eb) to Gray (#64748b) gradient
443
- 3. Layout: Responsive grid with 2-3 columns, cards with shadows
444
- 4. Include:
445
- - Top banner with "{dashboard_title}" as main title
446
- - 4-6 KPI cards with key metrics (large numbers, trend indicators)
447
- - 6-8 charts in grid layout (bar, line, pie, doughnut, area charts)
448
- - Each chart in a card with title, proper spacing
449
- - All charts must be USEFUL for Business Intelligence and KPI tracking
450
- - Focus on metrics that show: trends, comparisons, distributions, performance
451
- 5. If company data, add company logo placeholder at top
452
- 6. Footer: "{datetime.now().strftime('%B %d, %Y')} | {dashboard_title} Analytics Dashboard"
453
- 7. Make charts interactive: hover tooltips, legend toggle
454
- 8. Use actual data values, aggregate large datasets intelligently
455
- 9. Add smooth animations (fade-in, scale effects)
456
- 10. Ensure dates are always visible, accurate & readable
457
-
458
- Chart Configuration Best Practices:
459
- - Bar charts: Horizontal for many categories.
460
- - Line charts: Aggregate time data, show trends not noise
461
- - Pie/Doughnut: Limit to top 10 categories, group "Others"
462
- - Use appropriate scales and formatting (K, M, B for large numbers)
463
-
464
- Return ONLY complete HTML code starting with <!DOCTYPE html>
465
- NO markdown, NO explanations, just pure HTML that looks like a professional BI tool.
466
- """
467
-
468
- response = client.models.generate_content(
469
- model="gemini-2.0-flash-exp",
470
- contents=[html_prompt]
471
- )
472
-
473
- html_code = response.text.strip()
474
-
475
- if html_code.startswith("```"):
476
- html_code = html_code.split("```")[1]
477
- if html_code.startswith("html"):
478
- html_code = html_code[4:]
479
- html_code = html_code.strip()
480
-
481
- st.session_state['html_dashboard'] = html_code
482
- st.success("✅ Professional dashboard generated!")
483
-
484
- except Exception as e:
485
- st.error(f"❌ Error generating HTML dashboard: {e}")
486
- st.exception(e)
487
-
488
- # ------------------------------
489
- # Display Interactive HTML Dashboard
490
- # ------------------------------
491
- if 'html_dashboard' in st.session_state:
492
- st.markdown("---")
493
- st.markdown("### 🎨 Professional Interactive Dashboard")
494
-
495
- html_code = st.session_state['html_dashboard']
496
-
497
- # Display the interactive HTML
498
- components.html(html_code, height=1000, scrolling=True)
499
-
500
- col1, col2 = st.columns(2)
501
- with col1:
502
- st.download_button(
503
- label="📥 Download HTML Dashboard",
504
- data=html_code,
505
- file_name=f"dashboard_{uploaded_file.name.split('.')[0]}.html",
506
- mime="text/html",
507
- use_container_width=True
508
- )
509
-
510
- with col2:
511
- with st.expander("💻 View HTML Source Code"):
512
- st.code(html_code, language="html")
513
-
514
- # ------------------------------
515
- # Generate AI Presentation (PPT-style HTML)
516
- # ------------------------------
517
- if generate_presentation:
518
- with st.spinner("Creating professional presentation..."):
519
- try:
520
- # Get insights and domain info
521
- chart_plan = st.session_state.get('chart_plan', {})
522
- domain = chart_plan.get('domain', 'general')
523
- insights = chart_plan.get('insights', [])
524
-
525
- dashboard_title = uploaded_file.name.split('.')[0].replace('_', ' ').title()
526
-
527
- # Prepare data summary
528
- key_metrics = []
529
- for col in df.select_dtypes(include=['number']).columns[:4]:
530
- key_metrics.append({
531
- 'metric': col,
532
- 'value': float(df[col].sum()),
533
- 'avg': float(df[col].mean()),
534
- 'trend': 'up' if df[col].mean() > df[col].median() else 'down'
535
- })
536
-
537
- presentation_prompt = f"""
538
- Create a professional HTML presentation (PowerPoint-style) with slide navigation.
539
-
540
- Presentation Context:
541
- - Title: {dashboard_title} - Business Intelligence Analysis
542
- - Domain: {domain}
543
- - Dataset: {df.shape[0]} rows, {df.shape[1]} columns
544
- - Key Insights: {json.dumps(insights, cls=CustomJSONEncoder)}
545
- - Key Metrics: {json.dumps(key_metrics, cls=CustomJSONEncoder)}
546
-
547
- Create EXACTLY 5 slides with this structure:
548
-
549
- SLIDE 1 - Title & Introduction:
550
- - Large title: "{dashboard_title}"
551
- - Subtitle: "Business Intelligence Dashboard Analysis"
552
- - Brief introduction about the data and purpose
553
- - Beautiful gradient background matching {domain} theme
554
- - Company logo placeholder if applicable
555
-
556
- SLIDE 2 - Key Objectives & Questions:
557
- - Title: "Business Objectives"
558
- - List 3-4 core business questions this analysis answers
559
- - Use bullet points with icons
560
- - Examples: "What drives revenue growth?", "Which segments perform best?", etc.
561
-
562
- SLIDE 3 - Data & Analysis:
563
- - Title: "Key Findings & Visualizations"
564
- - Include 2-3 mini chart visualizations using Chart.js
565
- - Show the most important metrics and trends
566
- - Use actual data from the metrics provided
567
- - Keep charts simple and clear
568
-
569
- SLIDE 4 - Insights & Recommendations:
570
- - Title: "Strategic Insights"
571
- - Present the top 3 insights from the data
572
- - Add actionable recommendations for each insight
573
- - Use cards/boxes for visual separation
574
- - Include trend indicators (↑↓→)
575
-
576
- SLIDE 5 - Conclusion & Next Steps:
577
- - Title: "Conclusion & Action Plan"
578
- - Recap key takeaways (3-4 points)
579
- - Suggest 2-3 concrete next steps
580
- - Add a "Questions?" section
581
- - Thank you message
582
-
583
- Technical Requirements:
584
- 1. Full-screen slides (100vh height, 100vw width)
585
- 2. Slide navigation: Previous/Next buttons + keyboard arrows
586
- 3. Slide counter: "Slide X of 5"
587
- 4. Smooth transitions between slides (slide/fade effect)
588
- 5. Professional design matching {domain} color scheme:
589
- - Finance: Navy blue with gold accents
590
- - Retail: Orange and green tones
591
- - Healthcare: Teal and blue
592
- - Entertainment: Purple and magenta
593
- - Technology: Cyan and blue
594
- - Generic: Professional blue-gray
595
- 6. Use Chart.js for any charts (CDN: https://cdn.jsdelivr.net/npm/chart.js)
596
- 7. Responsive typography and spacing
597
- 8. Each slide should be self-contained and visually appealing
598
- 9. Add subtle animations (fade-in effects for content)
599
- 10. Footer on each slide with page number and date
600
-
601
- Return ONLY complete HTML code starting with <!DOCTYPE html>
602
- NO markdown, NO explanations.
603
- The presentation should look like a professional PowerPoint/Keynote presentation.
604
- """
605
-
606
- response = client.models.generate_content(
607
- model="gemini-2.0-flash-exp",
608
- contents=[presentation_prompt]
609
- )
610
-
611
- ppt_html = response.text.strip()
612
-
613
- if ppt_html.startswith("```"):
614
- ppt_html = ppt_html.split("```")[1]
615
- if ppt_html.startswith("html"):
616
- ppt_html = ppt_html[4:]
617
- ppt_html = ppt_html.strip()
618
-
619
- st.session_state['presentation'] = ppt_html
620
- st.success("✅ Presentation generated!")
621
-
622
- except Exception as e:
623
- st.error(f"❌ Error generating presentation: {e}")
624
- st.exception(e)
625
-
626
- # ------------------------------
627
- # Display Presentation
628
- # ------------------------------
629
- if 'presentation' in st.session_state:
630
- st.markdown("---")
631
- st.markdown("### 🎤 AI-Generated Business Presentation")
632
- st.info("Use arrow keys or navigation buttons to move between slides")
633
-
634
- ppt_html = st.session_state['presentation']
635
-
636
- # Display the presentation
637
- components.html(ppt_html, height=700, scrolling=False)
638
-
639
- st.download_button(
640
- label="📥 Download Presentation (HTML)",
641
- data=ppt_html,
642
- file_name=f"presentation_{uploaded_file.name.split('.')[0]}.html",
643
- mime="text/html",
644
- use_container_width=True
645
- )
646
-
647
- except Exception as e:
648
- st.error(f"❌ Error loading file: {e}")
649
- st.exception(e)
650
 
651
- else:
652
- st.info("👆 Please upload a CSV or Excel file to get started.")
 
 
 
 
653
 
654
- # ------------------------------
655
- # Footer
656
- # ------------------------------
657
- st.markdown("---")
658
- st.markdown(
659
- f"<div style='text-align: center; color: gray;'>Built with Streamlit & Google Gemini AI</div>",
660
- unsafe_allow_html=True
661
- )
 
 
 
 
 
10
  from datetime import datetime, date
11
  import io
12
  import base64
13
+ from typing import Dict, List, Any, Optional
14
 
15
  # ------------------------------
16
+ # Configuration & Constants
17
+ # ------------------------------
18
+ APP_TITLE = "Enterprise AI BI Dashboard"
19
+ APP_ICON = "🚀"
20
+
21
+ # Model Configuration Strategy
22
+ # We define the specific requested models here.
23
+ # NOTE: Ensure your Google Cloud Project has access to these specific Model IDs.
24
+ AI_CONFIG = {
25
+ "analyst_model": "gemini-3.0-pro-preview", # The heavy lifter for reasoning
26
+ "dashboard_model": "gemini-nano-banana-pro", # The specialist for HTML/Code generation
27
+ "fallback_model": "gemini-2.0-flash-exp" # Fallback if specific previews aren't active
28
+ }
29
+
30
+ # ------------------------------
31
+ # Service Layer: Utilities
32
  # ------------------------------
33
  class CustomJSONEncoder(json.JSONEncoder):
34
+ """Robust JSON Encoder for Dataframes and NumPy types."""
35
  def default(self, obj):
36
  if isinstance(obj, (datetime, date, pd.Timestamp)):
37
  return obj.isoformat()
 
45
  return None
46
  return super().default(obj)
47
 
48
+ def clean_ai_response(text: str) -> str:
49
+ """Cleans Markdown code blocks from AI responses."""
50
+ text = text.strip()
51
+ if text.startswith("```"):
52
+ # Find the first newline to skip the language tag (e.g. ```json)
53
+ newline_index = text.find("\n")
54
+ if newline_index != -1:
55
+ text = text[newline_index+1:]
56
+ # Remove the closing ```
57
+ if text.endswith("```"):
58
+ text = text[:-3]
59
+ return text.strip()
60
+
61
  # ------------------------------
62
+ # Service Layer: AI Handler
63
  # ------------------------------
64
+ class AIService:
65
+ def __init__(self, api_key: str):
66
+ self.client = genai.Client(api_key=api_key)
67
+
68
+ def _generate(self, model_id: str, prompt: str) -> str:
69
+ """Wrapper to handle generation with fallback logic."""
70
+ try:
71
+ response = self.client.models.generate_content(
72
+ model=model_id,
73
+ contents=[prompt]
74
+ )
75
+ return response.text
76
+ except Exception as e:
77
+ # If the specific preview model fails (404/Permission), try fallback
78
+ if "404" in str(e) or "not found" in str(e).lower():
79
+ st.warning(f"⚠️ Model '{model_id}' not found. Falling back to '{AI_CONFIG['fallback_model']}'.")
80
+ response = self.client.models.generate_content(
81
+ model=AI_CONFIG['fallback_model'],
82
+ contents=[prompt]
83
+ )
84
+ return response.text
85
+ raise e
86
+
87
+ def analyze_dataset(self, schema: Dict) -> Dict:
88
+ """Uses Gemini 3.0 Pro to analyze data structure and suggest charts."""
89
+ prompt = f"""
90
+ You are a Principal Data Architect. Analyze this dataset schema:
91
+ {json.dumps(schema, indent=2, cls=CustomJSONEncoder)}
92
+
93
+ Task:
94
+ 1. Identify the industry/domain.
95
+ 2. Determine if this is company-specific data.
96
+ 3. Create a visualization plan with 4-6 specific charts.
97
+ 4. Generate 3 C-level executive insights.
98
 
99
+ Return ONLY raw JSON:
100
+ {{
101
+ "domain": "string",
102
+ "is_company_data": boolean,
103
+ "charts": [
104
+ {{"type": "bar|line|scatter|pie|histogram", "x": "col", "y": "col", "title": "string"}}
105
+ ],
106
+ "insights": ["string"]
107
+ }}
108
+ """
109
+ response_text = self._generate(AI_CONFIG['analyst_model'], prompt)
110
+ return json.loads(clean_ai_response(response_text))
111
+
112
+ def generate_dashboard_html(self, context: Dict) -> str:
113
+ """Uses Gemini Nano Banana Pro to generate high-performance HTML."""
114
+ prompt = f"""
115
+ You are an Expert Frontend Engineer specialized in BI Dashboards.
116
+
117
+ CONTEXT:
118
+ Title: {context['title']}
119
+ Domain: {context['domain']}
120
+ Stats: {json.dumps(context['stats'], cls=CustomJSONEncoder)}
121
+ Sample: {json.dumps(context['sample'], cls=CustomJSONEncoder)}
122
+
123
+ REQUIREMENTS:
124
+ 1. Create a single-file, responsive HTML dashboard.
125
+ 2. Use **Chart.js** via CDN.
126
+ 3. Style with a modern, glassmorphism dark theme suitable for {context['domain']}.
127
+ 4. Include a 'Key Metrics' row at the top (Cards).
128
+ 5. Include a grid of interactive charts.
129
+ 6. Handle missing data gracefully in JavaScript.
130
+
131
+ Return ONLY valid HTML code.
132
+ """
133
+ return clean_ai_response(self._generate(AI_CONFIG['dashboard_model'], prompt))
134
+
135
+ def generate_presentation(self, context: Dict) -> str:
136
+ """Uses Gemini 3.0 Pro to generate a strategic slide deck."""
137
+ prompt = f"""
138
+ Create a Reveal.js (HTML) presentation for this dataset.
139
+
140
+ Title: {context['title']}
141
+ Insights: {json.dumps(context['insights'])}
142
+
143
+ Create 5 slides: Title, Objectives, Data Analysis, Strategic Insights, Conclusion.
144
+ Use a professional gradient theme.
145
+ Return ONLY valid HTML.
146
+ """
147
+ return clean_ai_response(self._generate(AI_CONFIG['analyst_model'], prompt))
148
 
149
  # ------------------------------
150
+ # UI Configuration
151
  # ------------------------------
152
+ st.set_page_config(page_title=APP_TITLE, page_icon=APP_ICON, layout="wide")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
 
154
  st.markdown("""
155
  <style>
156
+ .stApp { background-color: #0e1117; color: #fafafa; }
157
+ .stButton>button { border-radius: 8px; font-weight: bold; }
158
+ div[data-testid="stMetricValue"] { font-size: 24px; color: #4db8ff; }
 
159
  </style>
160
  """, unsafe_allow_html=True)
161
 
162
  # ------------------------------
163
+ # Main Application Logic
164
  # ------------------------------
165
+ def main():
166
+ # --- Sidebar ---
167
+ with st.sidebar:
168
+ st.header(f"{APP_ICON} Configuration")
169
+ api_key = st.text_input("🔑 Google Gemini API Key", type="password")
170
+
171
+ st.divider()
172
+ st.caption("Active Models:")
173
+ st.code(f"Analyst: {AI_CONFIG['analyst_model']}\nDashboard: {AI_CONFIG['dashboard_model']}")
174
+
175
+ st.info("Ensure your API key has access to the Preview models, otherwise fallback will be used.")
176
 
177
+ if not api_key:
178
+ st.warning("⚠️ Please enter your API Key to initialize the AI Engine.")
179
+ st.stop()
 
 
 
 
 
 
180
 
181
+ # Initialize Service
182
  try:
183
+ ai_service = AIService(api_key)
184
+ except Exception as e:
185
+ st.error(f"Failed to initialize AI Client: {e}")
186
+ st.stop()
187
+
188
+ # --- Main Content ---
189
+ st.title(f"{APP_TITLE}")
190
+
191
+ uploaded_file = st.file_uploader("📂 Upload Data (CSV/Excel)", type=["csv", "xlsx"])
192
+
193
+ if uploaded_file:
194
+ # Load Data
195
+ try:
196
+ if uploaded_file.name.endswith('.csv'):
197
  df = pd.read_csv(uploaded_file)
198
  else:
199
  df = pd.read_excel(uploaded_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
+ # Basic cleanup
202
+ df.columns = [c.strip() for c in df.columns]
203
 
204
+ # --- Data Overview ---
205
+ st.divider()
206
+ c1, c2, c3, c4 = st.columns(4)
207
+ c1.metric("Rows", df.shape[0])
208
+ c2.metric("Columns", df.shape[1])
209
+ c3.metric("Numeric Fields", len(df.select_dtypes(include=np.number).columns))
210
+ c4.metric("Categorical Fields", len(df.select_dtypes(exclude=np.number).columns))
211
+
212
+ with st.expander("🔍 View Raw Data & Quality Checks"):
213
+ st.dataframe(df.head())
214
+ st.write(df.describe())
215
+
216
+ # --- AI Operations ---
217
+ st.divider()
218
+ st.subheader("🤖 AI Intelligence Operations")
219
+
220
+ col_ops1, col_ops2, col_ops3 = st.columns(3)
221
+
222
+ # Prepare Schema for AI (Lightweight)
223
+ sample_data = df.head(3).copy()
224
+ # Convert timestamps to string for JSON serialization
225
+ for col in sample_data.columns:
226
+ if pd.api.types.is_datetime64_any_dtype(sample_data[col]):
227
+ sample_data[col] = sample_data[col].astype(str)
228
+
229
+ schema = {
230
+ "columns": {col: str(df[col].dtype) for col in df.columns},
231
+ "sample": sample_data.to_dict(orient='records'),
232
+ "numeric_columns": df.select_dtypes(include=np.number).columns.tolist()
233
+ }
234
+
235
+ # 1. ANALYZE DATA
236
+ if col_ops1.button("📊 Analyze & Visualize", type="primary", use_container_width=True):
237
+ with st.spinner(f"Reasoning with {AI_CONFIG['analyst_model']}..."):
238
+ try:
239
+ analysis = ai_service.analyze_dataset(schema)
240
+ st.session_state['analysis'] = analysis
241
+ st.session_state['df_context'] = df # Store for plotting
242
+ except Exception as e:
243
+ st.error(f"Analysis failed: {e}")
244
+
245
+ # 2. GENERATE DASHBOARD
246
+ if col_ops2.button("🎨 Create HTML Dashboard", use_container_width=True):
247
+ if 'analysis' not in st.session_state:
248
+ st.warning("Please run 'Analyze' first to determine the domain.")
249
  else:
250
+ with st.spinner(f"Coding with {AI_CONFIG['dashboard_model']}..."):
251
+ try:
252
+ # Prepare context
253
+ stats = df.describe().to_dict()
254
+ context = {
255
+ "title": uploaded_file.name,
256
+ "domain": st.session_state['analysis'].get('domain', 'General'),
257
+ "stats": stats,
258
+ "sample": df.head(15).to_dict(orient='records') # larger sample for dashboard
259
+ }
260
+ html_code = ai_service.generate_dashboard_html(context)
261
+ st.session_state['html_dashboard'] = html_code
262
+ except Exception as e:
263
+ st.error(f"Dashboard generation failed: {e}")
264
+
265
+ # 3. GENERATE SLIDES
266
+ if col_ops3.button("🎤 Generate Presentation", use_container_width=True):
267
+ if 'analysis' not in st.session_state:
268
+ st.warning("Please run 'Analyze' first.")
269
+ else:
270
+ with st.spinner("Drafting slides..."):
271
+ context = {
272
+ "title": uploaded_file.name,
273
+ "insights": st.session_state['analysis'].get('insights', [])
274
+ }
275
+ ppt_html = ai_service.generate_presentation(context)
276
+ st.session_state['ppt_html'] = ppt_html
277
+
278
+ # --- Display Results ---
279
 
280
+ # Result 1: Static Charts (Collage)
281
+ if 'analysis' in st.session_state and 'df_context' in st.session_state:
282
+ st.divider()
283
+ st.subheader(f"📈 Strategic Analysis ({st.session_state['analysis']['domain']})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
+ # Display Insights
286
+ for i, insight in enumerate(st.session_state['analysis']['insights']):
287
+ st.success(f"**Insight {i+1}:** {insight}")
288
+
289
+ # Plotting logic
290
+ charts = st.session_state['analysis']['charts']
291
+ fig = plt.figure(figsize=(18, 5 * ((len(charts)+2)//3)))
292
 
293
+ for idx, chart in enumerate(charts, 1):
294
+ ax = fig.add_subplot(((len(charts)+2)//3), 3, idx)
295
+ try:
296
+ c_type = chart['type']
297
+ x_col = chart.get('x')
298
+ y_col = chart.get('y')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
+ if c_type == 'bar' and x_col and y_col:
301
+ # Aggregate for bar charts to avoid clutter
302
+ data_agg = df.groupby(x_col)[y_col].sum().nlargest(10)
303
+ sns.barplot(x=data_agg.values, y=data_agg.index, ax=ax, palette="viridis")
304
+ ax.set_title(chart['title'])
305
+ elif c_type == 'scatter' and x_col and y_col:
306
+ sns.scatterplot(data=df, x=x_col, y=y_col, ax=ax, alpha=0.6)
307
+ ax.set_title(chart['title'])
308
+ elif c_type == 'line' and x_col and y_col:
309
+ # Sort for line charts
310
+ temp_df = df.sort_values(x_col)
311
+ sns.lineplot(data=temp_df, x=x_col, y=y_col, ax=ax)
312
+ ax.set_title(chart['title'])
313
+ elif c_type == 'histogram' and x_col:
314
+ sns.histplot(df[x_col], kde=True, ax=ax)
315
+ ax.set_title(chart['title'])
316
 
317
+ # Cleanup axes
318
+ ax.tick_params(axis='x', rotation=45)
319
+ except Exception as e:
320
+ ax.text(0.5, 0.5, "Could not render chart", ha='center')
321
+
322
+ plt.tight_layout()
323
+ st.pyplot(fig)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
+ # Result 2: HTML Dashboard
326
+ if 'html_dashboard' in st.session_state:
327
+ st.divider()
328
+ st.subheader("🖥️ Interactive Dashboard (Banana Pro Generated)")
329
+ components.html(st.session_state['html_dashboard'], height=800, scrolling=True)
330
+ st.download_button("📥 Download HTML", st.session_state['html_dashboard'], "dashboard.html", "text/html")
331
 
332
+ # Result 3: Presentation
333
+ if 'ppt_html' in st.session_state:
334
+ st.divider()
335
+ st.subheader("📽️ Executive Presentation")
336
+ components.html(st.session_state['ppt_html'], height=600)
337
+ st.download_button("📥 Download Slides", st.session_state['ppt_html'], "presentation.html", "text/html")
338
+
339
+ except Exception as e:
340
+ st.error(f"Error processing file: {e}")
341
+
342
+ if __name__ == "__main__":
343
+ main()