Valentina9502 commited on
Commit
d4f154f
Β·
1 Parent(s): 232e999
Files changed (5) hide show
  1. .gradio/certificate.pem +31 -0
  2. dexcom_real_auth_system.py +237 -330
  3. hybrid_auth.py +133 -129
  4. main.py +397 -107
  5. mistral_chat.py +269 -213
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5
+ WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6
+ ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7
+ MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8
+ h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9
+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10
+ A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11
+ T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12
+ B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13
+ B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14
+ KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15
+ OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16
+ jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17
+ qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18
+ rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20
+ hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21
+ ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22
+ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23
+ NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24
+ ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25
+ TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26
+ jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27
+ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28
+ 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29
+ mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30
+ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31
+ -----END CERTIFICATE-----
dexcom_real_auth_system.py CHANGED
@@ -1,147 +1,74 @@
1
  #!/usr/bin/env python3
2
  """
3
- Dexcom Real Authentication System
4
- Uses your actual CLIENT_ID and CLIENT_SECRET from developer.dexcom.com
 
5
  """
6
 
 
7
  import requests
 
8
  import json
9
- import base64
10
  import secrets
11
- import hashlib
12
- import urllib.parse
13
  import webbrowser
14
- import threading
15
- import time
16
  from datetime import datetime, timedelta
17
- from typing import Dict, List, Optional, Tuple
18
- from http.server import HTTPServer, BaseHTTPRequestHandler
19
  import logging
20
 
21
- # Set up logging
22
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
  logger = logging.getLogger(__name__)
24
 
25
- # πŸ”‘ YOUR REAL DEXCOM CREDENTIALS (replace with your actual values)
26
- CLIENT_ID = "YOUR_REAL_CLIENT_ID_HERE" # Replace with your actual client ID
27
- CLIENT_SECRET = "YOUR_REAL_CLIENT_SECRET_HERE" # Replace with your actual client secret
28
- REDIRECT_URI = "http://localhost:8080/callback"
29
 
30
- # Dexcom API URLs
31
- SANDBOX_BASE_URL = "https://sandbox-api.dexcom.com"
32
- PRODUCTION_BASE_URL = "https://api.dexcom.com"
33
-
34
- class OAuth2CallbackHandler(BaseHTTPRequestHandler):
35
- """HTTP handler for OAuth2 callback"""
36
-
37
- def do_GET(self):
38
- """Handle GET request for OAuth callback"""
39
- if self.path.startswith('/callback'):
40
- # Parse the authorization code from the URL
41
- parsed_url = urllib.parse.urlparse(self.path)
42
- query_params = urllib.parse.parse_qs(parsed_url.query)
43
-
44
- if 'code' in query_params:
45
- # Store the authorization code in the server
46
- self.server.auth_code = query_params['code'][0]
47
- self.server.auth_error = None
48
-
49
- # Send success response
50
- self.send_response(200)
51
- self.send_header('Content-type', 'text/html')
52
- self.end_headers()
53
-
54
- success_html = """
55
- <html>
56
- <head><title>Dexcom Authorization Successful</title></head>
57
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
58
- <h2 style="color: green;">βœ… Authorization Successful!</h2>
59
- <p>You have successfully authorized the application to access your Dexcom data.</p>
60
- <p>You can close this window and return to the application.</p>
61
- <script>
62
- setTimeout(function(){
63
- window.close();
64
- }, 3000);
65
- </script>
66
- </body>
67
- </html>
68
- """
69
- self.wfile.write(success_html.encode())
70
-
71
- elif 'error' in query_params:
72
- # Handle authorization error
73
- error = query_params.get('error', ['Unknown error'])[0]
74
- error_description = query_params.get('error_description', [''])[0]
75
-
76
- self.server.auth_code = None
77
- self.server.auth_error = f"{error}: {error_description}"
78
-
79
- self.send_response(400)
80
- self.send_header('Content-type', 'text/html')
81
- self.end_headers()
82
-
83
- error_html = f"""
84
- <html>
85
- <head><title>Dexcom Authorization Failed</title></head>
86
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
87
- <h2 style="color: red;">❌ Authorization Failed</h2>
88
- <p><strong>Error:</strong> {error}</p>
89
- <p><strong>Description:</strong> {error_description}</p>
90
- <p>Please try again or contact support if the problem persists.</p>
91
- </body>
92
- </html>
93
- """
94
- self.wfile.write(error_html.encode())
95
- else:
96
- # Unexpected callback
97
- self.send_response(400)
98
- self.send_header('Content-type', 'text/html')
99
- self.end_headers()
100
- self.wfile.write(b"<html><body><h2>Invalid callback</h2></body></html>")
101
- else:
102
- # 404 for other paths
103
- self.send_response(404)
104
- self.end_headers()
105
-
106
- def log_message(self, format, *args):
107
- """Suppress default logging"""
108
- pass
109
 
110
  class DexcomRealAPI:
111
- """Real Dexcom API client using your actual credentials"""
 
112
 
113
- def __init__(self, client_id: str = CLIENT_ID, client_secret: str = CLIENT_SECRET,
114
- environment: str = "sandbox"):
115
- """
116
- Initialize Dexcom API client
117
-
118
- Args:
119
- client_id: Your real Dexcom client ID
120
- client_secret: Your real Dexcom client secret
121
- environment: "sandbox" or "production"
122
- """
123
- self.client_id = client_id
124
- self.client_secret = client_secret
125
  self.redirect_uri = REDIRECT_URI
 
126
 
 
127
  if environment == "sandbox":
128
- self.base_url = SANDBOX_BASE_URL
 
 
129
  else:
130
- self.base_url = PRODUCTION_BASE_URL
 
 
131
 
132
- self.environment = environment
133
  self.access_token = None
134
  self.refresh_token = None
135
  self.token_expires_at = None
136
 
137
- # Validate credentials
138
- if not client_id or client_id == "YOUR_REAL_CLIENT_ID_HERE":
139
- raise ValueError("Please set your real CLIENT_ID")
140
- if not client_secret or client_secret == "YOUR_REAL_CLIENT_SECRET_HERE":
141
- raise ValueError("Please set your real CLIENT_SECRET")
142
 
143
  def generate_auth_url(self, state: str = None) -> str:
144
- """Generate OAuth authorization URL"""
145
  if not state:
146
  state = secrets.token_urlsafe(32)
147
 
@@ -154,91 +81,91 @@ class DexcomRealAPI:
154
  }
155
 
156
  query_string = urllib.parse.urlencode(params)
157
- auth_url = f"{self.base_url}/v2/oauth2/login?{query_string}"
158
 
159
- logger.info(f"Generated authorization URL for {self.environment} environment")
 
 
 
 
 
 
160
  return auth_url
161
 
162
  def start_oauth_flow(self) -> bool:
163
- """Start the complete OAuth flow with browser"""
164
- print(f"\nπŸ” Starting Dexcom OAuth Authentication")
165
- print(f"🌐 Environment: {self.environment}")
166
- print(f"πŸ”‘ Client ID: {self.client_id[:8]}...")
 
 
167
 
168
  try:
169
- # Generate authorization URL
170
- state = secrets.token_urlsafe(32)
171
- auth_url = self.generate_auth_url(state)
172
 
173
- # Start local callback server
174
- server = HTTPServer(('localhost', 8080), OAuth2CallbackHandler)
175
- server.timeout = 120 # 2 minute timeout
176
- server.auth_code = None
177
- server.auth_error = None
 
 
 
178
 
179
- print(f"🌐 Opening browser for authorization...")
180
- print(f"πŸ“‹ URL: {auth_url}")
181
- print(f"⏳ Waiting for authorization (timeout: 2 minutes)...")
182
-
183
- # Open browser
184
  webbrowser.open(auth_url)
 
 
185
 
186
- # Wait for callback
187
- start_time = time.time()
188
- while time.time() - start_time < 120: # 2 minute timeout
189
- server.handle_request()
190
- if server.auth_code or server.auth_error:
191
- break
192
-
193
- if server.auth_error:
194
- print(f"❌ Authorization failed: {server.auth_error}")
195
- return False
196
-
197
- if not server.auth_code:
198
- print(f"❌ Authorization timeout - no response received")
199
- return False
200
-
201
- print(f"βœ… Authorization code received!")
202
-
203
- # Exchange code for tokens
204
- success = self.exchange_code_for_tokens(server.auth_code)
205
-
206
- if success:
207
- print(f"πŸŽ‰ Authentication successful!")
208
- print(f"πŸ“Š Access token obtained")
209
- print(f"⏰ Token expires: {self.token_expires_at}")
210
- return True
211
- else:
212
- print(f"❌ Token exchange failed")
213
- return False
214
-
215
  except Exception as e:
216
- logger.error(f"OAuth flow error: {e}")
217
- print(f"❌ OAuth flow error: {e}")
218
  return False
219
 
220
  def exchange_code_for_tokens(self, authorization_code: str) -> bool:
221
- """Exchange authorization code for access and refresh tokens"""
222
- url = f"{self.base_url}/v2/oauth2/token"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
 
 
 
 
 
 
 
224
  data = {
225
  'client_id': self.client_id,
226
  'client_secret': self.client_secret,
227
- 'code': authorization_code,
228
  'grant_type': 'authorization_code',
229
  'redirect_uri': self.redirect_uri
230
  }
231
 
 
232
  headers = {
233
  'Content-Type': 'application/x-www-form-urlencoded',
234
- 'Accept': 'application/json'
 
235
  }
236
 
237
  try:
238
- logger.info("Exchanging authorization code for tokens...")
239
- response = requests.post(url, data=data, headers=headers)
 
240
 
241
- logger.info(f"Token exchange response: {response.status_code}")
 
 
242
 
243
  if response.status_code == 200:
244
  token_data = response.json()
@@ -246,26 +173,89 @@ class DexcomRealAPI:
246
  self.access_token = token_data.get('access_token')
247
  self.refresh_token = token_data.get('refresh_token')
248
 
249
- expires_in = token_data.get('expires_in', 3600)
250
  self.token_expires_at = datetime.now() + timedelta(seconds=expires_in)
251
 
252
- logger.info("Successfully obtained access and refresh tokens")
 
 
 
 
 
 
253
  return True
254
  else:
