Valentina9502 commited on
Commit
224142e
·
verified ·
1 Parent(s): d4f154f

Delete main.py

Browse files
Files changed (1) hide show
  1. main.py +0 -1069
main.py DELETED
@@ -1,1069 +0,0 @@
1
- """
2
- GlycoAI - AI-Powered Glucose Insights
3
- Main Gradio application with both demo users AND real Dexcom OAuth
4
- """
5
-
6
- import gradio as gr
7
- import plotly.graph_objects as go
8
- import plotly.express as px
9
- from datetime import datetime, timedelta
10
- import pandas as pd
11
- from typing import Optional, Tuple, List
12
- import logging
13
- import os
14
- import webbrowser
15
- import urllib.parse
16
-
17
- # Load environment variables from .env file
18
- from dotenv import load_dotenv
19
- load_dotenv()
20
-
21
- # Import the Mistral chat class and unified data manager
22
- from mistral_chat import GlucoBuddyMistralChat, validate_environment
23
- from unified_data_manager import UnifiedDataManager
24
-
25
- # Setup logging
26
- logging.basicConfig(level=logging.INFO)
27
- logger = logging.getLogger(__name__)
28
-
29
- # Import our custom functions
30
- from apifunctions import (
31
- DexcomAPI,
32
- GlucoseAnalyzer,
33
- DEMO_USERS,
34
- format_glucose_data_for_display
35
- )
36
-
37
- # Import real Dexcom OAuth (now working!)
38
- try:
39
- from dexcom_real_auth_system import DexcomRealAPI
40
- REAL_OAUTH_AVAILABLE = True
41
- logger.info("✅ Real Dexcom OAuth available")
42
- except ImportError as e:
43
- REAL_OAUTH_AVAILABLE = False
44
- logger.warning(f"⚠️ Real Dexcom OAuth not available: {e}")
45
-
46
- class GlucoBuddyApp:
47
- """Main application class for GlucoBuddy with demo users AND real OAuth"""
48
-
49
- def __init__(self):
50
- # Validate environment before initializing
51
- if not validate_environment():
52
- raise ValueError("Environment validation failed - check your .env file or environment variables")
53
-
54
- # Single data manager for consistency
55
- self.data_manager = UnifiedDataManager()
56
-
57
- # Chat interface (will use data manager's context)
58
- self.mistral_chat = GlucoBuddyMistralChat()
59
-
60
- # Real OAuth API (if available)
61
- self.real_api = DexcomRealAPI(environment="sandbox") if REAL_OAUTH_AVAILABLE else None
62
-
63
- # UI state
64
- self.chat_history = []
65
- self.current_user_type = None # "demo" or "real"
66
-
67
- def select_demo_user(self, user_key: str) -> Tuple[str, str]:
68
- """Handle demo user selection and load data consistently"""
69
- if user_key not in DEMO_USERS:
70
- return "❌ Invalid user selection", gr.update(visible=False)
71
-
72
- try:
73
- # Load data through unified manager
74
- load_result = self.data_manager.load_user_data(user_key)
75
-
76
- if not load_result['success']:
77
- return f"❌ {load_result['message']}", gr.update(visible=False)
78
-
79
- user = self.data_manager.current_user
80
- self.current_user_type = "demo"
81
-
82
- # Update Mistral chat with the same context
83
- self._sync_chat_with_data_manager()
84
-
85
- # Clear chat history when switching users
86
- self.chat_history = []
87
- self.mistral_chat.clear_conversation()
88
-
89
- return (
90
- f"Connected: {user.name} ({user.device_type}) - DEMO DATA - Click 'Load Data' to begin",
91
- gr.update(visible=True)
92
- )
93
-
94
- except Exception as e:
95
- logger.error(f"Demo user selection failed: {str(e)}")
96
- return f"❌ Connection failed: {str(e)}", gr.update(visible=False)
97
-
98
- def start_real_oauth(self) -> str:
99
- """Start real Dexcom OAuth process"""
100
- if not REAL_OAUTH_AVAILABLE:
101
- return """
102
- ❌ **Real Dexcom OAuth Not Available**
103
-
104
- The real authentication module is not properly configured.
105
- Please ensure:
106
- 1. dexcom_real_auth_system.py exists and imports correctly
107
- 2. You have valid Dexcom developer credentials
108
- 3. All dependencies are installed
109
-
110
- For now, please use the demo users above for instant access to realistic glucose data.
111
- """
112
-
113
- try:
114
- # Start OAuth flow
115
- success = self.real_api.start_oauth_flow()
116
-
117
- if success:
118
- return f"""
119
- 🚀 **Real Dexcom OAuth Started**
120
-
121
- **SIMPLIFIED PROCESS FOR PORT 7860:**
122
-
123
- 1. ✅ Browser should have opened to Dexcom login page
124
- 2. 📝 Log in with your **real Dexcom account credentials**
125
- - For sandbox testing: `[email protected]` / `Dexcom123!`
126
- 3. ✅ Authorize GlycoAI to access your data
127
- 4. ❌ **You will get a 404 error - THIS IS EXPECTED!**
128
- 5. 📋 **Copy ONLY the authorization code** from the URL
129
-
130
- **Example callback URL:**
131
- `http://localhost:7860/callback?code=ABC123XYZ&state=...`
132
-
133
- **Copy just this part:** `ABC123XYZ`
134
-
135
- **Why simpler?** Your token generator script works perfectly with just the code, so we're using the same approach!
136
- """
137
- else:
138
- return "❌ Failed to start OAuth process. Check console for details."
139
-
140
- except Exception as e:
141
- logger.error(f"OAuth start error: {e}")
142
- return f"❌ OAuth error: {str(e)}"
143
-
144
- def complete_real_oauth(self, auth_code_input: str) -> Tuple[str, str]:
145
- """Complete real OAuth with authorization code (like the working script)"""
146
- if not REAL_OAUTH_AVAILABLE:
147
- return "❌ Real OAuth not available", gr.update(visible=False)
148
-
149
- if not auth_code_input or not auth_code_input.strip():
150
- return "❌ Please paste the authorization code", gr.update(visible=False)
151
-
152
- try:
153
- # Clean up the input - handle both full URLs and just codes
154
- auth_code = self._extract_auth_code(auth_code_input.strip())
155
-
156
- if not auth_code:
157
- return "❌ No authorization code found in input", gr.update(visible=False)
158
-
159
- logger.info(f"Processing authorization code: {auth_code[:20]}...")
160
-
161
- # Use the same method as the working script - direct token exchange
162
- success = self.real_api.exchange_code_for_tokens(auth_code)
163
-
164
- if success:
165
- logger.info("✅ Token exchange successful")
166
-
167
- # Load real data into data manager
168
- real_data_result = self._load_real_dexcom_data()
169
-
170
- if real_data_result['success']:
171
- self.current_user_type = "real"
172
-
173
- # Update chat context
174
- self._sync_chat_with_data_manager()
175
-
176
- # Clear chat history for new user
177
- self.chat_history = []
178
- self.mistral_chat.clear_conversation()
179
-
180
- return (
181
- f"✅ Connected: Real Dexcom User - LIVE DATA - Click 'Load Data' to begin",
182
- gr.update(visible=True)
183
- )
184
- else:
185
- return f"❌ Data loading failed: {real_data_result['message']}", gr.update(visible=False)
186
- else:
187
- return "❌ Token exchange failed - check the authorization code", gr.update(visible=False)
188
-
189
- except Exception as e:
190
- logger.error(f"OAuth completion error: {e}")
191
- return f"❌ OAuth completion failed: {str(e)}", gr.update(visible=False)
192
-
193
- def _extract_auth_code(self, input_text: str) -> str:
194
- """Extract authorization code from various input formats"""
195
- try:
196
- # If it's a full URL, parse it
197
- if input_text.startswith('http'):
198
- parsed_url = urllib.parse.urlparse(input_text)
199
- query_params = urllib.parse.parse_qs(parsed_url.query)
200
-
201
- if 'code' in query_params:
202
- return query_params['code'][0]
203
- else:
204
- logger.warning(f"No 'code' parameter found in URL: {input_text}")
205
- return ""
206
- else:
207
- # Assume it's just the authorization code
208
- # Remove any "code=" prefix if present
209
- if input_text.startswith('code='):
210
- return input_text[5:]
211
- else:
212
- return input_text
213
-
214
- except Exception as e:
215
- logger.error(f"Error extracting auth code: {e}")
216
- return ""
217
-
218
- def _load_real_dexcom_data(self) -> dict:
219
- """Load real Dexcom data through the unified data manager"""
220
- try:
221
- # Get data range
222
- data_range = self.real_api.get_data_range()
223
-
224
- # Get glucose data (last 14 days)
225
- end_time = datetime.now()
226
- start_time = end_time - timedelta(days=14)
227
-
228
- egv_data = self.real_api.get_egv_data(
229
- start_date=start_time.isoformat(),
230
- end_date=end_time.isoformat()
231
- )
232
-
233
- # Get events data
234
- events_data = self.real_api.get_events_data(
235
- start_date=start_time.isoformat(),
236
- end_date=end_time.isoformat()
237
- )
238
-
239
- # Create a real user profile
240
- from dataclasses import dataclass
241
-
242
- @dataclass
243
- class RealDexcomUser:
244
- name: str = "Real Dexcom User"
245
- age: int = 0
246
- device_type: str = "Real Dexcom Device"
247
- username: str = "authenticated_user"
248
- password: str = "oauth_token"
249
- description: str = "Authenticated real Dexcom user with live data"
250
- diabetes_type: str = "Real Patient"
251
- years_with_diabetes: int = 0
252
- typical_glucose_pattern: str = "real_data"
253
-
254
- real_user = RealDexcomUser()
255
-
256
- # Process the real data through unified data manager
257
- # Convert to format expected by data manager
258
- formatted_data = {
259
- "data_range": data_range,
260
- "egv_data": egv_data,
261
- "events_data": events_data,
262
- "source": "real_dexcom_api"
263
- }
264
-
265
- # Load into data manager
266
- self.data_manager.current_user = real_user
267
- self.data_manager.processed_glucose_data = pd.DataFrame(egv_data) if egv_data else pd.DataFrame()
268
-
269
- if not self.data_manager.processed_glucose_data.empty:
270
- # Process timestamps
271
- self.data_manager.processed_glucose_data['systemTime'] = pd.to_datetime(
272
- self.data_manager.processed_glucose_data['systemTime']
273
- )
274
- self.data_manager.processed_glucose_data['displayTime'] = pd.to_datetime(
275
- self.data_manager.processed_glucose_data['displayTime']
276
- )
277
- self.data_manager.processed_glucose_data['value'] = pd.to_numeric(
278
- self.data_manager.processed_glucose_data['value'], errors='coerce'
279
- )
280
-
281
- # Calculate stats
282
- from apifunctions import GlucoseAnalyzer
283
- self.data_manager.calculated_stats = GlucoseAnalyzer.calculate_basic_stats(
284
- self.data_manager.processed_glucose_data
285
- )
286
- self.data_manager.identified_patterns = GlucoseAnalyzer.identify_patterns(
287
- self.data_manager.processed_glucose_data
288
- )
289
-
290
- logger.info(f"Loaded real Dexcom data: {len(egv_data)} glucose readings")
291
-
292
- return {
293
- 'success': True,
294
- 'message': f'Loaded {len(egv_data)} real glucose readings'
295
- }
296
-
297
- except Exception as e:
298
- logger.error(f"Failed to load real Dexcom data: {e}")
299
- return {
300
- 'success': False,
301
- 'message': f'Failed to load real data: {str(e)}'
302
- }
303
-
304
- def load_glucose_data(self) -> Tuple[str, go.Figure]:
305
- """Load and display glucose data using unified manager"""
306
- if not self.data_manager.current_user:
307
- return "Please select a user first (demo or real Dexcom)", None
308
-
309
- try:
310
- # For real users, we already loaded the data during OAuth
311
- if self.current_user_type == "real":
312
- if self.data_manager.processed_glucose_data.empty:
313
- return "No real glucose data available", None
314
- else:
315
- # For demo users, force reload data to ensure freshness
316
- load_result = self.data_manager.load_user_data(
317
- self._get_current_user_key(),
318
- force_reload=True
319
- )
320
-
321
- if not load_result['success']:
322
- return load_result['message'], None
323
-
324
- # Get unified stats
325
- stats = self.data_manager.get_stats_for_ui()
326
- chart_data = self.data_manager.get_chart_data()
327
-
328
- # Sync chat with fresh data
329
- self._sync_chat_with_data_manager()
330
-
331
- if chart_data is None or chart_data.empty:
332
- return "No glucose data available", None
333
-
334
- # Build data summary with CONSISTENT metrics
335
- user = self.data_manager.current_user
336
- data_points = stats.get('total_readings', 0)
337
- avg_glucose = stats.get('average_glucose', 0)
338
- std_glucose = stats.get('std_glucose', 0)
339
- min_glucose = stats.get('min_glucose', 0)
340
- max_glucose = stats.get('max_glucose', 0)
341
-
342
- time_in_range = stats.get('time_in_range_70_180', 0)
343
- time_below_range = stats.get('time_below_70', 0)
344
- time_above_range = stats.get('time_above_180', 0)
345
-
346
- gmi = stats.get('gmi', 0)
347
- cv = stats.get('cv', 0)
348
-
349
- # Calculate date range
350
- end_date = datetime.now()
351
- start_date = end_date - timedelta(days=14)
352
-
353
- # Determine data source
354
- data_source = "REAL DEXCOM DATA" if self.current_user_type == "real" else "DEMO DATA"
355
-
356
- data_summary = f"""
357
- ## 📊 Data Summary for {user.name}
358
-
359
- ### Basic Information
360
- • **Data Type:** {data_source}
361
- • **Analysis Period:** {start_date.strftime('%B %d, %Y')} to {end_date.strftime('%B %d, %Y')} (14 days)
362
- • **Total Readings:** {data_points:,} glucose measurements
363
- • **Device:** {user.device_type}
364
- • **Data Source:** {stats.get('data_source', 'unknown').upper()}
365
-
366
- ### Glucose Statistics
367
- • **Average Glucose:** {avg_glucose:.1f} mg/dL
368
- • **Standard Deviation:** {std_glucose:.1f} mg/dL
369
- • **Coefficient of Variation:** {cv:.1f}%
370
- • **Glucose Range:** {min_glucose:.0f} - {max_glucose:.0f} mg/dL
371
- • **GMI (Glucose Management Indicator):** {gmi:.1f}%
372
-
373
- ### Time in Range Analysis
374
- • **Time in Range (70-180 mg/dL):** {time_in_range:.1f}%
375
- • **Time Below Range (<70 mg/dL):** {time_below_range:.1f}%
376
- • **Time Above Range (>180 mg/dL):** {time_above_range:.1f}%
377
-
378
- ### Clinical Targets
379
- • **Target Time in Range:** >70% (Current: {time_in_range:.1f}%)
380
- • **Target Time Below Range:** <4% (Current: {time_below_range:.1f}%)
381
- • **Target CV:** <36% (Current: {cv:.1f}%)
382
-
383
- ### Data Validation
384
- • **In Range Count:** {stats.get('in_range_count', 0)} readings
385
- • **Below Range Count:** {stats.get('below_range_count', 0)} readings
386
- • **Above Range Count:** {stats.get('above_range_count', 0)} readings
387
- • **Total Verified:** {stats.get('in_range_count', 0) + stats.get('below_range_count', 0) + stats.get('above_range_count', 0)} readings
388
-
389
- ### 14-Day Analysis Benefits
390
- • **Enhanced Pattern Recognition:** Captures full weekly cycles and variations
391
- • **Improved Trend Analysis:** Identifies consistent patterns vs. one-time events
392
- • **Better Clinical Insights:** More reliable data for healthcare decisions
393
- • **AI Consistency:** Same data used for chat analysis and UI display
394
-
395
- ### Authentication Status
396
- • **User Type:** {self.current_user_type.upper() if self.current_user_type else 'Unknown'}
397
- • **OAuth Status:** {'✅ Authenticated with real Dexcom account' if self.current_user_type == 'real' else '🎭 Using demo data for testing'}
398
- """
399
-
400
- chart = self.create_glucose_chart()
401
-
402
- return data_summary, chart
403
-
404
- except Exception as e:
405
- logger.error(f"Failed to load glucose data: {str(e)}")
406
- return f"Failed to load glucose data: {str(e)}", None
407
-
408
- def _sync_chat_with_data_manager(self):
409
- """Ensure chat uses the same data as the UI"""
410
- try:
411
- # Get context from unified data manager
412
- context = self.data_manager.get_context_for_agent()
413
-
414
- # Update chat's internal data to match
415
- if not context.get("error"):
416
- self.mistral_chat.current_user = self.data_manager.current_user
417
- self.mistral_chat.current_glucose_data = self.data_manager.processed_glucose_data
418
- self.mistral_chat.current_stats = self.data_manager.calculated_stats
419
- self.mistral_chat.current_patterns = self.data_manager.identified_patterns
420
-
421
- logger.info(f"Synced chat with data manager - TIR: {self.data_manager.calculated_stats.get('time_in_range_70_180', 0):.1f}%")
422
-
423
- except Exception as e:
424
- logger.error(f"Failed to sync chat with data manager: {e}")
425
-
426
- def _get_current_user_key(self) -> str:
427
- """Get the current user key"""
428
- if not self.data_manager.current_user:
429
- return ""
430
-
431
- # Find the key for current user
432
- for key, user in DEMO_USERS.items():
433
- if user == self.data_manager.current_user:
434
- return key
435
- return ""
436
-
437
- def get_template_prompts(self) -> List[str]:
438
- """Get template prompts based on current user data"""
439
- if not self.data_manager.current_user or not self.data_manager.calculated_stats:
440
- return [
441
- "What should I know about managing my diabetes?",
442
- "How can I improve my glucose control?"
443
- ]
444
-
445
- stats = self.data_manager.calculated_stats
446
- time_in_range = stats.get('time_in_range_70_180', 0)
447
- time_below_70 = stats.get('time_below_70', 0)
448
-
449
- templates = []
450
-
451
- if time_in_range < 70:
452
- templates.append(f"My time in range is {time_in_range:.1f}% which is below the 70% target. What specific strategies can help me improve it?")
453
- else:
454
- templates.append(f"My time in range is {time_in_range:.1f}% which meets the target. How can I maintain this level of control?")
455
-
456
- if time_below_70 > 4:
457
- templates.append(f"I'm experiencing {time_below_70:.1f}% time below 70 mg/dL. What can I do to prevent these low episodes?")
458
- else:
459
- templates.append("What are the best practices for preventing hypoglycemia in my situation?")
460
-
461
- # Add data source specific template
462
- if self.current_user_type == "real":
463
- templates.append("This is my real Dexcom data. What insights can you provide about my actual glucose patterns?")
464
- else:
465
- templates.append("Based on this demo data, what would you recommend for someone with similar patterns?")
466
-
467
- return templates
468
-
469
- def chat_with_mistral(self, message: str, history: List) -> Tuple[str, List]:
470
- """Handle chat interaction with Mistral using unified data"""
471
- if not message.strip():
472
- return "", history
473
-
474
- if not self.data_manager.current_user:
475
- response = "Please select a user first (demo or real Dexcom) to get personalized insights about glucose data."
476
- history.append([message, response])
477
- return "", history
478
-
479
- try:
480
- # Ensure chat is synced with latest data
481
- self._sync_chat_with_data_manager()
482
-
483
- # Send message to Mistral chat
484
- result = self.mistral_chat.chat_with_mistral(message)
485
-
486
- if result['success']:
487
- response = result['response']
488
-
489
- # Add data consistency note
490
- validation = self.data_manager.validate_data_consistency()
491
- if validation.get('valid'):
492
- data_age = validation.get('data_age_minutes', 0)
493
- if data_age > 10: # Warn if data is old
494
- response += f"\n\n📊 *Note: Analysis based on data from {data_age} minutes ago. Reload data for most current insights.*"
495
-
496
- # Add data source context
497
- data_type = "real Dexcom data" if self.current_user_type == "real" else "demo data"
498
- if self.current_user_type == "real":
499
- response += f"\n\n🔐 *This analysis is based on your real Dexcom data from your authenticated account.*"
500
- else:
501
- response += f"\n\n🎭 *This analysis is based on demo data for testing purposes.*"
502
-
503
- # Add context note if no user data was included
504
- if not result.get('context_included', True):
505
- response += f"\n\n💡 *For more personalized advice, make sure your glucose data is loaded.*"
506
- else:
507
- response = f"I apologize, but I encountered an error: {result.get('error', 'Unknown error')}. Please try again or rephrase your question."
508
-
509
- history.append([message, response])
510
- return "", history
511
-
512
- except Exception as e:
513
- logger.error(f"Chat error: {str(e)}")
514
- error_response = f"I apologize, but I encountered an error while processing your question: {str(e)}. Please try rephrasing your question."
515
- history.append([message, error_response])
516
- return "", history
517
-
518
- def use_template_prompt(self, template_text: str) -> str:
519
- """Use a template prompt in the chat"""
520
- return template_text
521
-
522
- def clear_chat_history(self) -> List:
523
- """Clear chat history"""
524
- self.chat_history = []
525
- self.mistral_chat.clear_conversation()
526
- return []
527
-
528
- def create_glucose_chart(self) -> Optional[go.Figure]:
529
- """Create an interactive glucose chart using unified data"""
530
- chart_data = self.data_manager.get_chart_data()
531
-
532
- if chart_data is None or chart_data.empty:
533
- return None
534
-
535
- fig = go.Figure()
536
-
537
- # Color code based on glucose ranges
538
- colors = []
539
- for value in chart_data['value']:
540
- if value < 70:
541
- colors.append('#E74C3C') # Red for low
542
- elif value > 180:
543
- colors.append('#F39C12') # Orange for high
544
- else:
545
- colors.append('#27AE60') # Green for in range
546
-
547
- fig.add_trace(go.Scatter(
548
- x=chart_data['systemTime'],
549
- y=chart_data['value'],
550
- mode='lines+markers',
551
- name='Glucose',
552
- line=dict(color='#2E86AB', width=2),
553
- marker=dict(size=4, color=colors),
554
- hovertemplate='<b>%{y} mg/dL</b><br>%{x}<extra></extra>'
555
- ))
556
-
557
- # Add target range shading
558
- fig.add_hrect(
559
- y0=70, y1=180,
560
- fillcolor="rgba(39, 174, 96, 0.1)",
561
- layer="below",
562
- line_width=0,
563
- annotation_text="Target Range",
564
- annotation_position="top left"
565
- )
566
-
567
- # Add reference lines
568
- fig.add_hline(y=70, line_dash="dash", line_color="#E67E22",
569
- annotation_text="Low (70 mg/dL)", annotation_position="right")
570
- fig.add_hline(y=180, line_dash="dash", line_color="#E67E22",
571
- annotation_text="High (180 mg/dL)", annotation_position="right")
572
- fig.add_hline(y=54, line_dash="dot", line_color="#E74C3C",
573
- annotation_text="Severe Low (54 mg/dL)", annotation_position="right")
574
- fig.add_hline(y=250, line_dash="dot", line_color="#E74C3C",
575
- annotation_text="Severe High (250 mg/dL)", annotation_position="right")
576
-
577
- # Get current stats for title
578
- stats = self.data_manager.get_stats_for_ui()
579
- tir = stats.get('time_in_range_70_180', 0)
580
- data_type = "REAL" if self.current_user_type == "real" else "DEMO"
581
-
582
- fig.update_layout(
583
- title={
584
- 'text': f"14-Day Glucose Trends - {self.data_manager.current_user.name} ({data_type} DATA - TIR: {tir:.1f}%)",
585
- 'x': 0.5,
586
- 'xanchor': 'center'
587
- },
588
- xaxis_title="Time",
589
- yaxis_title="Glucose (mg/dL)",
590
- hovermode='x unified',
591
- height=500,
592
- showlegend=False,
593
- plot_bgcolor='rgba(0,0,0,0)',
594
- paper_bgcolor='rgba(0,0,0,0)',
595
- font=dict(size=12),
596
- margin=dict(l=60, r=60, t=80, b=60)
597
- )
598
-
599
- fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(128,128,128,0.2)')
600
- fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(128,128,128,0.2)')
601
-
602
- return fig
603
-
604
-
605
- def create_interface():
606
- """Create the Gradio interface with demo users AND real OAuth"""
607
- app = GlucoBuddyApp()
608
-
609
- custom_css = """
610
- .main-header {
611
- text-align: center;
612
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
613
- color: white;
614
- padding: 2rem;
615
- border-radius: 10px;
616
- margin-bottom: 2rem;
617
- }
618
- .load-data-section {
619
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
620
- border-radius: 15px;
621
- padding: 2rem;
622
- margin: 1.5rem 0;
623
- box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
624
- backdrop-filter: blur(4px);
625
- border: 1px solid rgba(255, 255, 255, 0.18);
626
- }
627
- .prominent-button {
628
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
629
- border: none !important;
630
- border-radius: 15px !important;
631
- padding: 1.5rem 3rem !important;
632
- font-size: 1.2rem !important;
633
- font-weight: bold !important;
634
- color: white !important;
635
- box-shadow: 0 8px 32px rgba(102, 126, 234, 0.4) !important;
636
- transition: all 0.3s ease !important;
637
- min-height: 80px !important;
638
- text-align: center !important;
639
- }
640
- .prominent-button:hover {
641
- transform: translateY(-2px) !important;
642
- box-shadow: 0 12px 40px rgba(102, 126, 234, 0.6) !important;
643
- }
644
- .real-oauth-button {
645
- background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%) !important;
646
- color: white !important;
647
- border: none !important;
648
- font-weight: bold !important;
649
- }
650
- """
651
-
652
- with gr.Blocks(
653
- title="GlycoAI - AI Glucose Insights",
654
- theme=gr.themes.Soft(),
655
- css=custom_css
656
- ) as interface:
657
-
658
- # Header
659
- with gr.Row():
660
- with gr.Column():
661
- gr.HTML("""
662
- <div class="main-header">
663
- <div style="display: flex; align-items: center; justify-content: center; gap: 1rem;">
664
- <div style="width: 60px; height: 60px; background: white; border-radius: 50%; display: flex; align-items: center; justify-content: center;">
665
- <span style="color: #667eea; font-size: 24px; font-weight: bold;">🩺</span>
666
- </div>
667
- <div>
668
- <h1 style="margin: 0; font-size: 2.5rem; color: white;">GlycoAI</h1>
669
- <p style="margin: 0; font-size: 1.2rem; color: white; opacity: 0.9;">AI-Powered Glucose Chatbot</p>
670
- </div>
671
- </div>
672
- <p style="margin-top: 1rem; font-size: 1rem; color: white; opacity: 0.8;">
673
- Connect your Dexcom CGM data OR try demo users and chat with AI for personalized glucose insights
674
- </p>
675
- </div>
676
- """)
677
-
678
- # User Selection Section
679
- with gr.Row():
680
- with gr.Column():
681
- gr.Markdown("### 👥 Choose Your Data Source")
682
- gr.Markdown("Select demo users for instant testing OR authenticate with your real Dexcom account")
683
-
684
- # Demo Users Section
685
- with gr.Group():
686
- gr.Markdown("#### 🎭 Demo Users (Instant Access)")
687
- gr.Markdown("*Realistic demo data for testing GlycoAI's capabilities*")
688
-
689
- with gr.Row():
690
- sarah_btn = gr.Button(
691
- "Sarah Thompson\n(G7 Mobile - Stable Control)",
692
- variant="secondary",
693
- size="lg"
694
- )
695
- marcus_btn = gr.Button(
696
- "Marcus Rodriguez\n(ONE+ Mobile - Type 2)",
697
- variant="secondary",
698
- size="lg"
699
- )
700
- jennifer_btn = gr.Button(
701
- "Jennifer Chen\n(G6 Mobile - Athletic)",
702
- variant="secondary",
703
- size="lg"
704
- )
705
- robert_btn = gr.Button(
706
- "Robert Williams\n(G6 Receiver - Experienced)",
707
- variant="secondary",
708
- size="lg"
709
- )
710
-
711
- # Real OAuth Section (if available)
712
- if REAL_OAUTH_AVAILABLE:
713
- with gr.Group():
714
- gr.Markdown("#### 🔐 Real Dexcom Authentication")
715
- gr.Markdown("*Connect your actual Dexcom account for live glucose data analysis*")
716
-
717
- with gr.Row():
718
- real_oauth_btn = gr.Button(
719
- "🚀 Connect Real Dexcom Account\n(OAuth Authentication)",
720
- variant="primary",
721
- size="lg",
722
- elem_classes=["real-oauth-button"]
723
- )
724
-
725
- oauth_instructions = gr.Markdown(
726
- "Click above to start real Dexcom authentication",
727
- visible=True
728
- )
729
-
730
- with gr.Row():
731
- auth_code_input = gr.Textbox(
732
- label="Authorization Code (from callback URL after 404 error)",
733
- placeholder="ABC123XYZ... (just the code part, not the full URL)",
734
- lines=2,
735
- visible=False
736
- )
737
- complete_oauth_btn = gr.Button(
738
- "✅ Complete OAuth",
739
- variant="primary",
740
- visible=False
741
- )
742
- else:
743
- with gr.Group():
744
- gr.Markdown("#### 🔒 Real Dexcom Authentication (Unavailable)")
745
- gr.Markdown("*Real OAuth is not configured. Please check your setup.*")
746
-
747
- gr.Button(
748
- "🔒 Real OAuth Not Available\n(Check Configuration)",
749
- variant="secondary",
750
- size="lg",
751
- interactive=False
752
- )
753
-
754
- # Create dummy variables for consistency
755
- oauth_instructions = gr.Markdown("Real OAuth not available")
756
- auth_code_input = gr.Textbox(visible=False)
757
- complete_oauth_btn = gr.Button(visible=False)
758
-
759
- # Connection Status
760
- with gr.Row():
761
- connection_status = gr.Textbox(
762
- label="Current User",
763
- value="No user selected",
764
- interactive=False,
765
- container=True
766
- )
767
-
768
- # Section Divider
769
- gr.HTML('<div style="height: 2px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 1px; margin: 2rem 0;"></div>')
770
-
771
- # PROMINENT CENTRALIZED DATA LOADING SECTION
772
- with gr.Group(visible=False) as main_interface:
773
- # PROMINENT LOAD BUTTON - Centered and Large
774
- with gr.Row():
775
- with gr.Column(scale=1):
776
- pass # Left spacer
777
- with gr.Column(scale=2):
778
- load_data_btn = gr.Button(
779
- "🚀 LOAD 14-DAY GLUCOSE DATA\n📈 Start Analysis & Enable AI Chat",
780
- elem_classes=["prominent-button"],
781
- size="lg"
782
- )
783
- with gr.Column(scale=1):
784
- pass # Right spacer
785
-
786
- # Section Divider
787
- gr.HTML('<div style="height: 2px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 1px; margin: 2rem 0;"></div>')
788
-
789
- # Main Content Tabs
790
- with gr.Tabs():
791
-
792
- # Glucose Chart Tab
793
- with gr.TabItem("📈 Glucose Chart"):
794
- with gr.Column():
795
- gr.Markdown("### 📊 Interactive 14-Day Glucose Analysis")
796
- gr.Markdown("*Load your data using the button above to see your comprehensive glucose trends*")
797
-
798
- glucose_chart = gr.Plot(
799
- label="Interactive 14-Day Glucose Trends",
800
- container=True
801
- )
802
-
803
- # Chat Tab
804
- with gr.TabItem("💬 Chat with AI"):
805
- with gr.Column():
806
- gr.Markdown("### 🤖 Chat with GlycoAI about your glucose data")
807
- gr.Markdown("*📊 Load your data using the button above to enable personalized AI insights*")
808
-
809
- # Template Prompts
810
- with gr.Row():
811
- with gr.Column():
812
- gr.Markdown("**💡 Quick Start Templates:**")
813
- with gr.Row():
814
- template1_btn = gr.Button(
815
- "🎯 Analyze My 14-Day Patterns",
816
- variant="secondary",
817
- size="sm"
818
- )
819
- template2_btn = gr.Button(
820
- "⚡ Improve My Control",
821
- variant="secondary",
822
- size="sm"
823
- )
824
- template3_btn = gr.Button(
825
- "🍽️ Meal Management Tips",
826
- variant="secondary",
827
- size="sm"
828
- )
829
-
830
- # Chat Interface
831
- chatbot = gr.Chatbot(
832
- label="💬 Chat with GlycoAI (Demo + Real Data)",
833
- height=500,
834
- show_label=True,
835
- container=True,
836
- bubble_full_width=False,
837
- avatar_images=(None, "🩺")
838
- )
839
-
840
- # Chat Input
841
- with gr.Row():
842
- chat_input = gr.Textbox(
843
- placeholder="Ask me about your glucose patterns, trends, or management strategies...",
844
- label="Your Question",
845
- lines=2,
846
- scale=4
847
- )
848
- send_btn = gr.Button(
849
- "Send 💬",
850
- variant="primary",
851
- scale=1
852
- )
853
-
854
- # Chat Controls
855
- with gr.Row():
856
- clear_chat_btn = gr.Button(
857
- "🗑️ Clear Chat",
858
- variant="secondary",
859
- size="sm"
860
- )
861
- gr.Markdown("*AI responses are for informational purposes only. Always consult your healthcare provider.*")
862
-
863
- # Data Overview Tab
864
- with gr.TabItem("📋 Data Overview"):
865
- with gr.Column():
866
- gr.Markdown("### 📋 Comprehensive Data Analysis")
867
- gr.Markdown("*Load your data using the button above to see detailed glucose statistics*")
868
-
869
- data_display = gr.Markdown("Click 'Load 14-Day Glucose Data' above to see your comprehensive analysis", container=True)
870
-
871
- # Event Handlers
872
- def handle_demo_user_selection(user_key):
873
- status, interface_visibility = app.select_demo_user(user_key)
874
- return status, interface_visibility, []
875
-
876
- def handle_load_data():
877
- overview, chart = app.load_glucose_data()
878
- return overview, chart
879
-
880
- def get_template_prompt(template_type):
881
- templates = app.get_template_prompts()
882
- if template_type == 1:
883
- return templates[0] if templates else "Can you analyze my recent glucose patterns and give me insights?"
884
- elif template_type == 2:
885
- return templates[1] if len(templates) > 1 else "What can I do to improve my diabetes management based on my data?"
886
- else:
887
- return "What are some meal management strategies for better glucose control?"
888
-
889
- def handle_chat_submit(message, history):
890
- return app.chat_with_mistral(message, history)
891
-
892
- def handle_enter_key(message, history):
893
- if message.strip():
894
- return app.chat_with_mistral(message, history)
895
- return "", history
896
-
897
- # Connect Event Handlers for Demo Users
898
- sarah_btn.click(
899
- lambda: handle_demo_user_selection("sarah_g7"),
900
- outputs=[connection_status, main_interface, chatbot]
901
- )
902
-
903
- marcus_btn.click(
904
- lambda: handle_demo_user_selection("marcus_one"),
905
- outputs=[connection_status, main_interface, chatbot]
906
- )
907
-
908
- jennifer_btn.click(
909
- lambda: handle_demo_user_selection("jennifer_g6"),
910
- outputs=[connection_status, main_interface, chatbot]
911
- )
912
-
913
- robert_btn.click(
914
- lambda: handle_demo_user_selection("robert_receiver"),
915
- outputs=[connection_status, main_interface, chatbot]
916
- )
917
-
918
- # Connect Event Handlers for Real OAuth (if available)
919
- if REAL_OAUTH_AVAILABLE:
920
- real_oauth_btn.click(
921
- app.start_real_oauth,
922
- outputs=[oauth_instructions]
923
- ).then(
924
- lambda: (gr.update(visible=True), gr.update(visible=True)),
925
- outputs=[auth_code_input, complete_oauth_btn]
926
- )
927
-
928
- complete_oauth_btn.click(
929
- app.complete_real_oauth,
930
- inputs=[auth_code_input],
931
- outputs=[connection_status, main_interface]
932
- ).then(
933
- lambda: [], # Clear chatbot
934
- outputs=[chatbot]
935
- )
936
-
937
- # PROMINENT DATA LOADING - Single button updates all views
938
- load_data_btn.click(
939
- handle_load_data,
940
- outputs=[data_display, glucose_chart]
941
- )
942
-
943
- # Chat Handlers
944
- send_btn.click(
945
- handle_chat_submit,
946
- inputs=[chat_input, chatbot],
947
- outputs=[chat_input, chatbot]
948
- )
949
-
950
- chat_input.submit(
951
- handle_enter_key,
952
- inputs=[chat_input, chatbot],
953
- outputs=[chat_input, chatbot]
954
- )
955
-
956
- # Template Button Handlers
957
- template1_btn.click(
958
- lambda: get_template_prompt(1),
959
- outputs=[chat_input]
960
- )
961
-
962
- template2_btn.click(
963
- lambda: get_template_prompt(2),
964
- outputs=[chat_input]
965
- )
966
-
967
- template3_btn.click(
968
- lambda: get_template_prompt(3),
969
- outputs=[chat_input]
970
- )
971
-
972
- # Clear Chat
973
- clear_chat_btn.click(
974
- app.clear_chat_history,
975
- outputs=[chatbot]
976
- )
977
-
978
- # Footer
979
- with gr.Row():
980
- gr.HTML(f"""
981
- <div style="text-align: center; padding: 2rem; margin-top: 2rem; border-top: 1px solid #dee2e6; color: #6c757d;">
982
- <p><strong>⚠️ Important Medical Disclaimer</strong></p>
983
- <p>GlycoAI is for informational and educational purposes only. Always consult your healthcare provider
984
- before making any changes to your diabetes management plan. This tool does not replace professional medical advice.</p>
985
- <p style="margin-top: 1rem; font-size: 0.9rem;">
986
- 🔒 Your data is processed securely and not stored permanently.
987
- 💡 Powered by Dexcom API integration and Mistral AI.<br>
988
- {"🎭 Demo data available instantly • 🔐 Real OAuth: " + ("Available" if REAL_OAUTH_AVAILABLE else "Not configured")}
989
- </p>
990
- </div>
991
- """)
992
-
993
- return interface
994
-
995
-
996
- def main():
997
- """Main function to launch the application"""
998
- print("🚀 Starting GlycoAI - AI-Powered Glucose Insights (Demo + Real OAuth)...")
999
-
1000
- # Check OAuth availability
1001
- oauth_status = "✅ Available" if REAL_OAUTH_AVAILABLE else "❌ Not configured"
1002
- print(f"🔐 Real Dexcom OAuth: {oauth_status}")
1003
-
1004
- # Validate environment before starting
1005
- print("🔍 Validating environment configuration...")
1006
- if not validate_environment():
1007
- print("❌ Environment validation failed!")
1008
- print("Please check your .env file or environment variables.")
1009
- return
1010
-
1011
- print("✅ Environment validation passed!")
1012
-
1013
- try:
1014
- # Create and launch the interface
1015
- demo = create_interface()
1016
-
1017
- print("🎯 GlycoAI is starting with enhanced features...")
1018
- print("📊 Features: Demo users + Real OAuth, unified data management, consistent metrics")
1019
- print("🎭 Demo users: 4 realistic profiles for instant testing")
1020
- if REAL_OAUTH_AVAILABLE:
1021
- print("🔐 Real OAuth: Available - connect your actual Dexcom account")
1022
- else:
1023
- print("🔒 Real OAuth: Not configured - demo users only")
1024
-
1025
- # Launch with custom settings
1026
- demo.launch(
1027
- server_name="0.0.0.0", # Allow external access
1028
- server_port=7860, # Your port
1029
- share=True, # Set to True for public sharing (tunneling)
1030
- debug=os.getenv("DEBUG", "false").lower() == "true",
1031
- show_error=True, # Show errors in the interface
1032
- auth=None, # No authentication required
1033
- favicon_path=None, # Use default favicon
1034
- ssl_verify=False # Disable SSL verification for development
1035
- )
1036
-
1037
- except Exception as e:
1038
- logger.error(f"Failed to launch GlycoAI application: {e}")
1039
- print(f"❌ Error launching application: {e}")
1040
-
1041
- # Provide helpful error information
1042
- if "environment" in str(e).lower():
1043
- print("\n💡 Environment troubleshooting:")
1044
- print("1. Check if .env file exists with MISTRAL_API_KEY")
1045
- print("2. Verify your API key is valid")
1046
- print("3. For Hugging Face Spaces, check Repository secrets")
1047
- else:
1048
- print("\n💡 Try checking:")
1049
- print("1. All dependencies are installed: pip install -r requirements.txt")
1050
- print("2. Port 7860 is available")
1051
- print("3. Check the logs above for specific error details")
1052
-
1053
- raise
1054
-
1055
-
1056
- if __name__ == "__main__":
1057
- # Setup logging configuration
1058
- log_level = os.getenv("LOG_LEVEL", "INFO")
1059
- logging.basicConfig(
1060
- level=getattr(logging, log_level.upper()),
1061
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
1062
- handlers=[
1063
- logging.FileHandler('glycoai.log'),
1064
- logging.StreamHandler()
1065
- ]
1066
- )
1067
-
1068
- # Run the main application
1069
- main()