255
- logger.error(f"Token exchange failed: {response.status_code} - {response.text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  return False
257
 
258
  except requests.exceptions.RequestException as e:
 
259
  logger.error(f"Network error during token exchange: {e}")
260
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
  def refresh_access_token(self) -> bool:
263
- """Refresh the access token using refresh token"""
264
  if not self.refresh_token:
265
  logger.error("No refresh token available")
266
  return False
267
 
268
- url = f"{self.base_url}/v2/oauth2/token"
269
 
270
  data = {
271
  'client_id': self.client_id,
@@ -280,82 +270,72 @@ class DexcomRealAPI:
280
  }
281
 
282
  try:
283
- response = requests.post(url, data=data, headers=headers)
284
 
285
  if response.status_code == 200:
286
  token_data = response.json()
287
 
288
  self.access_token = token_data.get('access_token')
289
- # Note: Some OAuth providers issue new refresh tokens
290
- if 'refresh_token' in token_data:
291
- self.refresh_token = token_data.get('refresh_token')
292
 
293
- expires_in = token_data.get('expires_in', 3600)
294
  self.token_expires_at = datetime.now() + timedelta(seconds=expires_in)
295
 
296
- logger.info("Access token refreshed successfully")
297
  return True
298
  else:
299
  logger.error(f"Token refresh failed: {response.status_code} - {response.text}")
300
  return False
301
 
302
- except requests.exceptions.RequestException as e:
303
- logger.error(f"Network error during token refresh: {e}")
304
  return False
305
 
306
- def is_token_valid(self) -> bool:
307
- """Check if current access token is valid and not expired"""
308
  if not self.access_token:
309
- return False
310
 
311
- if not self.token_expires_at:
312
- return True # Assume valid if no expiry set
313
-
314
- # Consider token expired if it expires within next 5 minutes
315
- return datetime.now() < (self.token_expires_at - timedelta(minutes=5))
316
-
317
- def ensure_valid_token(self) -> bool:
318
- """Ensure we have a valid access token, refresh if needed"""
319
- if not self.is_token_valid():
320
- logger.info("Token expired or invalid, attempting refresh...")
321
- if self.refresh_token:
322
- return self.refresh_access_token()
323
- else:
324
- logger.error("No refresh token available, re-authentication required")
325
- return False
326
- return True
327
 
328
  def get_auth_headers(self) -> Dict[str, str]:
329
- """Get headers with valid authorization token"""
330
- if not self.ensure_valid_token():
331
- raise Exception("No valid access token available. Please authenticate first.")
332
 
333
  return {
334
  'Authorization': f'Bearer {self.access_token}',
335
- 'Accept': 'application/json'
 
336
  }
337
 
338
  def get_data_range(self) -> Dict:
339
- """Get available data range for authenticated user"""
340
- url = f"{self.base_url}/v2/users/self/dataRange"
341
  headers = self.get_auth_headers()
342
 
343
  try:
344
  response = requests.get(url, headers=headers)
345
 
346
  if response.status_code == 200:
347
- return response.json()
 
 
348
  else:
349
  logger.error(f"Data range API error: {response.status_code} - {response.text}")
350
  raise Exception(f"Data range API error: {response.status_code}")
351
 
352
  except requests.exceptions.RequestException as e:
353
  logger.error(f"Network error getting data range: {e}")
354
- raise Exception(f"Network error getting data range: {e}")
355
 
356
  def get_egv_data(self, start_date: str = None, end_date: str = None) -> List[Dict]:
357
- """Get Estimated Glucose Values (EGV) data"""
358
- url = f"{self.base_url}/v2/users/self/egvs"
359
  headers = self.get_auth_headers()
360
 
361
  params = {}
@@ -369,133 +349,60 @@ class DexcomRealAPI:
369
 
370
  if response.status_code == 200:
371
  data = response.json()
372
- return data.get('egvs', [])
 
 
373
  else:
374
  logger.error(f"EGV API error: {response.status_code} - {response.text}")
375
  raise Exception(f"EGV API error: {response.status_code}")
376
 
377
  except requests.exceptions.RequestException as e:
378
  logger.error(f"Network error getting EGV data: {e}")
379
- raise Exception(f"Network error getting EGV data: {e}")
380
-
381
- def get_events_data(self, start_date: str = None, end_date: str = None) -> List[Dict]:
382
- """Get events data (meals, insulin, etc.)"""
383
- url = f"{self.base_url}/v2/users/self/events"
384
- headers = self.get_auth_headers()
385
-
386
- params = {}
387
- if start_date:
388
- params['startDate'] = start_date
389
- if end_date:
390
- params['endDate'] = end_date
391
-
392
- try:
393
- response = requests.get(url, headers=headers)
394
-
395
- if response.status_code == 200:
396
- data = response.json()
397
- return data.get('events', [])
398
- else:
399
- logger.error(f"Events API error: {response.status_code} - {response.text}")
400
- raise Exception(f"Events API error: {response.status_code}")
401
-
402
- except requests.exceptions.RequestException as e:
403
- logger.error(f"Network error getting events data: {e}")
404
- raise Exception(f"Network error getting events data: {e}")
405
 
406
- def test_real_dexcom_api():
407
- """Test the real Dexcom API with your credentials"""
408
- print("πŸ§ͺ Testing Real Dexcom API Integration")
 
409
  print("=" * 60)
410
 
411
- try:
412
- # Initialize API with your real credentials
413
- api = DexcomRealAPI(environment="sandbox")
414
-
415
- # Start OAuth authentication
416
- print("\nπŸ” Step 1: Authentication")
417
- auth_success = api.start_oauth_flow()
418
-
419
- if not auth_success:
420
- print("❌ Authentication failed - cannot proceed")
421
- return False
422
-
423
- # Test data range
424
- print("\nπŸ“… Step 2: Getting Data Range")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  try:
426
  data_range = api.get_data_range()
427
- print(f"βœ… Data range retrieved:")
428
- print(f" EGV: {data_range.get('egvStart', 'N/A')} to {data_range.get('egvEnd', 'N/A')}")
429
- print(f" Events: {data_range.get('eventStart', 'N/A')} to {data_range.get('eventEnd', 'N/A')}")
430
  except Exception as e:
431
- print(f"❌ Data range error: {e}")
432
-
433
- # Test glucose data
434
- print("\nπŸ“Š Step 3: Getting Glucose Data")
435
- try:
436
- # Get last 24 hours
437
- end_time = datetime.now()
438
- start_time = end_time - timedelta(hours=24)
439
-
440
- egv_data = api.get_egv_data(
441
- start_date=start_time.isoformat(),
442
- end_date=end_time.isoformat()
443
- )
444
-
445
- print(f"βœ… Retrieved {len(egv_data)} glucose readings")
446
-
447
- if egv_data:
448
- latest = egv_data[-1]
449
- print(f" Latest: {latest['value']} mg/dL at {latest['displayTime']}")
450
- print(f" Trend: {latest.get('trend', 'N/A')}")
451
- except Exception as e:
452
- print(f"❌ Glucose data error: {e}")
453
-
454
- # Test events data
455
- print("\n🍽️ Step 4: Getting Events Data")
456
- try:
457
- events_data = api.get_events_data(
458
- start_date=start_time.isoformat(),
459
- end_date=end_time.isoformat()
460
- )
461
-
462
- print(f"βœ… Retrieved {len(events_data)} events")
463
-
464
- if events_data:
465
- carb_events = [e for e in events_data if e.get('eventType') == 'carbs']
466
- insulin_events = [e for e in events_data if e.get('eventType') == 'insulin']
467
- print(f" Carb events: {len(carb_events)}")
468
- print(f" Insulin events: {len(insulin_events)}")
469
- except Exception as e:
470
- print(f"❌ Events data error: {e}")
471
-
472
- print(f"\nπŸŽ‰ Real Dexcom API integration completed!")
473
- return True
474
-
475
- except ValueError as e:
476
- print(f"❌ Configuration error: {e}")
477
- print(f"πŸ’‘ Please update CLIENT_ID and CLIENT_SECRET with your real credentials")
478
- return False
479
- except Exception as e:
480
- print(f"❌ Unexpected error: {e}")
481
- return False
482
 
483
  if __name__ == "__main__":
484
- print("πŸ”‘ Dexcom Real API Authentication System")
485
- print("πŸ“‹ Make sure to update CLIENT_ID and CLIENT_SECRET with your real values!")
486
- print()
487
-
488
- # Run the test
489
- test_real_dexcom_api()
490
-
491
- print(f"\nπŸ’‘ Usage Example:")
492
- print(f" api = DexcomRealAPI(environment='sandbox')")
493
- print(f" api.start_oauth_flow() # Opens browser for auth")
494
- print(f" glucose_data = api.get_egv_data()")
495
- print(f" events_data = api.get_events_data()")
496
-
497
- print(f"\n⚠️ Important Notes:")
498
- print(f" β€’ This uses the real Dexcom sandbox environment")
499
- print(f" β€’ You'll need to authenticate through the browser")
500
- print(f" β€’ Sandbox users are provided by Dexcom (like SandboxUser7)")
501
- print(f" β€’ Update environment='production' for real user data")
 
1
  #!/usr/bin/env python3
2
  """
3
+ MIXED ENDPOINTS FIX for Dexcom OAuth
4
+ Login endpoint: developer.dexcom.com/sandbox-login (WORKING - you got the code!)
5
+ Token endpoint: sandbox-api.dexcom.com/v2/oauth2/token (FROM OFFICIAL DOCS)
6
  """
7
 
8
+ import os
9
  import requests
10
+ import urllib.parse
11
  import json
 
12
  import secrets
 
 
13
  import webbrowser
 
 
14
  from datetime import datetime, timedelta
15
+ from typing import Dict, List, Optional
 
16
  import logging
17
 
18
+ # Setup logging
19
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
20
  logger = logging.getLogger(__name__)
21
 
22
+ # Credentials
23
+ CLIENT_ID = os.getenv("DEXCOM_CLIENT_ID", "mLElKHKRwRDVUrAOPBzktFGY7qkTc7Zm")
24
+ CLIENT_SECRET = os.getenv("DEXCOM_CLIENT_SECRET", "HmFpgyVweuwKrQpf")
25
+ REDIRECT_URI = "http://localhost:7860/callback"
26
 
27
+ # MIXED ENDPOINTS CONFIGURATION
28
+ # Login: Use what worked for you
29
+ # Token: Use official documentation
30
+ SANDBOX_LOGIN_BASE = "https://developer.dexcom.com" # βœ… WORKED for login
31
+ SANDBOX_TOKEN_BASE = "https://sandbox-api.dexcom.com" # βœ… OFFICIAL for token
32
+ PRODUCTION_BASE = "https://api.dexcom.com" # βœ… OFFICIAL for production
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  class DexcomRealAPI:
35
+ """
36
+ MIXED ENDPOINTS Dexcom API
37
 
38
+ SOLUTION:
39
+ βœ… Login: developer.dexcom.com/sandbox-login (YOU CONFIRMED THIS WORKS)
40
+ βœ… Token: sandbox-api.dexcom.com/v2/oauth2/token (OFFICIAL DOCS)
41
+ βœ… API: sandbox-api.dexcom.com/v2/... (OFFICIAL DOCS)
42
+ """
43
+
44
+ def __init__(self, client_id: str = None, client_secret: str = None, environment: str = "sandbox"):
45
+ self.client_id = client_id or CLIENT_ID
46
+ self.client_secret = client_secret or CLIENT_SECRET
 
 
 
47
  self.redirect_uri = REDIRECT_URI
48
+ self.environment = environment
49
 
50
+ # MIXED ENDPOINT CONFIGURATION
51
  if environment == "sandbox":
52
+ self.login_base = SANDBOX_LOGIN_BASE # https://developer.dexcom.com
53
+ self.token_base = SANDBOX_TOKEN_BASE # https://sandbox-api.dexcom.com
54
+ self.api_base = SANDBOX_TOKEN_BASE # https://sandbox-api.dexcom.com
55
  else:
56
+ self.login_base = PRODUCTION_BASE # https://api.dexcom.com
57
+ self.token_base = PRODUCTION_BASE # https://api.dexcom.com
58
+ self.api_base = PRODUCTION_BASE # https://api.dexcom.com
59
 
 
60
  self.access_token = None
61
  self.refresh_token = None
62
  self.token_expires_at = None
63
 
64
+ logger.info(f"βœ… MIXED ENDPOINTS Dexcom API initialized")
65
+ logger.info(f" Environment: {environment}")
66
+ logger.info(f" Login base: {self.login_base}")
67
+ logger.info(f" Token base: {self.token_base}")
68
+ logger.info(f" API base: {self.api_base}")
69
 
70
  def generate_auth_url(self, state: str = None) -> str:
71
+ """Generate OAuth URL using the login endpoint that WORKED for you"""
72
  if not state:
73
  state = secrets.token_urlsafe(32)
74
 
 
81
  }
82
 
83
  query_string = urllib.parse.urlencode(params)
 
84
 
85
+ # Use the login endpoint that worked for you
86
+ if self.environment == "sandbox":
87
+ auth_url = f"{self.login_base}/sandbox-login?{query_string}"
88
+ else:
89
+ auth_url = f"{self.login_base}/v2/oauth2/login?{query_string}"
90
+
91
+ logger.info(f"βœ… Auth URL (proven working): {auth_url}")
92
  return auth_url
93
 
94
  def start_oauth_flow(self) -> bool:
95
+ """Start OAuth with proven working login endpoint"""
96
+ print(f"\nπŸ” MIXED ENDPOINTS DEXCOM OAUTH")
97
+ print("=" * 60)
98
+ print(f"Environment: {self.environment}")
99
+ print(f"Login endpoint: {self.login_base} ← PROVEN TO WORK FOR YOU")
100
+ print(f"Token endpoint: {self.token_base} ← FROM OFFICIAL DOCS")
101
 
102
  try:
103
+ auth_url = self.generate_auth_url()
 
 
104
 
105
+ print(f"\nπŸ“‹ OAUTH INSTRUCTIONS:")
106
+ print(f"1. 🌐 Opening: {auth_url}")
107
+ print(f"2. πŸ“ Log in (this endpoint worked before!)")
108
+ if self.environment == "sandbox":
109
+ print(f" Sandbox: sandboxuser1@dexcom.com / Dexcom123!")
110
+ print(f"3. βœ… Authorize application")
111
+ print(f"4. πŸ“‹ Copy authorization code from callback")
112
+ print(f"5. ⚑ Token exchange with CORRECTED endpoint")
113
 
 
 
 
 
 
114
  webbrowser.open(auth_url)
115
+ print(f"\nβœ… Browser opened to WORKING login endpoint")
116
+ return True
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  except Exception as e:
119
+ logger.error(f"OAuth start error: {e}")
 
120
  return False
121
 
122
  def exchange_code_for_tokens(self, authorization_code: str) -> bool:
123
+ """
124
+ Exchange code for tokens using CORRECTED token endpoint
125
+
126
+ KEY FIX: Use sandbox-api.dexcom.com for token exchange
127
+ NOT developer.dexcom.com
128
+ """
129
+
130
+ # Clean authorization code
131
+ auth_code = self._extract_auth_code(authorization_code.strip())
132
+
133
+ if not auth_code:
134
+ logger.error("No valid authorization code provided")
135
+ return False
136
+
137
+ # CORRECTED TOKEN ENDPOINT (from official docs)
138
+ token_url = f"{self.token_base}/v2/oauth2/token"
139
 
140
+ logger.info(f"πŸ”„ TOKEN EXCHANGE WITH CORRECTED ENDPOINT")
141
+ logger.info(f" Login was: {self.login_base} (worked!)")
142
+ logger.info(f" Token is: {token_url} ← CORRECTED!")
143
+ logger.info(f" Auth code: {auth_code[:15]}...{auth_code[-8:]}")
144
+
145
+ # Request data
146
  data = {
147
  'client_id': self.client_id,
148
  'client_secret': self.client_secret,
149
+ 'code': auth_code,
150
  'grant_type': 'authorization_code',
151
  'redirect_uri': self.redirect_uri
152
  }
153
 
154
+ # Headers
155
  headers = {
156
  'Content-Type': 'application/x-www-form-urlencoded',
157
+ 'Accept': 'application/json',
158
+ 'User-Agent': 'GlycoAI/1.0'
159
  }
160
 
161
  try:
162
+ print(f"\nπŸ“‘ Making token request to CORRECTED endpoint...")
163
+ print(f" URL: {token_url}")
164
+ print(f" Data: client_id, client_secret, code, grant_type, redirect_uri")
165
 
166
+ response = requests.post(token_url, data=data, headers=headers, timeout=30)
167
+
168
+ print(f"πŸ“¨ Response status: {response.status_code}")
169
 
170
  if response.status_code == 200:
171
  token_data = response.json()
 
173
  self.access_token = token_data.get('access_token')
174
  self.refresh_token = token_data.get('refresh_token')
175
 
176
+ expires_in = token_data.get('expires_in', 7200)
177
  self.token_expires_at = datetime.now() + timedelta(seconds=expires_in)
178
 
179
+ print(f"βœ… TOKEN EXCHANGE SUCCESSFUL!")
180
+ print(f" Access token: {self.access_token[:25]}...")
181
+ print(f" Refresh token: {self.refresh_token[:25] if self.refresh_token else 'None'}...")
182
+ print(f" Expires in: {expires_in} seconds ({expires_in/3600:.1f} hours)")
183
+ print(f" Token type: {token_data.get('token_type', 'Bearer')}")
184
+
185
+ logger.info("βœ… TOKEN EXCHANGE SUCCESS WITH MIXED ENDPOINTS!")
186
  return True
187
  else:
188
+ print(f"❌ Token exchange failed: {response.status_code}")
189
+
190
+ try:
191
+ error_data = response.json()
192
+ print(f"πŸ“‹ Error details:")
193
+ for key, value in error_data.items():
194
+ print(f" {key}: {value}")
195
+
196
+ # Specific guidance
197
+ if response.status_code == 400:
198
+ error_type = error_data.get('error', 'unknown')
199
+ if error_type == 'invalid_grant':
200
+ print(f"\nπŸ’‘ SOLUTION: Authorization code expired or invalid")
201
+ print(f" β†’ Get a fresh code (expires in 1 minute!)")
202
+ elif error_type == 'invalid_client':
203
+ print(f"\nπŸ’‘ SOLUTION: Check credentials")
204
+ print(f" β†’ Verify CLIENT_ID and CLIENT_SECRET")
205
+ elif error_type == 'invalid_request':
206
+ print(f"\nπŸ’‘ SOLUTION: Check redirect URI")
207
+ print(f" β†’ Must match: {self.redirect_uri}")
208
+
209
+ except:
210
+ print(f"πŸ“‹ Raw error response: {response.text}")
211
+
212
  return False
213
 
214
  except requests.exceptions.RequestException as e:
215
+ print(f"❌ Network error: {e}")
216
  logger.error(f"Network error during token exchange: {e}")
217
  return False
218
+ except Exception as e:
219
+ print(f"❌ Unexpected error: {e}")
220
+ logger.error(f"Unexpected error during token exchange: {e}")
221
+ return False
222
+
223
+ def _extract_auth_code(self, input_text: str) -> str:
224
+ """Extract authorization code from various formats"""
225
+ try:
226
+ if input_text.startswith('http'):
227
+ # Full callback URL
228
+ parsed_url = urllib.parse.urlparse(input_text)
229
+ query_params = urllib.parse.parse_qs(parsed_url.query)
230
+
231
+ if 'code' in query_params:
232
+ code = query_params['code'][0]
233
+ logger.info(f"Extracted code from URL: {code[:15]}...")
234
+ return code
235
+ else:
236
+ logger.error("No 'code' parameter in callback URL")
237
+ return ""
238
+ else:
239
+ # Direct code input
240
+ if input_text.startswith('code='):
241
+ code = input_text[5:]
242
+ else:
243
+ code = input_text.strip()
244
+
245
+ logger.info(f"Using direct code input: {code[:15]}...")
246
+ return code
247
+
248
+ except Exception as e:
249
+ logger.error(f"Error extracting auth code: {e}")
250
+ return ""
251
 
252
  def refresh_access_token(self) -> bool:
253
+ """Refresh token using corrected endpoint"""
254
  if not self.refresh_token:
255
  logger.error("No refresh token available")
256
  return False
257
 
258
+ token_url = f"{self.token_base}/v2/oauth2/token"
259
 
260
  data = {
261
  'client_id': self.client_id,
 
270
  }
271
 
272
  try:
273
+ response = requests.post(token_url, data=data, headers=headers)
274
 
275
  if response.status_code == 200:
276
  token_data = response.json()
277
 
278
  self.access_token = token_data.get('access_token')
279
+ new_refresh_token = token_data.get('refresh_token')
280
+ if new_refresh_token:
281
+ self.refresh_token = new_refresh_token
282
 
283
+ expires_in = token_data.get('expires_in', 7200)
284
  self.token_expires_at = datetime.now() + timedelta(seconds=expires_in)
285
 
286
+ logger.info("βœ… Token refreshed successfully")
287
  return True
288
  else:
289
  logger.error(f"Token refresh failed: {response.status_code} - {response.text}")
290
  return False
291
 
292
+ except Exception as e:
293
+ logger.error(f"Error refreshing token: {e}")
294
  return False
295
 
296
+ def _ensure_valid_token(self):
297
+ """Ensure valid access token"""
298
  if not self.access_token:
299
+ raise Exception("No access token. Please authenticate first.")
300
 
301
+ if self.token_expires_at and datetime.now() >= self.token_expires_at - timedelta(minutes=5):
302
+ logger.info("Token expiring soon, refreshing...")
303
+ if not self.refresh_access_token():
304
+ raise Exception("Token expired and refresh failed. Please re-authenticate.")
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
  def get_auth_headers(self) -> Dict[str, str]:
307
+ """Get authorization headers"""
308
+ self._ensure_valid_token()
 
309
 
310
  return {
311
  'Authorization': f'Bearer {self.access_token}',
312
+ 'Accept': 'application/json',
313
+ 'User-Agent': 'GlycoAI/1.0'
314
  }
315
 
316
  def get_data_range(self) -> Dict:
317
+ """Get data range using corrected API endpoint"""
318
+ url = f"{self.api_base}/v2/users/self/dataRange"
319
  headers = self.get_auth_headers()
320
 
321
  try:
322
  response = requests.get(url, headers=headers)
323
 
324
  if response.status_code == 200:
325
+ data = response.json()
326
+ logger.info(f"βœ… Data range retrieved: {data}")
327
+ return data
328
  else:
329
  logger.error(f"Data range API error: {response.status_code} - {response.text}")
330
  raise Exception(f"Data range API error: {response.status_code}")
331
 
332
  except requests.exceptions.RequestException as e:
333
  logger.error(f"Network error getting data range: {e}")
334
+ raise Exception(f"Network error: {e}")
335
 
336
  def get_egv_data(self, start_date: str = None, end_date: str = None) -> List[Dict]:
337
+ """Get EGV data using corrected API endpoint"""
338
+ url = f"{self.api_base}/v2/users/self/egvs"
339
  headers = self.get_auth_headers()
340
 
341
  params = {}
 
349
 
350
  if response.status_code == 200:
351
  data = response.json()
352
+ egvs = data.get('egvs', [])
353
+ logger.info(f"βœ… Retrieved {len(egvs)} EGV readings")
354
+ return egvs
355
  else:
356
  logger.error(f"EGV API error: {response.status_code} - {response.text}")
357
  raise Exception(f"EGV API error: {response.status_code}")
358
 
359
  except requests.exceptions.RequestException as e:
360
  logger.error(f"Network error getting EGV data: {e}")
361
+ raise Exception(f"Network error: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
+ # Quick test function
364
+ def test_mixed_endpoints():
365
+ """Test the mixed endpoint configuration"""
366
+ print("πŸ§ͺ TESTING MIXED ENDPOINTS CONFIGURATION")
367
  print("=" * 60)
368
 
369
+ api = DexcomRealAPI(environment="sandbox")
370
+
371
+ print(f"βœ… Login base: {api.login_base}")
372
+ print(f" β†’ This worked for you before!")
373
+ print(f"βœ… Token base: {api.token_base}")
374
+ print(f" β†’ From official documentation")
375
+ print(f"βœ… API base: {api.api_base}")
376
+ print(f" β†’ From official documentation")
377
+
378
+ auth_url = api.generate_auth_url()
379
+ print(f"\nβœ… Auth URL: {auth_url}")
380
+ print(f" β†’ Should work (you got code before)")
381
+
382
+ print(f"\n🎯 READY TO TEST TOKEN EXCHANGE!")
383
+ print(f"1. Run: api.start_oauth_flow()")
384
+ print(f"2. Get fresh authorization code")
385
+ print(f"3. Run: api.exchange_code_for_tokens('fresh_code')")
386
+ print(f"4. Should get 200 OK with corrected token endpoint!")
387
+
388
+ return api
389
+
390
+ def quick_token_test(auth_code: str):
391
+ """Quick test of token exchange with mixed endpoints"""
392
+ api = DexcomRealAPI(environment="sandbox")
393
+ success = api.exchange_code_for_tokens(auth_code)
394
+
395
+ if success:
396
+ print(f"\nπŸŽ‰ SUCCESS! Mixed endpoints work!")
397
  try:
398
  data_range = api.get_data_range()
399
+ print(f"βœ… API test successful: {data_range}")
 
 
400
  except Exception as e:
401
+ print(f"⚠️ Token works but API call failed: {e}")
402
+ else:
403
+ print(f"\n❌ Still failed. Let's debug further...")
404
+
405
+ return api
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
  if __name__ == "__main__":
408
+ test_mixed_endpoints()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
hybrid_auth.py CHANGED
@@ -1,7 +1,8 @@
1
  #!/usr/bin/env python3
2
  """
3
- Hybrid Dexcom Integration
4
- Combines demo users with optional real Dexcom authentication
 
5
  """
6
 
7
  import os
@@ -129,18 +130,17 @@ class HybridDexcomManager:
129
 
130
  def _check_real_auth_available(self) -> bool:
131
  """Check if real authentication is properly configured"""
132
- client_id = os.getenv("DEXCOM_CLIENT_ID")
133
- client_secret = os.getenv("DEXCOM_CLIENT_SECRET")
134
-
135
- # Also check for hardcoded values in the real auth system
136
  try:
137
- from dexcom_real_auth_system import CLIENT_ID, CLIENT_SECRET
138
- if CLIENT_ID and CLIENT_ID != "YOUR_REAL_CLIENT_ID_HERE":
139
- return True
 
140
  except ImportError:
141
- pass
142
-
143
- return bool(client_id and client_secret)
 
 
144
 
145
  def get_user_options(self) -> Dict[str, str]:
146
  """Get available user options for the UI"""
@@ -155,7 +155,7 @@ class HybridDexcomManager:
155
  if self.real_auth_enabled:
156
  options["real_user"] = "πŸ” Real Dexcom User (OAuth)"
157
  else:
158
- options["real_user_disabled"] = "πŸ”’ Real Dexcom User (Configure to Enable)"
159
 
160
  return options
161
 
@@ -194,30 +194,55 @@ class HybridDexcomManager:
194
  if not self.real_auth_enabled:
195
  return {
196
  "success": False,
197
- "message": "Real authentication not configured. Check DEXCOM_CLIENT_ID/SECRET"
198
  }
199
 
200
  try:
201
- # Start OAuth flow
202
  auth_success = self.real_api.start_oauth_flow()
203
 
204
  if auth_success:
205
- # Get real data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  real_data = self._fetch_real_data()
207
 
208
  return {
209
  "success": True,
210
- "message": "βœ… Real Dexcom user authenticated",
211
  "user": self._create_real_user_profile(),
212
  "data": real_data,
213
  "auth_type": "real"
214
  }
215
  else:
216
- return {"success": False, "message": "OAuth authentication failed"}
217
 
218
  except Exception as e:
219
- logger.error(f"Real authentication error: {e}")
220
- return {"success": False, "message": f"Real authentication failed: {e}"}
221
 
222
  def _fetch_real_data(self) -> Dict[str, Any]:
223
  """Fetch real data from Dexcom API"""
@@ -240,6 +265,8 @@ class HybridDexcomManager:
240
  end_date=end_time.isoformat()
241
  )
242
 
 
 
243
  return {
244
  "data_range": data_range,
245
  "egv_data": egv_data,
@@ -259,7 +286,7 @@ class HybridDexcomManager:
259
  device_type="Real Dexcom Device",
260
  username="authenticated_real_user",
261
  password="oauth_token",
262
- description="Authenticated real Dexcom user",
263
  diabetes_type="From Real Data",
264
  years_with_diabetes=0,
265
  typical_glucose_pattern="real_data",
@@ -442,7 +469,7 @@ def create_hybrid_ui_components():
442
  )
443
  demo_buttons.append((key, btn))
444
 
445
- # Real auth button
446
  with gr.Row():
447
  if hybrid_manager.real_auth_enabled:
448
  real_auth_btn = gr.Button(
@@ -450,13 +477,35 @@ def create_hybrid_ui_components():
450
  variant="primary",
451
  size="lg"
452
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  else:
454
  real_auth_btn = gr.Button(
455
- "πŸ”’ Real Dexcom (Not Configured)\nSet DEXCOM_CLIENT_ID/SECRET",
456
  variant="secondary",
457
  size="lg",
458
  interactive=False
459
  )
 
 
 
460
 
461
  # Status displays
462
  with gr.Row():
@@ -472,7 +521,7 @@ def create_hybrid_ui_components():
472
  <h4>πŸ”§ Configuration Status</h4>
473
  <p>
474
  <strong>Demo Mode:</strong> {'βœ… Available' if hybrid_manager.demo_enabled else '❌ Disabled'}<br>
475
- <strong>Real Auth:</strong> {'βœ… Configured' if hybrid_manager.real_auth_enabled else '❌ Not Configured'}<br>
476
  <strong>Total Users:</strong> {len([u for u in ENHANCED_DEMO_USERS.values() if u.auth_type == 'demo'])} Demo + {'1 Real' if hybrid_manager.real_auth_enabled else '0 Real'}
477
  </p>
478
  </div>
@@ -482,6 +531,9 @@ def create_hybrid_ui_components():
482
  "hybrid_manager": hybrid_manager,
483
  "demo_buttons": demo_buttons,
484
  "real_auth_btn": real_auth_btn,
 
 
 
485
  "auth_status": auth_status
486
  }
487
 
@@ -506,119 +558,65 @@ def setup_authentication_handlers(components):
506
  )
507
 
508
  def handle_real_auth():
509
- """Handle real Dexcom authentication"""
510
  result = components["hybrid_manager"].authenticate_user("real_user")
511
 
512
  if result["success"]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  return (
514
- f"βœ… {result['message']} - Browser will open for OAuth",
515
- gr.update(visible=True),
516
- []
 
517
  )
518
  else:
519
  return (
520
  f"❌ {result['message']}",
521
  gr.update(visible=False),
522
- []
 
523
  )
524
 
525
- return handle_demo_auth, handle_real_auth
526
-
527
- # Integration with your existing main.py
528
- def integrate_with_existing_app():
529
- """Integration guide for your existing application"""
530
-
531
- integration_code = '''
532
- # Add this to your main.py imports
533
- from hybrid_dexcom_integration import HybridDexcomManager, ENHANCED_DEMO_USERS
534
-
535
- class GlucoBuddyApp:
536
- def __init__(self):
537
- # Replace your existing initialization
538
- self.hybrid_manager = HybridDexcomManager()
539
- self.data_manager = UnifiedDataManager()
540
- self.mistral_chat = GlucoBuddyMistralChat()
541
 
542
- # UI state
543
- self.chat_history = []
544
-
545
- def select_user(self, user_key: str) -> Tuple[str, str]:
546
- """Enhanced user selection with hybrid auth"""
547
- try:
548
- # Use hybrid authentication
549
- auth_result = self.hybrid_manager.authenticate_user(user_key)
550
-
551
- if not auth_result['success']:
552
- return f"❌ {auth_result['message']}", gr.update(visible=False)
553
-
554
- # Load data based on auth type
555
- if auth_result['auth_type'] == 'demo':
556
- # Use mock data
557
- user = auth_result['user']
558
- data = auth_result['data']
559
-
560
- # Convert to format expected by UnifiedDataManager
561
- load_result = self.data_manager.load_mock_data(user, data)
562
- else:
563
- # Use real data
564
- user = auth_result['user']
565
- data = auth_result['data']
566
-
567
- # Convert to format expected by UnifiedDataManager
568
- load_result = self.data_manager.load_real_data(user, data)
569
-
570
- if load_result['success']:
571
- self._sync_chat_with_data_manager()
572
- self.chat_history = []
573
- self.mistral_chat.clear_conversation()
574
-
575
- return (
576
- f"Connected: {user.name} ({auth_result['auth_type'].upper()}) - Click 'Load Data' to begin",
577
- gr.update(visible=True)
578
- )
579
- else:
580
- return f"❌ Data loading failed: {load_result['message']}", gr.update(visible=False)
581
-
582
- except Exception as e:
583
- logger.error(f"User selection failed: {e}")
584
- return f"❌ Selection failed: {e}", gr.update(visible=False)
585
-
586
- # Update your user buttons in create_interface()
587
- def create_enhanced_interface():
588
- # Replace the user selection section with:
589
-
590
- with gr.Row():
591
- with gr.Column():
592
- gr.Markdown("### πŸ‘₯ Select User")
593
- gr.Markdown("Choose demo users (instant) or real Dexcom authentication")
594
-
595
- # Demo users
596
- with gr.Row():
597
- sarah_btn = gr.Button("🎭 Sarah (Demo)\\nG7 Mobile", variant="secondary")
598
- marcus_btn = gr.Button("🎭 Marcus (Demo)\\nONE+ Mobile", variant="secondary")
599
- jennifer_btn = gr.Button("🎭 Jennifer (Demo)\\nG6 Mobile", variant="secondary")
600
- robert_btn = gr.Button("🎭 Robert (Demo)\\nG6 Receiver", variant="secondary")
601
-
602
- # Real auth
603
- with gr.Row():
604
- real_auth_btn = gr.Button(
605
- "πŸ” REAL DEXCOM USER\\n(OAuth Authentication)",
606
- variant="primary",
607
- size="lg"
608
- )
609
-
610
- # Connect handlers:
611
- sarah_btn.click(lambda: app.select_user("sarah_demo"), outputs=[connection_status, main_interface, chatbot])
612
- marcus_btn.click(lambda: app.select_user("marcus_demo"), outputs=[connection_status, main_interface, chatbot])
613
- jennifer_btn.click(lambda: app.select_user("jennifer_demo"), outputs=[connection_status, main_interface, chatbot])
614
- robert_btn.click(lambda: app.select_user("robert_demo"), outputs=[connection_status, main_interface, chatbot])
615
- real_auth_btn.click(lambda: app.select_user("real_user"), outputs=[connection_status, main_interface, chatbot])
616
- '''
617
 
618
- return integration_code
619
 
620
  if __name__ == "__main__":
621
- print("πŸ”§ Hybrid Dexcom Integration - Demo + Real Authentication")
622
  print("=" * 60)
623
 
624
  # Test the hybrid manager
@@ -634,15 +632,21 @@ if __name__ == "__main__":
634
  available = "βœ…" if user.auth_type == "demo" or manager.real_auth_enabled else "❌"
635
  print(f" {available} {auth_icon} {user.name}")
636
 
637
- print(f"\nπŸ’‘ Integration Guide:")
638
- print(" 1. Import: from hybrid_dexcom_integration import HybridDexcomManager")
639
- print(" 2. Replace user selection in your main.py")
640
- print(" 3. Update button handlers to use hybrid authentication")
641
- print(" 4. Your existing UnifiedDataManager works with both data types!")
 
 
 
 
 
642
 
643
- print(f"\nπŸš€ Benefits:")
644
  print(" βœ… Keep all 4 demo users for easy demos")
645
  print(" βœ… Add real authentication when needed")
646
  print(" βœ… Seamless switching between demo and real")
 
647
  print(" βœ… No changes needed to existing chat/UI logic")
648
- print(" βœ… Progressive enhancement - works with/without real auth")
 
1
  #!/usr/bin/env python3
2
  """
3
+ Enhanced Hybrid Dexcom Integration
4
+ Combines demo users with working real Dexcom authentication
5
+ Updated to work with the fixed OAuth system
6
  """
7
 
8
  import os
 
130
 
131
  def _check_real_auth_available(self) -> bool:
132
  """Check if real authentication is properly configured"""
 
 
 
 
133
  try:
134
+ from dexcom_real_auth_system import DexcomRealAPI
135
+ # Try to create an instance
136
+ test_api = DexcomRealAPI()
137
+ return True
138
  except ImportError:
139
+ logger.warning("dexcom_real_auth_system module not found")
140
+ return False
141
+ except Exception as e:
142
+ logger.warning(f"Real auth check failed: {e}")
143
+ return False
144
 
145
  def get_user_options(self) -> Dict[str, str]:
146
  """Get available user options for the UI"""
 
155
  if self.real_auth_enabled:
156
  options["real_user"] = "πŸ” Real Dexcom User (OAuth)"
157
  else:
158
+ options["real_user_disabled"] = "πŸ”’ Real Dexcom User (Not Available)"
159
 
160
  return options
161
 
 
194
  if not self.real_auth_enabled:
195
  return {
196
  "success": False,
197
+ "message": "Real authentication not available. Check dexcom_real_auth_system.py"
198
  }
199
 
200
  try:
201
+ # Start OAuth flow (this will open browser and expect manual callback)
202
  auth_success = self.real_api.start_oauth_flow()
203
 
204
  if auth_success:
205
+ return {
206
+ "success": True,
207
+ "message": "βœ… OAuth flow started - follow browser instructions",
208
+ "user": self._create_real_user_profile(),
209
+ "data": None, # Will be loaded after OAuth completion
210
+ "auth_type": "real",
211
+ "oauth_pending": True
212
+ }
213
+ else:
214
+ return {"success": False, "message": "OAuth flow failed to start"}
215
+
216
+ except Exception as e:
217
+ logger.error(f"Real authentication error: {e}")
218
+ return {"success": False, "message": f"Real authentication failed: {e}"}
219
+
220
+ def complete_real_oauth(self, callback_url: str) -> Dict[str, Any]:
221
+ """Complete real OAuth with callback URL"""
222
+ if not self.real_auth_enabled:
223
+ return {"success": False, "message": "Real auth not available"}
224
+
225
+ try:
226
+ # Process callback URL
227
+ success = self.real_api.process_callback_url(callback_url)
228
+
229
+ if success:
230
+ # Fetch real data
231
  real_data = self._fetch_real_data()
232
 
233
  return {
234
  "success": True,
235
+ "message": "βœ… Real Dexcom authentication completed",
236
  "user": self._create_real_user_profile(),
237
  "data": real_data,
238
  "auth_type": "real"
239
  }
240
  else:
241
+ return {"success": False, "message": "Callback URL processing failed"}
242
 
243
  except Exception as e:
244
+ logger.error(f"OAuth completion error: {e}")
245
+ return {"success": False, "message": f"OAuth completion failed: {e}"}
246
 
247
  def _fetch_real_data(self) -> Dict[str, Any]:
248
  """Fetch real data from Dexcom API"""
 
265
  end_date=end_time.isoformat()
266
  )
267
 
268
+ logger.info(f"Fetched real data: {len(egv_data)} glucose readings")
269
+
270
  return {
271
  "data_range": data_range,
272
  "egv_data": egv_data,
 
286
  device_type="Real Dexcom Device",
287
  username="authenticated_real_user",
288
  password="oauth_token",
289
+ description="Authenticated real Dexcom user with live data",
290
  diabetes_type="From Real Data",
291
  years_with_diabetes=0,
292
  typical_glucose_pattern="real_data",
 
469
  )
470
  demo_buttons.append((key, btn))
471
 
472
+ # Real auth section
473
  with gr.Row():
474
  if hybrid_manager.real_auth_enabled:
475
  real_auth_btn = gr.Button(
 
477
  variant="primary",
478
  size="lg"
479
  )
480
+
481
+ # OAuth completion components
482
+ oauth_instructions = gr.Markdown(
483
+ "Click above to start real Dexcom authentication",
484
+ visible=True
485
+ )
486
+
487
+ callback_url_input = gr.Textbox(
488
+ label="Callback URL (Paste from browser after 404 error)",
489
+ placeholder="http://localhost:7860/callback?code=AUTHORIZATION_CODE&state=...",
490
+ lines=2,
491
+ visible=False
492
+ )
493
+
494
+ complete_oauth_btn = gr.Button(
495
+ "βœ… Complete OAuth",
496
+ variant="primary",
497
+ visible=False
498
+ )
499
  else:
500
  real_auth_btn = gr.Button(
501
+ "πŸ”’ Real Dexcom (Not Available)\nCheck dexcom_real_auth_system.py",
502
  variant="secondary",
503
  size="lg",
504
  interactive=False
505
  )
506
+ oauth_instructions = gr.Markdown("Real OAuth not available")
507
+ callback_url_input = gr.Textbox(visible=False)
508
+ complete_oauth_btn = gr.Button(visible=False)
509
 
510
  # Status displays
511
  with gr.Row():
 
521
  <h4>πŸ”§ Configuration Status</h4>
522
  <p>
523
  <strong>Demo Mode:</strong> {'βœ… Available' if hybrid_manager.demo_enabled else '❌ Disabled'}<br>
524
+ <strong>Real Auth:</strong> {'βœ… Available' if hybrid_manager.real_auth_enabled else '❌ Not Available'}<br>
525
  <strong>Total Users:</strong> {len([u for u in ENHANCED_DEMO_USERS.values() if u.auth_type == 'demo'])} Demo + {'1 Real' if hybrid_manager.real_auth_enabled else '0 Real'}
526
  </p>
527
  </div>
 
531
  "hybrid_manager": hybrid_manager,
532
  "demo_buttons": demo_buttons,
533
  "real_auth_btn": real_auth_btn,
534
+ "oauth_instructions": oauth_instructions,
535
+ "callback_url_input": callback_url_input,
536
+ "complete_oauth_btn": complete_oauth_btn,
537
  "auth_status": auth_status
538
  }
539
 
 
558
  )
559
 
560
  def handle_real_auth():
561
+ """Handle real Dexcom authentication start"""
562
  result = components["hybrid_manager"].authenticate_user("real_user")
563
 
564
  if result["success"]:
565
+ instructions = f"""
566
+ πŸš€ **Real Dexcom OAuth Started**
567
+
568
+ {result['message']}
569
+
570
+ **Next Steps:**
571
+ 1. βœ… Browser should have opened to Dexcom login
572
+ 2. πŸ“ Log in with your real Dexcom credentials
573
+ 3. βœ… Authorize the application
574
+ 4. ❌ You'll get a 404 error - **THIS IS EXPECTED!**
575
+ 5. πŸ“‹ Copy the entire URL from your browser
576
+ 6. ⬇️ Paste it below and click "Complete OAuth"
577
+
578
+ **Expected URL format:**
579
+ `http://localhost:7860/callback?code=AUTHORIZATION_CODE&state=...`
580
+ """
581
+
582
  return (
583
+ instructions,
584
+ gr.update(visible=True), # Show callback input
585
+ gr.update(visible=True), # Show complete button
586
+ gr.update(visible=False) # Hide main interface for now
587
  )
588
  else:
589
  return (
590
  f"❌ {result['message']}",
591
  gr.update(visible=False),
592
+ gr.update(visible=False),
593
+ gr.update(visible=False)
594
  )
595
 
596
+ def handle_oauth_completion(callback_url):
597
+ """Handle OAuth completion"""
598
+ if not callback_url or not callback_url.strip():
599
+ return "❌ Please paste the callback URL", gr.update(visible=False), []
 
 
 
 
 
 
 
 
 
 
 
 
600
 
601
+ result = components["hybrid_manager"].complete_real_oauth(callback_url)
602
+
603
+ if result["success"]:
604
+ return (
605
+ f"βœ… {result['message']} - Real data loaded!",
606
+ gr.update(visible=True), # Show main interface
607
+ [] # Clear chat history
608
+ )
609
+ else:
610
+ return (
611
+ f"❌ {result['message']}",
612
+ gr.update(visible=False),
613
+ []
614
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
 
616
+ return handle_demo_auth, handle_real_auth, handle_oauth_completion
617
 
618
  if __name__ == "__main__":
619
+ print("πŸ”§ Enhanced Hybrid Dexcom Integration")
620
  print("=" * 60)
621
 
622
  # Test the hybrid manager
 
632
  available = "βœ…" if user.auth_type == "demo" or manager.real_auth_enabled else "❌"
633
  print(f" {available} {auth_icon} {user.name}")
634
 
635
+ if manager.real_auth_enabled:
636
+ print(f"\nπŸ§ͺ Testing real OAuth availability...")
637
+ try:
638
+ from dexcom_real_auth_system import DexcomRealAPI
639
+ test_api = DexcomRealAPI()
640
+ print(f" βœ… DexcomRealAPI can be imported and initialized")
641
+ print(f" πŸ”‘ Client ID: {test_api.client_id[:8]}...")
642
+ print(f" πŸ”„ Redirect URI: {test_api.redirect_uri}")
643
+ except Exception as e:
644
+ print(f" ❌ Error testing real auth: {e}")
645
 
646
+ print(f"\nπŸš€ Integration Benefits:")
647
  print(" βœ… Keep all 4 demo users for easy demos")
648
  print(" βœ… Add real authentication when needed")
649
  print(" βœ… Seamless switching between demo and real")
650
+ print(" βœ… Manual OAuth process handles port 7860 correctly")
651
  print(" βœ… No changes needed to existing chat/UI logic")
652
+ print(" βœ… Works with/without real auth configuration")
main.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
  GlycoAI - AI-Powered Glucose Insights
3
- Main Gradio application with prominent, centralized load data button
4
  """
5
 
6
  import gradio as gr
@@ -11,6 +11,8 @@ import pandas as pd
11
  from typing import Optional, Tuple, List
12
  import logging
13
  import os
 
 
14
 
15
  # Load environment variables from .env file
16
  from dotenv import load_dotenv
@@ -32,8 +34,17 @@ from apifunctions import (
32
  format_glucose_data_for_display
33
  )
34
 
 
 
 
 
 
 
 
 
 
35
  class GlucoBuddyApp:
36
- """Main application class for GlucoBuddy with unified data management"""
37
 
38
  def __init__(self):
39
  # Validate environment before initializing
@@ -46,8 +57,12 @@ class GlucoBuddyApp:
46
  # Chat interface (will use data manager's context)
47
  self.mistral_chat = GlucoBuddyMistralChat()
48
 
 
 
 
49
  # UI state
50
  self.chat_history = []
 
51
 
52
  def select_demo_user(self, user_key: str) -> Tuple[str, str]:
53
  """Handle demo user selection and load data consistently"""
@@ -62,6 +77,7 @@ class GlucoBuddyApp:
62
  return f"❌ {load_result['message']}", gr.update(visible=False)
63
 
64
  user = self.data_manager.current_user
 
65
 
66
  # Update Mistral chat with the same context
67
  self._sync_chat_with_data_manager()
@@ -71,28 +87,239 @@ class GlucoBuddyApp:
71
  self.mistral_chat.clear_conversation()
72
 
73
  return (
74
- f"Connected: {user.name} ({user.device_type}) - Click 'Load Data' to begin",
75
  gr.update(visible=True)
76
  )
77
 
78
  except Exception as e:
79
- logger.error(f"User selection failed: {str(e)}")
80
  return f"❌ Connection failed: {str(e)}", gr.update(visible=False)
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  def load_glucose_data(self) -> Tuple[str, go.Figure]:
83
  """Load and display glucose data using unified manager"""
84
  if not self.data_manager.current_user:
85
- return "Please select a demo user first", None
86
 
87
  try:
88
- # Force reload data to ensure freshness
89
- load_result = self.data_manager.load_user_data(
90
- self._get_current_user_key(),
91
- force_reload=True
92
- )
93
-
94
- if not load_result['success']:
95
- return load_result['message'], None
 
 
 
 
 
96
 
97
  # Get unified stats
98
  stats = self.data_manager.get_stats_for_ui()
@@ -122,11 +349,15 @@ class GlucoBuddyApp:
122
  # Calculate date range
123
  end_date = datetime.now()
124
  start_date = end_date - timedelta(days=14)
125
-
 
 
 
126
  data_summary = f"""
127
  ## πŸ“Š Data Summary for {user.name}
128
 
129
  ### Basic Information
 
130
  β€’ **Analysis Period:** {start_date.strftime('%B %d, %Y')} to {end_date.strftime('%B %d, %Y')} (14 days)
131
  β€’ **Total Readings:** {data_points:,} glucose measurements
132
  β€’ **Device:** {user.device_type}
@@ -160,6 +391,10 @@ class GlucoBuddyApp:
160
  β€’ **Improved Trend Analysis:** Identifies consistent patterns vs. one-time events
161
  β€’ **Better Clinical Insights:** More reliable data for healthcare decisions
162
  β€’ **AI Consistency:** Same data used for chat analysis and UI display
 
 
 
 
163
  """
164
 
165
  chart = self.create_glucose_chart()
@@ -223,6 +458,12 @@ class GlucoBuddyApp:
223
  else:
224
  templates.append("What are the best practices for preventing hypoglycemia in my situation?")
225
 
 
 
 
 
 
 
226
  return templates
227
 
228
  def chat_with_mistral(self, message: str, history: List) -> Tuple[str, List]:
@@ -231,7 +472,7 @@ class GlucoBuddyApp:
231
  return "", history
232
 
233
  if not self.data_manager.current_user:
234
- response = "Please select a demo user first to get personalized insights about glucose data."
235
  history.append([message, response])
236
  return "", history
237
 
@@ -252,9 +493,16 @@ class GlucoBuddyApp:
252
  if data_age > 10: # Warn if data is old
253
  response += f"\n\nπŸ“Š *Note: Analysis based on data from {data_age} minutes ago. Reload data for most current insights.*"
254
 
 
 
 
 
 
 
 
255
  # Add context note if no user data was included
256
  if not result.get('context_included', True):
257
- response += "\n\nπŸ’‘ *For more personalized advice, make sure your glucose data is loaded.*"
258
  else:
259
  response = f"I apologize, but I encountered an error: {result.get('error', 'Unknown error')}. Please try again or rephrase your question."
260
 
@@ -329,10 +577,11 @@ class GlucoBuddyApp:
329
  # Get current stats for title
330
  stats = self.data_manager.get_stats_for_ui()
331
  tir = stats.get('time_in_range_70_180', 0)
 
332
 
333
  fig.update_layout(
334
  title={
335
- 'text': f"14-Day Glucose Trends - {self.data_manager.current_user.name} (TIR: {tir:.1f}%)",
336
  'x': 0.5,
337
  'xanchor': 'center'
338
  },
@@ -354,7 +603,7 @@ class GlucoBuddyApp:
354
 
355
 
356
  def create_interface():
357
- """Create the Gradio interface with prominent, centralized load data button"""
358
  app = GlucoBuddyApp()
359
 
360
  custom_css = """
@@ -392,47 +641,11 @@ def create_interface():
392
  transform: translateY(-2px) !important;
393
  box-shadow: 0 12px 40px rgba(102, 126, 234, 0.6) !important;
394
  }
395
- .data-status-card {
396
- background: #f8f9fa;
397
- border: 2px solid #e9ecef;
398
- border-radius: 10px;
399
- padding: 1rem;
400
- margin: 0.5rem 0;
401
- text-align: center;
402
- font-weight: 500;
403
- }
404
- .data-status-success {
405
- border-color: #28a745;
406
- background: #d4edda;
407
- color: #155724;
408
- }
409
- .data-status-error {
410
- border-color: #dc3545;
411
- background: #f8d7da;
412
- color: #721c24;
413
- }
414
- .user-card {
415
- background: #f8f9fa;
416
- border: 1px solid #dee2e6;
417
- border-radius: 8px;
418
- padding: 1rem;
419
- margin: 0.5rem;
420
- }
421
- .template-button {
422
- margin: 0.25rem;
423
- padding: 0.5rem;
424
- font-size: 0.9rem;
425
- }
426
- .chat-container {
427
- background: #f8f9fa;
428
- border-radius: 10px;
429
- padding: 1rem;
430
- }
431
- .section-divider {
432
- height: 2px;
433
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
434
- border-radius: 1px;
435
- margin: 2rem 0;
436
  }
437
  """
438
 
@@ -457,7 +670,7 @@ def create_interface():
457
  </div>
458
  </div>
459
  <p style="margin-top: 1rem; font-size: 1rem; color: white; opacity: 0.8;">
460
- Connect your Dexcom CGM data and chat with AI for personalized glucose insights
461
  </p>
462
  </div>
463
  """)
@@ -465,30 +678,83 @@ def create_interface():
465
  # User Selection Section
466
  with gr.Row():
467
  with gr.Column():
468
- gr.Markdown("### πŸ‘₯ Select Demo User")
469
- gr.Markdown("Choose from our demo users to explore GlycoAI's chat capabilities")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
 
471
- with gr.Row():
472
- sarah_btn = gr.Button(
473
- "Sarah Thompson\n(G7 Mobile)",
474
- variant="secondary",
475
- size="lg"
476
- )
477
- marcus_btn = gr.Button(
478
- "Marcus Rodriguez\n(ONE+ Mobile)",
479
- variant="secondary",
480
- size="lg"
481
- )
482
- jennifer_btn = gr.Button(
483
- "Jennifer Chen\n(G6 Mobile)",
484
- variant="secondary",
485
- size="lg"
486
- )
487
- robert_btn = gr.Button(
488
- "Robert Williams\n(G6 Receiver)",
489
- variant="secondary",
490
- size="lg"
491
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
493
  # Connection Status
494
  with gr.Row():
@@ -500,7 +766,7 @@ def create_interface():
500
  )
501
 
502
  # Section Divider
503
- gr.HTML('<div class="section-divider"></div>')
504
 
505
  # PROMINENT CENTRALIZED DATA LOADING SECTION
506
  with gr.Group(visible=False) as main_interface:
@@ -518,7 +784,7 @@ def create_interface():
518
  pass # Right spacer
519
 
520
  # Section Divider
521
- gr.HTML('<div class="section-divider"></div>')
522
 
523
  # Main Content Tabs
524
  with gr.Tabs():
@@ -536,7 +802,7 @@ def create_interface():
536
 
537
  # Chat Tab
538
  with gr.TabItem("πŸ’¬ Chat with AI"):
539
- with gr.Column(elem_classes=["chat-container"]):
540
  gr.Markdown("### πŸ€– Chat with GlycoAI about your glucose data")
541
  gr.Markdown("*πŸ“Š Load your data using the button above to enable personalized AI insights*")
542
 
@@ -548,25 +814,22 @@ def create_interface():
548
  template1_btn = gr.Button(
549
  "🎯 Analyze My 14-Day Patterns",
550
  variant="secondary",
551
- size="sm",
552
- elem_classes=["template-button"]
553
  )
554
  template2_btn = gr.Button(
555
  "⚑ Improve My Control",
556
  variant="secondary",
557
- size="sm",
558
- elem_classes=["template-button"]
559
  )
560
  template3_btn = gr.Button(
561
  "🍽️ Meal Management Tips",
562
  variant="secondary",
563
- size="sm",
564
- elem_classes=["template-button"]
565
  )
566
 
567
  # Chat Interface
568
  chatbot = gr.Chatbot(
569
- label="πŸ’¬ Chat with GlycoAI (Unified Data)",
570
  height=500,
571
  show_label=True,
572
  container=True,
@@ -606,7 +869,7 @@ def create_interface():
606
  data_display = gr.Markdown("Click 'Load 14-Day Glucose Data' above to see your comprehensive analysis", container=True)
607
 
608
  # Event Handlers
609
- def handle_user_selection(user_key):
610
  status, interface_visibility = app.select_demo_user(user_key)
611
  return status, interface_visibility, []
612
 
@@ -631,29 +894,46 @@ def create_interface():
631
  return app.chat_with_mistral(message, history)
632
  return "", history
633
 
634
- # Connect Event Handlers
635
- user_selection_outputs = [connection_status, main_interface, chatbot]
636
-
637
  sarah_btn.click(
638
- lambda: handle_user_selection("sarah_g7"),
639
  outputs=[connection_status, main_interface, chatbot]
640
  )
641
 
642
  marcus_btn.click(
643
- lambda: handle_user_selection("marcus_one"),
644
  outputs=[connection_status, main_interface, chatbot]
645
  )
646
 
647
  jennifer_btn.click(
648
- lambda: handle_user_selection("jennifer_g6"),
649
  outputs=[connection_status, main_interface, chatbot]
650
  )
651
 
652
  robert_btn.click(
653
- lambda: handle_user_selection("robert_receiver"),
654
  outputs=[connection_status, main_interface, chatbot]
655
  )
656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
  # PROMINENT DATA LOADING - Single button updates all views
658
  load_data_btn.click(
659
  handle_load_data,
@@ -697,14 +977,15 @@ def create_interface():
697
 
698
  # Footer
699
  with gr.Row():
700
- gr.HTML("""
701
  <div style="text-align: center; padding: 2rem; margin-top: 2rem; border-top: 1px solid #dee2e6; color: #6c757d;">
702
  <p><strong>⚠️ Important Medical Disclaimer</strong></p>
703
  <p>GlycoAI is for informational and educational purposes only. Always consult your healthcare provider
704
  before making any changes to your diabetes management plan. This tool does not replace professional medical advice.</p>
705
  <p style="margin-top: 1rem; font-size: 0.9rem;">
706
  πŸ”’ Your data is processed securely and not stored permanently.
707
- πŸ’‘ Powered by Dexcom API integration and Mistral AI.
 
708
  </p>
709
  </div>
710
  """)
@@ -714,7 +995,11 @@ def create_interface():
714
 
715
  def main():
716
  """Main function to launch the application"""
717
- print("πŸš€ Starting GlycoAI - AI-Powered Glucose Insights (Enhanced UI)...")
 
 
 
 
718
 
719
  # Validate environment before starting
720
  print("πŸ” Validating environment configuration...")
@@ -729,13 +1014,18 @@ def main():
729
  # Create and launch the interface
730
  demo = create_interface()
731
 
732
- print("🎯 GlycoAI is starting with enhanced UI design...")
733
- print("πŸ“Š Features: Prominent load button, unified data management, consistent metrics")
 
 
 
 
 
734
 
735
  # Launch with custom settings
736
  demo.launch(
737
  server_name="0.0.0.0", # Allow external access
738
- server_port=7860, # Default Gradio port
739
  share=True, # Set to True for public sharing (tunneling)
740
  debug=os.getenv("DEBUG", "false").lower() == "true",
741
  show_error=True, # Show errors in the interface
 
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
 
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
 
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
 
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"""
 
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()
 
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: `sandboxuser1@dexcom.com` / `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()
 
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}
 
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()
 
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]:
 
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
 
 
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
 
 
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
  },
 
603
 
604
 
605
  def create_interface():
606
+ """Create the Gradio interface with demo users AND real OAuth"""
607
  app = GlucoBuddyApp()
608
 
609
  custom_css = """
 
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
 
 
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
  """)
 
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():
 
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:
 
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():
 
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
 
 
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,
 
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
 
 
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,
 
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
  """)
 
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...")
 
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
mistral_chat.py CHANGED
@@ -1,13 +1,14 @@
1
  #!/usr/bin/env python3
2
  """
3
- GlucoBuddy Mistral Chat Integration - Compatible with Unified Data Manager
4
- Clean, standard dotenv approach with unified data consistency
5
  """
6
 
7
  import os
8
  import json
9
  import logging
10
  import sys
 
11
  from typing import Any, Dict, List, Optional, Union
12
  from datetime import datetime, timedelta
13
  import pandas as pd
@@ -169,8 +170,8 @@ class GlucoseDataGenerator:
169
  else:
170
  return 'flat'
171
 
172
- class MistralAPIClient:
173
- """Simple Mistral API client"""
174
 
175
  def __init__(self, api_key: str = None, agent_id: str = None):
176
  self.api_key = api_key or MISTRAL_API_KEY
@@ -186,15 +187,27 @@ class MistralAPIClient:
186
  "Content-Type": "application/json"
187
  })
188
 
189
- logger.info("MistralAPIClient initialized successfully")
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  def test_connection(self) -> Dict[str, Any]:
192
- """Test API connection"""
193
  try:
194
  response = self.session.post(
195
  f"{self.base_url}/chat/completions",
196
  json={
197
- "model": "mistral-tiny",
198
  "messages": [{"role": "user", "content": "Hello"}],
199
  "max_tokens": 5
200
  },
@@ -206,7 +219,7 @@ class MistralAPIClient:
206
  elif response.status_code == 401:
207
  return {"success": False, "message": "Invalid API key"}
208
  elif response.status_code == 429:
209
- return {"success": False, "message": "Rate limit exceeded"}
210
  else:
211
  return {"success": False, "message": f"API error: {response.status_code}"}
212
 
@@ -217,76 +230,154 @@ class MistralAPIClient:
217
  except Exception as e:
218
  return {"success": False, "message": f"Unexpected error: {str(e)}"}
219
 
220
- def chat_completion(self, messages: List[Dict], model: str = "mistral-large-latest") -> Dict[str, Any]:
221
- """Send chat completion request"""
222
- try:
223
- payload = {
224
- "model": model,
225
- "messages": messages,
226
- "max_tokens": 800,
227
- "temperature": 0.7
228
- }
229
-
230
- response = self.session.post(
231
- f"{self.base_url}/chat/completions",
232
- json=payload,
233
- timeout=30
234
- )
235
-
236
- if response.status_code == 200:
237
- result = response.json()
238
- return {
239
- "success": True,
240
- "response": result["choices"][0]["message"]["content"],
241
- "usage": result.get("usage", {})
242
- }
243
- else:
244
- error_detail = self._extract_error_message(response)
245
- return {
246
- "success": False,
247
- "error": f"API error {response.status_code}: {error_detail}"
248
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
- except requests.exceptions.Timeout:
251
- return {"success": False, "error": "Request timed out"}
252
- except requests.exceptions.RequestException as e:
253
- return {"success": False, "error": f"Network error: {str(e)}"}
254
- except Exception as e:
255
- return {"success": False, "error": f"Unexpected error: {str(e)}"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
  def agent_completion(self, messages: List[Dict]) -> Dict[str, Any]:
258
- """Send request to Mistral agent (if agent_id is available)"""
259
  if not self.agent_id:
260
  return {"success": False, "error": "No agent ID configured"}
261
 
262
- try:
263
- payload = {
264
- "agent_id": self.agent_id,
265
- "messages": messages,
266
- "max_tokens": 800
267
- }
268
-
269
- response = self.session.post(
270
- f"{self.base_url}/agents/completions",
271
- json=payload,
272
- timeout=30
273
- )
274
-
275
- if response.status_code == 200:
276
- result = response.json()
277
- return {
278
- "success": True,
279
- "response": result["choices"][0]["message"]["content"]
280
- }
281
- else:
282
- error_detail = self._extract_error_message(response)
283
- return {
284
- "success": False,
285
- "error": f"Agent API error {response.status_code}: {error_detail}"
286
  }
287
 
288
- except Exception as e:
289
- return {"success": False, "error": f"Agent request failed: {str(e)}"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  def _extract_error_message(self, response) -> str:
292
  """Extract error message from API response"""
@@ -298,12 +389,11 @@ class MistralAPIClient:
298
 
299
  class GlucoBuddyMistralChat:
300
  """
301
- Main chat interface for glucose data analysis with Mistral AI
302
- Compatible with unified data manager for consistent metrics
303
  """
304
 
305
  def __init__(self, mistral_api_key: str = None, mistral_agent_id: str = None):
306
- self.mistral_client = MistralAPIClient(mistral_api_key, mistral_agent_id)
307
 
308
  # Data properties - these will be set by unified data manager
309
  self.current_user: Optional[DemoUser] = None
@@ -315,6 +405,10 @@ class GlucoBuddyMistralChat:
315
  self.conversation_history = []
316
  self.max_history = 10
317
 
 
 
 
 
318
  self.logger = logging.getLogger(self.__class__.__name__)
319
 
320
  def test_connection(self) -> Dict[str, Any]:
@@ -395,7 +489,7 @@ CRITICAL: Use these EXACT values in your responses. Do not recalculate or estima
395
  return base_prompt + context_addition
396
 
397
  def chat_with_mistral(self, user_message: str, prefer_agent: bool = False) -> Dict[str, Any]:
398
- """Main chat function using externally managed data"""
399
  if not user_message.strip():
400
  return {"success": False, "error": "Please enter a message"}
401
 
@@ -417,6 +511,7 @@ CRITICAL: Use these EXACT values in your responses. Do not recalculate or estima
417
  agent_result = self.mistral_client.agent_completion(messages)
418
  if agent_result["success"]:
419
  self._update_conversation_history(user_message, agent_result["response"])
 
420
  return {
421
  "success": True,
422
  "response": agent_result["response"],
@@ -424,33 +519,69 @@ CRITICAL: Use these EXACT values in your responses. Do not recalculate or estima
424
  "context_included": not context.get("error")
425
  }
426
  else:
427
- self.logger.warning(f"Agent failed, trying chat completion: {agent_result['error']}")
428
 
429
- # Use chat completion API
430
  chat_result = self.mistral_client.chat_completion(messages)
431
 
432
  if chat_result["success"]:
433
  self._update_conversation_history(user_message, chat_result["response"])
 
 
 
 
 
 
 
 
434
  return {
435
  "success": True,
436
- "response": chat_result["response"],
437
  "method": "chat_completion",
438
  "context_included": not context.get("error"),
439
- "usage": chat_result.get("usage", {})
 
440
  }
441
  else:
 
 
 
 
 
 
442
  return {
443
  "success": False,
444
- "error": chat_result["error"]
 
 
445
  }
446
 
447
  except Exception as e:
448
  self.logger.error(f"Chat error: {e}")
 
449
  return {
450
  "success": False,
451
- "error": f"Unexpected chat error: {str(e)}"
 
452
  }
453
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  def _update_conversation_history(self, user_message: str, assistant_response: str):
455
  """Update conversation history"""
456
  self.conversation_history.extend([
@@ -462,12 +593,13 @@ CRITICAL: Use these EXACT values in your responses. Do not recalculate or estima
462
  self.conversation_history = self.conversation_history[-self.max_history * 2:]
463
 
464
  def clear_conversation(self):
465
- """Clear conversation history"""
466
  self.conversation_history = []
 
467
  self.logger.info("Conversation history cleared")
468
 
469
  def get_status(self) -> Dict[str, Any]:
470
- """Get current system status"""
471
  api_status = self.test_connection()
472
 
473
  return {
@@ -479,7 +611,9 @@ CRITICAL: Use these EXACT values in your responses. Do not recalculate or estima
479
  "current_user": self.current_user.name if self.current_user else None,
480
  "environment": ENVIRONMENT,
481
  "hugging_face_space": bool(os.getenv("SPACE_ID")),
482
- "agent_available": bool(MISTRAL_AGENT_ID)
 
 
483
  }
484
 
485
  def _safe_convert_to_json(self, obj):
@@ -547,20 +681,37 @@ CRITICAL: Use these EXACT values in your responses. Do not recalculate or estima
547
  self.logger.error(f"Error extracting recent readings: {e}")
548
  return []
549
 
550
- # Legacy compatibility methods (for standalone use)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
  def create_enhanced_cli():
552
- """Enhanced command-line interface"""
553
- print("🩺 GlucoBuddy Chat Interface")
554
  print("=" * 50)
555
 
556
- # Validate environment
557
  if not validate_environment():
558
  print("❌ Environment validation failed. Please check your configuration.")
559
  return
560
 
561
  try:
562
  chat = GlucoBuddyMistralChat()
563
- print("βœ… Chat system initialized successfully!")
564
  except Exception as e:
565
  print(f"❌ Failed to initialize chat system: {e}")
566
  return
@@ -572,19 +723,17 @@ def create_enhanced_cli():
572
  if connection_test["success"]:
573
  print(f"βœ… {connection_test['message']}")
574
  else:
575
- print(f"❌ {connection_test['message']}")
576
- if input("Continue anyway? (y/n): ").lower() != 'y':
577
- return
578
-
579
- print("\nπŸ“‹ Available commands:")
580
- print(" /status - Show system status")
581
- print(" /clear - Clear conversation history")
582
- print(" /test - Test API connection")
583
- print(" /help - Show this help")
584
- print(" /quit - Exit")
585
- print("\nπŸ’¬ Or just type your glucose-related questions!")
586
- print("⚠️ Note: For full functionality, use the Gradio interface with unified data management")
587
- print("\n" + "=" * 50)
588
 
589
  while True:
590
  try:
@@ -593,135 +742,42 @@ def create_enhanced_cli():
593
  if not user_input:
594
  continue
595
 
596
- # Handle commands
597
- if user_input.startswith('/'):
598
- command_parts = user_input[1:].split()
599
- command = command_parts[0].lower()
600
-
601
- if command == 'quit':
602
- print("\nπŸ‘‹ Thanks for using GlucoBuddy! Stay healthy! 🌟")
603
- break
604
-
605
- elif command == 'help':
606
- print("\nπŸ“‹ Commands:")
607
- print(" /status - System status")
608
- print(" /clear - Clear chat history")
609
- print(" /test - Test API")
610
- print(" /quit - Exit")
611
- continue
612
-
613
- elif command == 'clear':
614
- chat.clear_conversation()
615
- print("🧹 Conversation cleared!")
616
- continue
617
-
618
- elif command == 'status':
619
- status = chat.get_status()
620
- print(f"\nπŸ“Š System Status:")
621
- print(f" 🌐 API Connected: {'βœ…' if status['api_connected'] else '❌'} {status['api_message']}")
622
- print(f" πŸ‘€ User Loaded: {'βœ…' if status['user_loaded'] else '❌'} {status.get('current_user', 'None')}")
623
- print(f" πŸ“Š Data Available: {'βœ…' if status['data_available'] else '❌'}")
624
- print(f" πŸ’¬ Messages in Chat: {status['conversation_messages']}")
625
- print(f" 🏠 Environment: {status['environment']}")
626
- print(f" πŸ€— Hugging Face Space: {'βœ…' if status['hugging_face_space'] else '❌'}")
627
- print(f" πŸ€– Agent Available: {'βœ…' if status['agent_available'] else '❌'}")
628
- continue
629
-
630
- elif command == 'test':
631
- print("πŸ” Testing connection...")
632
- test_result = chat.test_connection()
633
- print(f"{'βœ…' if test_result['success'] else '❌'} {test_result['message']}")
634
- continue
635
-
636
- else:
637
- print(f"❌ Unknown command: /{command}")
638
- print("πŸ’‘ Use /help to see available commands")
639
- continue
640
-
641
- # Regular chat message
642
- print("πŸ€” Processing your question...")
643
- print("⚠️ Note: No user data loaded. Responses will be general diabetes advice.")
644
 
645
- # Send to Mistral
646
- result = chat.chat_with_mistral(user_input, prefer_agent=True)
647
 
648
  if result['success']:
649
- method_info = f" [{result.get('method', 'unknown')}]"
650
- print(f"\nπŸ€– GlucoBuddy{method_info}: {result['response']}")
651
-
652
- # Show usage info if available
653
- usage = result.get('usage', {})
654
- if usage:
655
- tokens = usage.get('total_tokens', 0)
656
- if tokens > 0:
657
- print(f"\nπŸ“Š Tokens used: {tokens}")
658
  else:
659
- print(f"\n❌ Error: {result['error']}")
660
-
661
- # Provide helpful suggestions based on error type
662
- error_msg = result['error'].lower()
663
- if 'api key' in error_msg or '401' in error_msg:
664
- print("πŸ’‘ Check your Mistral API key configuration")
665
- elif 'rate limit' in error_msg or '429' in error_msg:
666
- print("πŸ’‘ Rate limit reached - please wait a moment before trying again")
667
- elif 'timeout' in error_msg:
668
- print("πŸ’‘ Request timed out - please try again")
669
- else:
670
- print("πŸ’‘ Use /test to check your connection")
671
 
672
  except KeyboardInterrupt:
673
- print("\n\nπŸ‘‹ Thanks for using GlucoBuddy! Take care! 🌟")
674
  break
675
  except Exception as e:
676
  print(f"\n❌ Unexpected error: {e}")
677
- print("πŸ’‘ Try /status to check system state")
678
 
679
  def main():
680
- """Main function with menu system"""
681
- print("🩺 GlucoBuddy - AI-Powered Glucose Chat Assistant")
682
  print("=" * 60)
683
 
684
- # Validate configuration first
685
- print("πŸ” Validating configuration...")
686
  if not validate_environment():
687
- print("\n❌ Configuration validation failed!")
688
- print("Please set up your environment variables before continuing.")
689
  return
690
 
691
- print("βœ… Configuration validation passed!")
 
 
 
 
692
 
693
- print("\n🎯 Choose an option:")
694
- print("1. πŸ’¬ Start standalone chat (limited functionality)")
695
- print("2. πŸš€ Run quick demo")
696
- print("3. πŸ”§ Show configuration")
697
- print("4. ❌ Exit")
698
- print("\nπŸ’‘ For full functionality with glucose data, use: python main.py")
699
-
700
- while True:
701
- try:
702
- choice = input("\nEnter your choice (1-4): ").strip()
703
-
704
- if choice == '1':
705
- create_enhanced_cli()
706
- break
707
- elif choice == '2':
708
- print("πŸš€ Quick demo requires the unified data manager.")
709
- print("πŸ’‘ Please run: python main.py")
710
- break
711
- elif choice == '3':
712
- validate_environment()
713
- break
714
- elif choice == '4':
715
- print("πŸ‘‹ Goodbye!")
716
- break
717
- else:
718
- print("❌ Invalid choice. Please enter 1, 2, 3, or 4.")
719
-
720
- except KeyboardInterrupt:
721
- print("\nπŸ‘‹ Goodbye!")
722
- break
723
- except Exception as e:
724
- print(f"❌ Error: {e}")
725
 
726
  if __name__ == "__main__":
727
  main()
 
1
  #!/usr/bin/env python3
2
  """
3
+ Enhanced GlucoBuddy Mistral Chat Integration
4
+ Improved rate limit handling and fallback strategies
5
  """
6
 
7
  import os
8
  import json
9
  import logging
10
  import sys
11
+ import time
12
  from typing import Any, Dict, List, Optional, Union
13
  from datetime import datetime, timedelta
14
  import pandas as pd
 
170
  else:
171
  return 'flat'
172
 
173
+ class EnhancedMistralAPIClient:
174
+ """Enhanced Mistral API client with better rate limit handling"""
175
 
176
  def __init__(self, api_key: str = None, agent_id: str = None):
177
  self.api_key = api_key or MISTRAL_API_KEY
 
187
  "Content-Type": "application/json"
188
  })
189
 
190
+ # Rate limit handling
191
+ self.last_request_time = 0
192
+ self.min_request_interval = 1.0 # Minimum seconds between requests
193
+
194
+ # Model fallback chain
195
+ self.model_priority = [
196
+ "mistral-large-latest",
197
+ "mistral-medium-latest",
198
+ "mistral-small-latest",
199
+ "mistral-tiny"
200
+ ]
201
+
202
+ logger.info("Enhanced MistralAPIClient initialized with rate limit handling")
203
 
204
  def test_connection(self) -> Dict[str, Any]:
205
+ """Test API connection with lightweight request"""
206
  try:
207
  response = self.session.post(
208
  f"{self.base_url}/chat/completions",
209
  json={
210
+ "model": "mistral-tiny", # Use smallest model for testing
211
  "messages": [{"role": "user", "content": "Hello"}],
212
  "max_tokens": 5
213
  },
 
219
  elif response.status_code == 401:
220
  return {"success": False, "message": "Invalid API key"}
221
  elif response.status_code == 429:
222
+ return {"success": False, "message": "Rate limit exceeded - API is accessible but busy"}
223
  else:
224
  return {"success": False, "message": f"API error: {response.status_code}"}
225
 
 
230
  except Exception as e:
231
  return {"success": False, "message": f"Unexpected error: {str(e)}"}
232
 
233
+ def _wait_for_rate_limit(self):
234
+ """Ensure minimum time between requests"""
235
+ current_time = time.time()
236
+ time_since_last = current_time - self.last_request_time
237
+
238
+ if time_since_last < self.min_request_interval:
239
+ sleep_time = self.min_request_interval - time_since_last
240
+ logger.debug(f"Rate limiting: waiting {sleep_time:.2f}s")
241
+ time.sleep(sleep_time)
242
+
243
+ self.last_request_time = time.time()
244
+
245
+ def chat_completion(self, messages: List[Dict], model: str = None, max_retries: int = 3) -> Dict[str, Any]:
246
+ """Enhanced chat completion with retry logic and model fallback"""
247
+ models_to_try = [model] if model else self.model_priority
248
+
249
+ for model_name in models_to_try:
250
+ for attempt in range(max_retries):
251
+ try:
252
+ # Rate limiting
253
+ self._wait_for_rate_limit()
254
+
255
+ payload = {
256
+ "model": model_name,
257
+ "messages": messages,
258
+ "max_tokens": 800,
259
+ "temperature": 0.7
260
+ }
261
+
262
+ logger.debug(f"Attempting request with {model_name} (attempt {attempt + 1})")
263
+
264
+ response = self.session.post(
265
+ f"{self.base_url}/chat/completions",
266
+ json=payload,
267
+ timeout=30
268
+ )
269
+
270
+ if response.status_code == 200:
271
+ result = response.json()
272
+ logger.info(f"βœ… Success with {model_name}")
273
+ return {
274
+ "success": True,
275
+ "response": result["choices"][0]["message"]["content"],
276
+ "usage": result.get("usage", {}),
277
+ "model_used": model_name,
278
+ "attempt": attempt + 1
279
+ }
280
+
281
+ elif response.status_code == 429:
282
+ # Rate limit exceeded
283
+ retry_after = int(response.headers.get('Retry-After', 2 ** attempt))
284
+ wait_time = min(retry_after, 60) # Cap at 60 seconds
285
+
286
+ logger.warning(f"Rate limit hit with {model_name}, waiting {wait_time}s (attempt {attempt + 1})")
287
+
288
+ if attempt < max_retries - 1:
289
+ time.sleep(wait_time)
290
+ continue
291
+ else:
292
+ # Try next model if available
293
+ break
294
+
295
+ elif response.status_code == 422:
296
+ # Model capacity exceeded, try next model immediately
297
+ logger.warning(f"Model {model_name} capacity exceeded, trying next model")
298
+ break
299
+
300
+ else:
301
+ error_detail = self._extract_error_message(response)
302
+ if attempt == max_retries - 1: # Last attempt
303
+ logger.error(f"API error {response.status_code} with {model_name}: {error_detail}")
304
+ break
305
+ else:
306
+ logger.warning(f"API error {response.status_code} with {model_name}, retrying...")
307
+ time.sleep(2 ** attempt) # Exponential backoff
308
 
309
+ except requests.exceptions.Timeout:
310
+ if attempt == max_retries - 1:
311
+ logger.error(f"Timeout with {model_name} after {max_retries} attempts")
312
+ break
313
+ else:
314
+ logger.warning(f"Timeout with {model_name}, retrying...")
315
+ time.sleep(2 ** attempt)
316
+
317
+ except requests.exceptions.RequestException as e:
318
+ if attempt == max_retries - 1:
319
+ logger.error(f"Network error with {model_name}: {str(e)}")
320
+ break
321
+ else:
322
+ logger.warning(f"Network error with {model_name}, retrying...")
323
+ time.sleep(2 ** attempt)
324
+
325
+ # All models and retries failed
326
+ return {
327
+ "success": False,
328
+ "error": "All models are currently experiencing high demand. Please try again in a few minutes.",
329
+ "suggestion": "Consider upgrading your Mistral AI plan for higher rate limits, or try again during off-peak hours."
330
+ }
331
 
332
  def agent_completion(self, messages: List[Dict]) -> Dict[str, Any]:
333
+ """Enhanced agent completion with retry logic"""
334
  if not self.agent_id:
335
  return {"success": False, "error": "No agent ID configured"}
336
 
337
+ max_retries = 2 # Fewer retries for agent calls
338
+
339
+ for attempt in range(max_retries):
340
+ try:
341
+ self._wait_for_rate_limit()
342
+
343
+ payload = {
344
+ "agent_id": self.agent_id,
345
+ "messages": messages,
346
+ "max_tokens": 800
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  }
348
 
349
+ response = self.session.post(
350
+ f"{self.base_url}/agents/completions",
351
+ json=payload,
352
+ timeout=30
353
+ )
354
+
355
+ if response.status_code == 200:
356
+ result = response.json()
357
+ return {
358
+ "success": True,
359
+ "response": result["choices"][0]["message"]["content"]
360
+ }
361
+ elif response.status_code == 429:
362
+ retry_after = int(response.headers.get('Retry-After', 5))
363
+ if attempt < max_retries - 1:
364
+ logger.warning(f"Agent rate limit, waiting {retry_after}s")
365
+ time.sleep(retry_after)
366
+ continue
367
+ else:
368
+ error_detail = self._extract_error_message(response)
369
+ return {
370
+ "success": False,
371
+ "error": f"Agent API error {response.status_code}: {error_detail}"
372
+ }
373
+
374
+ except Exception as e:
375
+ if attempt == max_retries - 1:
376
+ return {"success": False, "error": f"Agent request failed: {str(e)}"}
377
+ else:
378
+ time.sleep(2)
379
+
380
+ return {"success": False, "error": "Agent request failed after retries"}
381
 
382
  def _extract_error_message(self, response) -> str:
383
  """Extract error message from API response"""
 
389
 
390
  class GlucoBuddyMistralChat:
391
  """
392
+ Enhanced chat interface with better error handling and user feedback
 
393
  """
394
 
395
  def __init__(self, mistral_api_key: str = None, mistral_agent_id: str = None):
396
+ self.mistral_client = EnhancedMistralAPIClient(mistral_api_key, mistral_agent_id)
397
 
398
  # Data properties - these will be set by unified data manager
399
  self.current_user: Optional[DemoUser] = None
 
405
  self.conversation_history = []
406
  self.max_history = 10
407
 
408
+ # Error tracking
409
+ self.consecutive_errors = 0
410
+ self.last_successful_model = None
411
+
412
  self.logger = logging.getLogger(self.__class__.__name__)
413
 
414
  def test_connection(self) -> Dict[str, Any]:
 
489
  return base_prompt + context_addition
490
 
491
  def chat_with_mistral(self, user_message: str, prefer_agent: bool = False) -> Dict[str, Any]:
492
+ """Enhanced chat function with better error handling"""
493
  if not user_message.strip():
494
  return {"success": False, "error": "Please enter a message"}
495
 
 
511
  agent_result = self.mistral_client.agent_completion(messages)
512
  if agent_result["success"]:
513
  self._update_conversation_history(user_message, agent_result["response"])
514
+ self.consecutive_errors = 0 # Reset error counter
515
  return {
516
  "success": True,
517
  "response": agent_result["response"],
 
519
  "context_included": not context.get("error")
520
  }
521
  else:
522
+ self.logger.warning(f"Agent failed: {agent_result['error']}")
523
 
524
+ # Use enhanced chat completion API
525
  chat_result = self.mistral_client.chat_completion(messages)
526
 
527
  if chat_result["success"]:
528
  self._update_conversation_history(user_message, chat_result["response"])
529
+ self.consecutive_errors = 0 # Reset error counter
530
+ self.last_successful_model = chat_result.get("model_used")
531
+
532
+ # Add helpful info about which model was used if there were retries
533
+ response = chat_result["response"]
534
+ if chat_result.get("attempt", 1) > 1:
535
+ response += f"\n\n*Note: Response generated after {chat_result['attempt']} attempts due to high demand.*"
536
+
537
  return {
538
  "success": True,
539
+ "response": response,
540
  "method": "chat_completion",
541
  "context_included": not context.get("error"),
542
+ "usage": chat_result.get("usage", {}),
543
+ "model_used": chat_result.get("model_used")
544
  }
545
  else:
546
+ self.consecutive_errors += 1
547
+
548
+ # Provide helpful error messages based on the type of error
549
+ error_msg = chat_result["error"]
550
+ user_friendly_msg = self._get_user_friendly_error(error_msg)
551
+
552
  return {
553
  "success": False,
554
+ "error": user_friendly_msg,
555
+ "suggestion": chat_result.get("suggestion", ""),
556
+ "consecutive_errors": self.consecutive_errors
557
  }
558
 
559
  except Exception as e:
560
  self.logger.error(f"Chat error: {e}")
561
+ self.consecutive_errors += 1
562
  return {
563
  "success": False,
564
+ "error": f"I'm experiencing technical difficulties. Please try again in a moment.",
565
+ "consecutive_errors": self.consecutive_errors
566
  }
567
 
568
+ def _get_user_friendly_error(self, error_msg: str) -> str:
569
+ """Convert technical error messages to user-friendly ones"""
570
+ error_lower = error_msg.lower()
571
+
572
+ if "rate limit" in error_lower or "429" in error_lower:
573
+ return "I'm experiencing high demand right now. Please wait a moment and try again."
574
+ elif "capacity exceeded" in error_lower or "service tier" in error_lower:
575
+ return "The AI service is currently busy. Please try again in a few minutes."
576
+ elif "timeout" in error_lower:
577
+ return "The response is taking longer than expected. Please try again."
578
+ elif "api key" in error_lower or "401" in error_lower:
579
+ return "There's an authentication issue. Please contact support."
580
+ elif "network" in error_lower:
581
+ return "I'm having trouble connecting. Please check your internet connection and try again."
582
+ else:
583
+ return "I'm experiencing technical difficulties. Please try again or rephrase your question."
584
+
585
  def _update_conversation_history(self, user_message: str, assistant_response: str):
586
  """Update conversation history"""
587
  self.conversation_history.extend([
 
593
  self.conversation_history = self.conversation_history[-self.max_history * 2:]
594
 
595
  def clear_conversation(self):
596
+ """Clear conversation history and reset error counters"""
597
  self.conversation_history = []
598
+ self.consecutive_errors = 0
599
  self.logger.info("Conversation history cleared")
600
 
601
  def get_status(self) -> Dict[str, Any]:
602
+ """Get current system status with enhanced information"""
603
  api_status = self.test_connection()
604
 
605
  return {
 
611
  "current_user": self.current_user.name if self.current_user else None,
612
  "environment": ENVIRONMENT,
613
  "hugging_face_space": bool(os.getenv("SPACE_ID")),
614
+ "agent_available": bool(MISTRAL_AGENT_ID),
615
+ "consecutive_errors": self.consecutive_errors,
616
+ "last_successful_model": self.last_successful_model
617
  }
618
 
619
  def _safe_convert_to_json(self, obj):
 
681
  self.logger.error(f"Error extracting recent readings: {e}")
682
  return []
683
 
684
+ # Update the main.py chat handler to use enhanced error messages
685
+ def enhanced_chat_error_handler(app, message, history):
686
+ """Enhanced error handler for chat interactions"""
687
+ result = app.chat_with_mistral(message, history)
688
+
689
+ if not result[0]: # If message is empty, return as-is
690
+ return result
691
+
692
+ # If there were consecutive errors, add helpful message
693
+ if hasattr(app.mistral_chat, 'consecutive_errors') and app.mistral_chat.consecutive_errors > 2:
694
+ error_help = "\n\nπŸ’‘ *Multiple errors detected. This usually indicates high API demand. Consider trying again later or during off-peak hours.*"
695
+ if result[1] and len(result[1]) > 0:
696
+ last_response = result[1][-1][1] if len(result[1][-1]) > 1 else ""
697
+ if "technical difficulties" in last_response or "try again" in last_response:
698
+ result[1][-1][1] += error_help
699
+
700
+ return result
701
+
702
+ # Legacy compatibility
703
  def create_enhanced_cli():
704
+ """Enhanced CLI with better error handling"""
705
+ print("🩺 GlucoBuddy Chat Interface (Enhanced)")
706
  print("=" * 50)
707
 
 
708
  if not validate_environment():
709
  print("❌ Environment validation failed. Please check your configuration.")
710
  return
711
 
712
  try:
713
  chat = GlucoBuddyMistralChat()
714
+ print("βœ… Enhanced chat system initialized!")
715
  except Exception as e:
716
  print(f"❌ Failed to initialize chat system: {e}")
717
  return
 
723
  if connection_test["success"]:
724
  print(f"βœ… {connection_test['message']}")
725
  else:
726
+ print(f"⚠️ {connection_test['message']}")
727
+ print("πŸ’‘ The chat will still work but may experience rate limits.")
728
+
729
+ print("\nπŸ“‹ Enhanced features:")
730
+ print(" β€’ Automatic retry on rate limits")
731
+ print(" β€’ Model fallback (large β†’ medium β†’ small β†’ tiny)")
732
+ print(" β€’ Better error messages")
733
+ print(" β€’ Smart rate limiting")
734
+
735
+ print("\nπŸ’¬ Start chatting! (Type /quit to exit)")
736
+ print("=" * 50)
 
 
737
 
738
  while True:
739
  try:
 
742
  if not user_input:
743
  continue
744
 
745
+ if user_input == '/quit':
746
+ print("\nπŸ‘‹ Thanks for using GlucoBuddy Enhanced! 🌟")
747
+ break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
 
749
+ print("πŸ€” Processing...")
750
+ result = chat.chat_with_mistral(user_input)
751
 
752
  if result['success']:
753
+ model_info = f" [{result.get('model_used', 'unknown')}]" if result.get('model_used') else ""
754
+ print(f"\nπŸ€– GlucoBuddy{model_info}: {result['response']}")
 
 
 
 
 
 
 
755
  else:
756
+ print(f"\n❌ {result['error']}")
757
+ if result.get('suggestion'):
758
+ print(f"πŸ’‘ {result['suggestion']}")
 
 
 
 
 
 
 
 
 
759
 
760
  except KeyboardInterrupt:
761
+ print("\nπŸ‘‹ Goodbye!")
762
  break
763
  except Exception as e:
764
  print(f"\n❌ Unexpected error: {e}")
 
765
 
766
  def main():
767
+ """Enhanced main function"""
768
+ print("🩺 GlucoBuddy Enhanced - Better Rate Limit Handling")
769
  print("=" * 60)
770
 
 
 
771
  if not validate_environment():
 
 
772
  return
773
 
774
+ print("πŸš€ Enhanced features:")
775
+ print(" βœ… Automatic retry with exponential backoff")
776
+ print(" βœ… Model fallback chain (large β†’ small)")
777
+ print(" βœ… Smart rate limiting")
778
+ print(" βœ… User-friendly error messages")
779
 
780
+ create_enhanced_cli()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
781
 
782
  if __name__ == "__main__":
783
  main()