uumerrr684 commited on
Commit
cd96ee7
·
verified ·
1 Parent(s): 7cf67c9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +610 -446
app.py CHANGED
@@ -5,16 +5,15 @@ import streamlit as st
5
  from datetime import datetime, timedelta
6
  import time
7
  import uuid
8
- import streamlit.components.v1 as components
9
 
10
  # Page configuration
11
  st.set_page_config(
12
  page_title="Chat Flow 🕷",
13
  page_icon="💬",
14
- initial_sidebar_state="expanded"
15
  )
16
 
17
- # Enhanced CSS with chat history styling and BLACK NEW CHAT BUTTON
18
  st.markdown("""
19
  <style>
20
  .stApp {
@@ -41,52 +40,6 @@ st.markdown("""
41
  font-style: italic;
42
  }
43
 
44
- /* NEW CHAT BUTTON - Black background, white text */
45
- .stButton > button[kind="primary"] {
46
- background-color: #000000 !important;
47
- border-color: #000000 !important;
48
- color: #ffffff !important;
49
- }
50
-
51
- .stButton > button[kind="primary"]:hover {
52
- background-color: #333333 !important;
53
- border-color: #333333 !important;
54
- color: #ffffff !important;
55
- }
56
-
57
- .stButton > button[kind="primary"]:active {
58
- background-color: #1a1a1a !important;
59
- border-color: #1a1a1a !important;
60
- color: #ffffff !important;
61
- }
62
-
63
- .stButton > button[kind="primary"]:focus {
64
- background-color: #000000 !important;
65
- border-color: #000000 !important;
66
- color: #ffffff !important;
67
- box-shadow: 0 0 0 0.2rem rgba(0, 0, 0, 0.25) !important;
68
- }
69
-
70
- /* Location styling */
71
- .location-info {
72
- background: #f8f9fa;
73
- padding: 8px 12px;
74
- border-radius: 6px;
75
- margin: 4px 0;
76
- border-left: 3px solid #28a745;
77
- }
78
-
79
- .location-flag {
80
- font-size: 1.2em;
81
- margin-right: 6px;
82
- }
83
-
84
- .distance-info {
85
- color: #666;
86
- font-size: 0.85em;
87
- font-style: italic;
88
- }
89
-
90
  /* Chat history styling */
91
  .chat-history-item {
92
  padding: 8px 12px;
@@ -129,377 +82,11 @@ st.markdown("""
129
 
130
  # File to store chat history
131
  HISTORY_FILE = "chat_history.json"
 
132
  USERS_FILE = "online_users.json"
 
133
  SESSIONS_FILE = "chat_sessions.json"
134
 
135
- # ================= REAL LOCATION DETECTION FUNCTIONS =================
136
-
137
- def get_user_location_browser():
138
- """Get user's real location using browser geolocation API"""
139
-
140
- # JavaScript code to get real user location
141
- location_js = """
142
- <script>
143
- function getUserLocation() {
144
- if (navigator.geolocation) {
145
- navigator.geolocation.getCurrentPosition(
146
- function(position) {
147
- const location = {
148
- latitude: position.coords.latitude,
149
- longitude: position.coords.longitude,
150
- accuracy: position.coords.accuracy,
151
- timestamp: new Date().toISOString()
152
- };
153
-
154
- // Store in sessionStorage for Streamlit to access
155
- sessionStorage.setItem('userLocation', JSON.stringify(location));
156
-
157
- // Trigger a Streamlit rerun
158
- window.parent.postMessage({
159
- type: 'streamlit:componentReady'
160
- }, '*');
161
-
162
- console.log('Location detected:', location);
163
- },
164
- function(error) {
165
- console.error('Geolocation error:', error.message);
166
- const errorInfo = {
167
- error: error.message,
168
- code: error.code,
169
- timestamp: new Date().toISOString()
170
- };
171
- sessionStorage.setItem('locationError', JSON.stringify(errorInfo));
172
- },
173
- {
174
- enableHighAccuracy: true,
175
- timeout: 10000,
176
- maximumAge: 300000 // 5 minutes cache
177
- }
178
- );
179
- } else {
180
- console.error('Geolocation not supported');
181
- sessionStorage.setItem('locationError', JSON.stringify({
182
- error: 'Geolocation not supported by browser',
183
- code: -1,
184
- timestamp: new Date().toISOString()
185
- }));
186
- }
187
- }
188
-
189
- // Auto-start location detection
190
- getUserLocation();
191
-
192
- // Also provide manual trigger
193
- window.getUserLocation = getUserLocation;
194
- </script>
195
-
196
- <div id="location-detector">
197
- <p>🔍 Detecting your real location...</p>
198
- <button onclick="getUserLocation()" style="
199
- background: #28a745;
200
- color: white;
201
- border: none;
202
- padding: 8px 16px;
203
- border-radius: 4px;
204
- cursor: pointer;
205
- ">📍 Allow Location Access</button>
206
- </div>
207
- """
208
-
209
- return location_js
210
-
211
- def reverse_geocode(lat, lon):
212
- """Convert coordinates to address using free APIs"""
213
- try:
214
- # Using Nominatim (OpenStreetMap) - Free and reliable
215
- url = f"https://nominatim.openstreetmap.org/reverse?lat={lat}&lon={lon}&format=json&addressdetails=1"
216
- headers = {'User-Agent': 'ChatFlow-App/1.0'}
217
-
218
- response = requests.get(url, headers=headers, timeout=10)
219
-
220
- if response.status_code == 200:
221
- data = response.json()
222
- address = data.get('address', {})
223
-
224
- location_data = {
225
- 'latitude': lat,
226
- 'longitude': lon,
227
- 'city': address.get('city') or address.get('town') or address.get('village'),
228
- 'region': address.get('state') or address.get('province'),
229
- 'country': address.get('country'),
230
- 'country_code': address.get('country_code', '').upper(),
231
- 'postal': address.get('postcode'),
232
- 'address': data.get('display_name'),
233
- 'accuracy': 'GPS-based (Very High)',
234
- 'timestamp': datetime.now().isoformat(),
235
- 'api_used': 'Nominatim/OpenStreetMap',
236
- 'source': 'Browser Geolocation'
237
- }
238
- return location_data
239
-
240
- except Exception as e:
241
- st.error(f"Reverse geocoding error: {e}")
242
-
243
- return None
244
-
245
- def calculate_distance(lat1, lon1, lat2, lon2):
246
- """Calculate distance between two points in kilometers"""
247
- if not all([lat1, lon1, lat2, lon2]):
248
- return None
249
-
250
- try:
251
- from math import radians, cos, sin, asin, sqrt
252
-
253
- # Convert to radians
254
- lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
255
-
256
- # Haversine formula
257
- dlat = lat2 - lat1
258
- dlon = lon2 - lon1
259
- a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
260
- c = 2 * asin(sqrt(a))
261
- r = 6371 # Earth's radius in kilometers
262
-
263
- distance = c * r
264
- return round(distance, 1)
265
-
266
- except Exception as e:
267
- print(f"Distance calculation error: {e}")
268
- return None
269
-
270
- def get_country_flag(country_code):
271
- """Get emoji flag for country code"""
272
- flags = {
273
- 'US': '🇺🇸', 'UK': '🇬🇧', 'CA': '🇨🇦', 'AU': '🇦🇺', 'DE': '🇩🇪',
274
- 'FR': '🇫🇷', 'IT': '🇮🇹', 'ES': '🇪🇸', 'JP': '🇯🇵', 'CN': '🇨🇳',
275
- 'IN': '🇮🇳', 'BR': '🇧🇷', 'RU': '🇷🇺', 'MX': '🇲🇽', 'NL': '🇳🇱',
276
- 'PK': '🇵🇰', 'BD': '🇧🇩', 'ID': '🇮🇩', 'NG': '🇳🇬', 'TR': '🇹🇷',
277
- 'EG': '🇪🇬', 'ZA': '🇿🇦', 'KR': '🇰🇷', 'TH': '🇹🇭', 'VN': '🇻🇳',
278
- 'PH': '🇵🇭', 'MY': '🇲🇾', 'SG': '🇸🇬', 'AE': '🇦🇪', 'SA': '🇸🇦'
279
- }
280
- return flags.get(country_code, '🌍')
281
-
282
- # ================= USER FUNCTIONS WITH REAL LOCATION =================
283
-
284
- def get_user_id():
285
- """Get unique ID for this user session"""
286
- if 'user_id' not in st.session_state:
287
- st.session_state.user_id = str(uuid.uuid4())[:8]
288
- return st.session_state.user_id
289
-
290
- def update_online_users_with_real_location():
291
- """Update user status with REAL browser-based location"""
292
- try:
293
- # Load current users
294
- users = {}
295
- if os.path.exists(USERS_FILE):
296
- with open(USERS_FILE, 'r') as f:
297
- users = json.load(f)
298
-
299
- user_id = get_user_id()
300
-
301
- # Check if we have real location data from browser
302
- location_data = st.session_state.get(f'real_location_{user_id}')
303
-
304
- # Update user info with real location
305
- users[user_id] = {
306
- 'last_seen': datetime.now().isoformat(),
307
- 'name': f'User-{user_id}',
308
- 'location': location_data,
309
- 'session_start': users.get(user_id, {}).get('session_start', datetime.now().isoformat())
310
- }
311
-
312
- # Clean up old users (not seen in 5 minutes)
313
- current_time = datetime.now()
314
- active_users = {}
315
- for uid, data in users.items():
316
- try:
317
- last_seen = datetime.fromisoformat(data['last_seen'])
318
- if current_time - last_seen < timedelta(minutes=5):
319
- active_users[uid] = data
320
- except:
321
- continue
322
-
323
- # Save updated users
324
- with open(USERS_FILE, 'w') as f:
325
- json.dump(active_users, f, indent=2)
326
-
327
- return len(active_users)
328
-
329
- except Exception as e:
330
- st.error(f"Location tracking error: {e}")
331
- return 1
332
-
333
- def show_real_location_detector():
334
- """Show real location detector interface"""
335
- st.header("🌍 Real Location Detection")
336
-
337
- user_id = get_user_id()
338
-
339
- # Check if we already have location
340
- if f'real_location_{user_id}' in st.session_state:
341
- location = st.session_state[f'real_location_{user_id}']
342
- if location:
343
- flag = get_country_flag(location.get('country_code', ''))
344
- st.success(f"✅ Location detected: {flag} {location.get('city', 'Unknown')}, {location.get('country', 'Unknown')}")
345
- return True
346
-
347
- # Show location detector
348
- st.info("🔍 Click below to detect your real location using browser GPS")
349
-
350
- # Embed JavaScript for real geolocation
351
- location_component = get_user_location_browser()
352
- components.html(location_component, height=100)
353
-
354
- # Manual coordinate input as backup
355
- with st.expander("🛠️ Manual Location Entry (Backup)"):
356
- st.write("If automatic detection doesn't work, enter your coordinates:")
357
-
358
- col1, col2 = st.columns(2)
359
- with col1:
360
- manual_lat = st.number_input("Latitude", value=0.0, format="%.6f")
361
- with col2:
362
- manual_lon = st.number_input("Longitude", value=0.0, format="%.6f")
363
-
364
- if st.button("🎯 Use These Coordinates"):
365
- if manual_lat != 0.0 and manual_lon != 0.0:
366
- location_data = reverse_geocode(manual_lat, manual_lon)
367
- if location_data:
368
- st.session_state[f'real_location_{user_id}'] = location_data
369
- st.success("✅ Manual location set!")
370
- st.rerun()
371
- else:
372
- st.error("❌ Could not resolve coordinates to address")
373
-
374
- return False
375
-
376
- def process_browser_location():
377
- """Process location data from browser JavaScript"""
378
- user_id = get_user_id()
379
-
380
- # This would be called when JavaScript sends location data
381
- # In a real implementation, you'd use Streamlit's session state
382
- # or a custom component to receive the JavaScript data
383
-
384
- # For now, we'll simulate this with manual input
385
- # In production, you'd replace this with actual JavaScript communication
386
- pass
387
-
388
- def show_user_locations():
389
- """Display all user locations with real coordinates"""
390
- st.header("🌍 Who's Online & Where")
391
-
392
- try:
393
- if not os.path.exists(USERS_FILE):
394
- st.info("No user data yet")
395
- return 0
396
-
397
- with open(USERS_FILE, 'r') as f:
398
- users = json.load(f)
399
-
400
- if not users:
401
- st.info("No active users")
402
- return 0
403
-
404
- # Get current user's location for distance calculation
405
- current_user_id = get_user_id()
406
- current_location = st.session_state.get(f'real_location_{current_user_id}')
407
-
408
- online_count = len(users)
409
-
410
- # Show count
411
- if online_count == 1:
412
- st.success("🟢 Just you online")
413
- else:
414
- st.success(f"🟢 {online_count} people online")
415
-
416
- st.divider()
417
-
418
- # Show each user with real location
419
- for user_id, data in users.items():
420
- location = data.get('location')
421
- is_current_user = (user_id == current_user_id)
422
-
423
- # User header
424
- if is_current_user:
425
- st.markdown("**👤 You**")
426
- else:
427
- st.markdown(f"**👤 {data.get('name', user_id)}**")
428
-
429
- if location and location.get('city'):
430
- # Get flag
431
- flag = get_country_flag(location.get('country_code', ''))
432
-
433
- # Location string with accuracy indicator
434
- accuracy = location.get('accuracy', 'Unknown')
435
- location_str = f"{flag} {location['city']}, {location['country']}"
436
-
437
- if 'GPS' in accuracy:
438
- st.markdown(f"📍 **{location_str}** 🎯")
439
- st.caption("✅ Real GPS location")
440
- else:
441
- st.markdown(f"📍 **{location_str}**")
442
- st.caption("⚠️ Approximate location")
443
-
444
- # Calculate REAL distance to other users
445
- if not is_current_user and current_location and current_location.get('latitude'):
446
- distance = calculate_distance(
447
- current_location.get('latitude'),
448
- current_location.get('longitude'),
449
- location.get('latitude'),
450
- location.get('longitude')
451
- )
452
-
453
- if distance:
454
- if distance < 1:
455
- st.caption("📏 Very close to you! (<1km)")
456
- elif distance < 10:
457
- st.caption(f"📏 ~{distance} km from you (same city)")
458
- elif distance < 100:
459
- st.caption(f"📏 ~{distance} km from you (nearby)")
460
- else:
461
- st.caption(f"📏 ~{distance} km from you")
462
-
463
- # Show detailed location info
464
- with st.expander(f"Real Location Details - {user_id}"):
465
- col1, col2 = st.columns(2)
466
-
467
- with col1:
468
- st.write(f"🎯 **Coordinates:** {location.get('latitude', 'N/A'):.6f}, {location.get('longitude', 'N/A'):.6f}")
469
- if location.get('region'):
470
- st.write(f"🏛️ **Region:** {location['region']}")
471
- if location.get('postal'):
472
- st.write(f"📮 **Postal:** {location['postal']}")
473
-
474
- with col2:
475
- st.write(f"🔍 **Source:** {location.get('source', 'Unknown')}")
476
- st.write(f"🎯 **Accuracy:** {location.get('accuracy', 'Unknown')}")
477
- st.write(f"⏰ **Detected:** {location.get('timestamp', 'Unknown')[:16]}")
478
-
479
- else:
480
- st.caption("📍 Location not detected yet")
481
- if is_current_user:
482
- st.caption("👆 Use the location detector above")
483
-
484
- # Show session info
485
- try:
486
- session_start = datetime.fromisoformat(data['session_start'])
487
- duration = datetime.now() - session_start
488
- minutes = int(duration.total_seconds() / 60)
489
- st.caption(f"🕐 Online for {minutes} minutes")
490
- except:
491
- st.caption("🕐 Session time unknown")
492
-
493
- st.divider()
494
-
495
- return online_count
496
-
497
- except Exception as e:
498
- st.error(f"Error showing locations: {e}")
499
- return 0
500
-
501
- # ================= CHAT FUNCTIONS (unchanged) =================
502
- # [All the existing chat functions remain the same]
503
 
504
  def load_chat_history():
505
  """Load chat history from file"""
@@ -511,6 +98,7 @@ def load_chat_history():
511
  st.error(f"Error loading chat history: {e}")
512
  return []
513
 
 
514
  def save_chat_history(messages):
515
  """Save chat history to file"""
516
  try:
@@ -519,6 +107,7 @@ def save_chat_history(messages):
519
  except Exception as e:
520
  st.error(f"Error saving chat history: {e}")
521
 
 
522
  def clear_chat_history():
523
  """Clear chat history file"""
524
  try:
@@ -528,6 +117,8 @@ def clear_chat_history():
528
  except Exception as e:
529
  st.error(f"Error clearing chat history: {e}")
530
 
 
 
531
  def load_chat_sessions():
532
  """Load all chat sessions"""
533
  try:
@@ -538,6 +129,7 @@ def load_chat_sessions():
538
  st.error(f"Error loading chat sessions: {e}")
539
  return {}
540
 
 
541
  def save_chat_sessions(sessions):
542
  """Save chat sessions to file"""
543
  try:
@@ -546,76 +138,648 @@ def save_chat_sessions(sessions):
546
  except Exception as e:
547
  st.error(f"Error saving chat sessions: {e}")
548
 
 
549
  def get_session_id():
550
  """Get or create session ID"""
551
  if 'session_id' not in st.session_state:
552
  st.session_state.session_id = str(uuid.uuid4())
553
  return st.session_state.session_id
554
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
555
  def start_new_chat():
556
  """Start a new chat session"""
 
557
  if st.session_state.messages:
558
  save_current_session()
 
 
559
  st.session_state.messages = []
560
  st.session_state.session_id = str(uuid.uuid4())
561
 
562
- # Initialize session state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  if "messages" not in st.session_state:
564
  st.session_state.messages = load_chat_history()
565
 
 
566
  if "session_id" not in st.session_state:
567
  st.session_state.session_id = str(uuid.uuid4())
568
 
569
  # Get API key
570
  OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
571
 
572
- # ================= MAIN APP WITH REAL LOCATION =================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
 
574
  # Header
575
  st.title("Chat Flow 🕷")
576
  st.caption("10 powerful Models, one simple chat.")
577
 
578
- # Sidebar with REAL LOCATION TRACKING
579
  with st.sidebar:
580
- # New Chat Button (BLACK)
 
 
 
581
  if st.button("➕ New Chat", use_container_width=True, type="primary"):
582
  start_new_chat()
583
  st.rerun()
584
 
585
  st.divider()
586
 
587
- # REAL LOCATION DETECTION
588
- show_real_location_detector()
 
589
 
590
- st.divider()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
 
592
- # Show online users with real locations
593
- online_count = show_user_locations()
594
 
595
- # Update location tracking
596
- update_online_users_with_real_location()
 
597
 
598
- # Quick refresh
599
- if st.button("🔄 Refresh Locations", use_container_width=True):
600
  st.rerun()
601
 
602
  st.divider()
603
-
604
- # Rest of the sidebar (chat sessions, settings, etc.) remains the same
605
- # [All existing sidebar code continues here...]
606
 
607
- # Main chat area remains the same
608
- # [All existing chat code continues here...]
609
 
610
- # Location Status Footer - Updated for real location
611
- user_id = get_user_id()
612
- location = st.session_state.get(f'real_location_{user_id}')
613
- if location and location.get('city'):
614
- flag = get_country_flag(location.get('country_code', ''))
615
- accuracy = location.get('accuracy', 'Unknown')
616
- if 'GPS' in accuracy:
617
- st.caption(f"📍 Your real location: {flag} {location['city']}, {location['country']} | ✅ GPS-accurate")
618
  else:
619
- st.caption(f"📍 Your location: {flag} {location['city']}, {location['country']} | Accuracy: {accuracy}")
620
- else:
621
- st.caption("📍 Real location not detected yet - click 'Allow Location Access' above")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from datetime import datetime, timedelta
6
  import time
7
  import uuid
 
8
 
9
  # Page configuration
10
  st.set_page_config(
11
  page_title="Chat Flow 🕷",
12
  page_icon="💬",
13
+ initial_sidebar_state="expanded" # Changed to show chat history by default
14
  )
15
 
16
+ # Enhanced CSS with chat history styling
17
  st.markdown("""
18
  <style>
19
  .stApp {
 
40
  font-style: italic;
41
  }
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  /* Chat history styling */
44
  .chat-history-item {
45
  padding: 8px 12px;
 
82
 
83
  # File to store chat history
84
  HISTORY_FILE = "chat_history.json"
85
+ # NEW: File to store online users
86
  USERS_FILE = "online_users.json"
87
+ # NEW: File to store chat sessions
88
  SESSIONS_FILE = "chat_sessions.json"
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  def load_chat_history():
92
  """Load chat history from file"""
 
98
  st.error(f"Error loading chat history: {e}")
99
  return []
100
 
101
+
102
  def save_chat_history(messages):
103
  """Save chat history to file"""
104
  try:
 
107
  except Exception as e:
108
  st.error(f"Error saving chat history: {e}")
109
 
110
+
111
  def clear_chat_history():
112
  """Clear chat history file"""
113
  try:
 
117
  except Exception as e:
118
  st.error(f"Error clearing chat history: {e}")
119
 
120
+
121
+ # NEW: Chat Sessions Management
122
  def load_chat_sessions():
123
  """Load all chat sessions"""
124
  try:
 
129
  st.error(f"Error loading chat sessions: {e}")
130
  return {}
131
 
132
+
133
  def save_chat_sessions(sessions):
134
  """Save chat sessions to file"""
135
  try:
 
138
  except Exception as e:
139
  st.error(f"Error saving chat sessions: {e}")
140
 
141
+
142
  def get_session_id():
143
  """Get or create session ID"""
144
  if 'session_id' not in st.session_state:
145
  st.session_state.session_id = str(uuid.uuid4())
146
  return st.session_state.session_id
147
 
148
+
149
+ def get_chat_title(messages):
150
+ """Generate a title for the chat based on conversation content using AI"""
151
+ if not messages:
152
+ return "New Chat"
153
+
154
+ # If only one message, use first 30 characters
155
+ if len(messages) <= 1:
156
+ for msg in messages:
157
+ if msg["role"] == "user":
158
+ content = msg["content"]
159
+ if len(content) > 30:
160
+ return content[:30] + "..."
161
+ return content
162
+ return "New Chat"
163
+
164
+ # If we have a conversation, use AI to generate a smart title
165
+ try:
166
+ return generate_smart_title(messages)
167
+ except:
168
+ # Fallback to first message if AI title generation fails
169
+ for msg in messages:
170
+ if msg["role"] == "user":
171
+ content = msg["content"]
172
+ if len(content) > 30:
173
+ return content[:30] + "..."
174
+ return content
175
+ return "New Chat"
176
+
177
+
178
+ def generate_smart_title(messages):
179
+ """Use AI to generate a smart title for the conversation"""
180
+ if not OPENROUTER_API_KEY:
181
+ # Fallback if no API key
182
+ for msg in messages:
183
+ if msg["role"] == "user":
184
+ content = msg["content"]
185
+ if len(content) > 30:
186
+ return content[:30] + "..."
187
+ return content
188
+ return "New Chat"
189
+
190
+ # Prepare conversation summary for title generation
191
+ conversation_text = ""
192
+ message_count = 0
193
+
194
+ for msg in messages:
195
+ if message_count >= 6: # Limit to first 6 messages for title generation
196
+ break
197
+ if msg["role"] in ["user", "assistant"]:
198
+ role = "User" if msg["role"] == "user" else "Assistant"
199
+ # Clean the message content
200
+ content = msg["content"]
201
+ if "Response created by:" in content:
202
+ content = content.split("\n\n---\n*Response created by:")[0]
203
+ conversation_text += f"{role}: {content[:200]}...\n"
204
+ message_count += 1
205
+
206
+ # Create prompt for title generation
207
+ title_prompt = f"""Based on this conversation, generate a short, descriptive title (2-5 words max):
208
+ {conversation_text}
209
+ Generate only a brief title that captures the main topic. Examples:
210
+ - "Python Code Help"
211
+ - "Recipe Ideas"
212
+ - "Travel Planning"
213
+ - "Math Problem"
214
+ - "Writing Assistance"
215
+ Title:"""
216
+
217
+ url = "https://openrouter.ai/api/v1/chat/completions"
218
+ headers = {
219
+ "Content-Type": "application/json",
220
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
221
+ "HTTP-Referer": "http://localhost:8501",
222
+ "X-Title": "Streamlit AI Assistant"
223
+ }
224
+
225
+ data = {
226
+ "model": "openai/gpt-3.5-turbo", # Use fast model for title generation
227
+ "messages": [{"role": "user", "content": title_prompt}],
228
+ "max_tokens": 20, # Short response
229
+ "temperature": 0.3, # More focused
230
+ "stream": False # Don't stream for title generation
231
+ }
232
+
233
+ try:
234
+ response = requests.post(url, headers=headers, json=data, timeout=10)
235
+ if response.status_code == 200:
236
+ result = response.json()
237
+ title = result["choices"][0]["message"]["content"].strip()
238
+
239
+ # Clean up the title
240
+ title = title.replace('"', '').replace("Title:", "").strip()
241
+
242
+ # Limit length
243
+ if len(title) > 40:
244
+ title = title[:40] + "..."
245
+
246
+ return title if title else "New Chat"
247
+ except Exception as e:
248
+ # If anything fails, use fallback
249
+ pass
250
+
251
+ # Final fallback
252
+ for msg in messages:
253
+ if msg["role"] == "user":
254
+ content = msg["content"]
255
+ if len(content) > 30:
256
+ return content[:30] + "..."
257
+ return content
258
+ return "New Chat"
259
+
260
+
261
+ def save_current_session():
262
+ """Save current chat session with smart AI-generated title"""
263
+ if not st.session_state.messages:
264
+ return
265
+
266
+ sessions = load_chat_sessions()
267
+ session_id = get_session_id()
268
+
269
+ # Generate smart title only if we have meaningful conversation
270
+ # (at least one user message and one assistant response)
271
+ user_messages = [msg for msg in st.session_state.messages if msg["role"] == "user"]
272
+ assistant_messages = [msg for msg in st.session_state.messages if msg["role"] == "assistant"]
273
+
274
+ if len(user_messages) >= 1 and len(assistant_messages) >= 1:
275
+ # We have a real conversation, generate smart title
276
+ title = get_chat_title(st.session_state.messages)
277
+ else:
278
+ # Just starting conversation, use simple title
279
+ title = "New Chat"
280
+ if user_messages:
281
+ first_message = user_messages[0]["content"]
282
+ if len(first_message) > 30:
283
+ title = first_message[:30] + "..."
284
+ else:
285
+ title = first_message
286
+
287
+ sessions[session_id] = {
288
+ "title": title,
289
+ "messages": st.session_state.messages,
290
+ "created_at": sessions.get(session_id, {}).get("created_at", datetime.now().isoformat()),
291
+ "updated_at": datetime.now().isoformat()
292
+ }
293
+
294
+ save_chat_sessions(sessions)
295
+
296
+
297
+ def load_session(session_id):
298
+ """Load a specific chat session"""
299
+ sessions = load_chat_sessions()
300
+ if session_id in sessions:
301
+ st.session_state.messages = sessions[session_id]["messages"]
302
+ st.session_state.session_id = session_id
303
+ return True
304
+ return False
305
+
306
+
307
+ def delete_session(session_id):
308
+ """Delete a chat session"""
309
+ sessions = load_chat_sessions()
310
+ if session_id in sessions:
311
+ del sessions[session_id]
312
+ save_chat_sessions(sessions)
313
+ return True
314
+ return False
315
+
316
+
317
  def start_new_chat():
318
  """Start a new chat session"""
319
+ # Save current session if it has messages
320
  if st.session_state.messages:
321
  save_current_session()
322
+
323
+ # Clear current chat and create new session
324
  st.session_state.messages = []
325
  st.session_state.session_id = str(uuid.uuid4())
326
 
327
+
328
+ # NEW: User tracking functions
329
+ def get_user_id():
330
+ """Get unique ID for this user session"""
331
+ if 'user_id' not in st.session_state:
332
+ st.session_state.user_id = str(uuid.uuid4())[
333
+ :8] # Short ID for family use
334
+ return st.session_state.user_id
335
+
336
+
337
+ def update_online_users():
338
+ """Update that this user is online right now"""
339
+ try:
340
+ # Load current online users
341
+ users = {}
342
+ if os.path.exists(USERS_FILE):
343
+ with open(USERS_FILE, 'r') as f:
344
+ users = json.load(f)
345
+
346
+ # Add/update this user
347
+ user_id = get_user_id()
348
+ users[user_id] = {
349
+ 'last_seen': datetime.now().isoformat(),
350
+ 'name': f'User-{user_id}' # You can customize this
351
+ }
352
+
353
+ # Remove users not seen in last 5 minutes
354
+ current_time = datetime.now()
355
+ active_users = {}
356
+ for uid, data in users.items():
357
+ last_seen = datetime.fromisoformat(data['last_seen'])
358
+ if current_time - last_seen < timedelta(minutes=5):
359
+ active_users[uid] = data
360
+
361
+ # Save updated list
362
+ with open(USERS_FILE, 'w') as f:
363
+ json.dump(active_users, f, indent=2)
364
+
365
+ return len(active_users)
366
+ except Exception:
367
+ return 1 # If error, assume at least you're online
368
+
369
+
370
+ def get_online_count():
371
+ """Get number of people currently online"""
372
+ try:
373
+ if not os.path.exists(USERS_FILE):
374
+ return 0
375
+
376
+ with open(USERS_FILE, 'r') as f:
377
+ users = json.load(f)
378
+
379
+ # Check who's still active (last 5 minutes)
380
+ current_time = datetime.now()
381
+ active_count = 0
382
+ for data in users.values():
383
+ last_seen = datetime.fromisoformat(data['last_seen'])
384
+ if current_time - last_seen < timedelta(minutes=5):
385
+ active_count += 1
386
+
387
+ return active_count
388
+ except Exception:
389
+ return 0
390
+
391
+
392
+ # Initialize session state with saved history
393
  if "messages" not in st.session_state:
394
  st.session_state.messages = load_chat_history()
395
 
396
+ # Initialize session ID
397
  if "session_id" not in st.session_state:
398
  st.session_state.session_id = str(uuid.uuid4())
399
 
400
  # Get API key
401
  OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
402
 
403
+
404
+ @st.cache_data(ttl=300)
405
+ def check_api_status():
406
+ if not OPENROUTER_API_KEY:
407
+ return "No API Key"
408
+ try:
409
+ url = "https://openrouter.ai/api/v1/models"
410
+ headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
411
+ response = requests.get(url, headers=headers, timeout=10)
412
+ return "Connected" if response.status_code == 200 else "Error"
413
+ except:
414
+ return "Error"
415
+
416
+
417
+ def get_ai_response(messages, model="openai/gpt-3.5-turbo"):
418
+ if not OPENROUTER_API_KEY:
419
+ return "No API key found. Please add OPENROUTER_API_KEY to environment variables."
420
+
421
+ url = "https://openrouter.ai/api/v1/chat/completions"
422
+ headers = {
423
+ "Content-Type": "application/json",
424
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
425
+ "HTTP-Referer": "http://localhost:8501", # Optional: Your site URL
426
+ "X-Title": "Streamlit AI Assistant" # Optional: Your app name
427
+ }
428
+
429
+ # Create system message and user messages
430
+ api_messages = [
431
+ {"role": "system", "content": "You are a helpful AI assistant. Provide clear and helpful responses."}]
432
+ api_messages.extend(messages)
433
+
434
+ data = {
435
+ "model": model,
436
+ "messages": api_messages,
437
+ "stream": True,
438
+ "max_tokens": 2000,
439
+ "temperature": 0.7,
440
+ "top_p": 1,
441
+ "frequency_penalty": 0,
442
+ "presence_penalty": 0
443
+ }
444
+
445
+ try:
446
+ response = requests.post(url, headers=headers,
447
+ json=data, stream=True, timeout=60)
448
+
449
+ # Better error handling
450
+ if response.status_code != 200:
451
+ error_detail = ""
452
+ try:
453
+ error_data = response.json()
454
+ error_detail = error_data.get('error', {}).get(
455
+ 'message', f"HTTP {response.status_code}")
456
+ except:
457
+ error_detail = f"HTTP {response.status_code}: {response.reason}"
458
+
459
+ yield f"API Error: {error_detail}. Please try a different model or check your API key."
460
+ return
461
+
462
+ full_response = ""
463
+ buffer = ""
464
+
465
+ # Using your working streaming logic
466
+ for line in response.iter_lines():
467
+ if line:
468
+ # The server sends lines starting with "data: ..."
469
+ if line.startswith(b"data: "):
470
+ data_str = line[len(b"data: "):].decode("utf-8")
471
+ if data_str.strip() == "[DONE]":
472
+ break
473
+ try:
474
+ data = json.loads(data_str)
475
+ delta = data["choices"][0]["delta"].get("content", "")
476
+ if delta:
477
+ full_response += delta
478
+ yield full_response
479
+ except json.JSONDecodeError:
480
+ continue
481
+ except (KeyError, IndexError):
482
+ continue
483
+
484
+ except requests.exceptions.Timeout:
485
+ yield "Request timed out. Please try again with a shorter message or different model."
486
+ except requests.exceptions.ConnectionError:
487
+ yield "Connection error. Please check your internet connection and try again."
488
+ except requests.exceptions.RequestException as e:
489
+ yield f"Request error: {str(e)}. Please try again."
490
+ except Exception as e:
491
+ yield f"Unexpected error: {str(e)}. Please try again or contact support."
492
+
493
 
494
  # Header
495
  st.title("Chat Flow 🕷")
496
  st.caption("10 powerful Models, one simple chat.")
497
 
498
+ # Sidebar with Chat History
499
  with st.sidebar:
500
+ # NEW: Chat History Section at the top
501
+ st.header("💬 Chat History")
502
+
503
+ # New Chat Button
504
  if st.button("➕ New Chat", use_container_width=True, type="primary"):
505
  start_new_chat()
506
  st.rerun()
507
 
508
  st.divider()
509
 
510
+ # Load and display chat sessions
511
+ sessions = load_chat_sessions()
512
+ current_session_id = get_session_id()
513
 
514
+ if sessions:
515
+ st.subheader("Previous Chats")
516
+
517
+ # Sort sessions by updated_at (most recent first)
518
+ sorted_sessions = sorted(
519
+ sessions.items(),
520
+ key=lambda x: x[1].get("updated_at", x[1].get("created_at", "")),
521
+ reverse=True
522
+ )
523
+
524
+ for session_id, session_data in sorted_sessions:
525
+ # Create container for each chat item
526
+ chat_container = st.container()
527
+
528
+ with chat_container:
529
+ # Show current chat with different styling
530
+ if session_id == current_session_id:
531
+ st.markdown(f"🔹 **{session_data['title']}**")
532
+ else:
533
+ col_load, col_delete = st.columns([3, 1])
534
+
535
+ with col_load:
536
+ if st.button(
537
+ f"💭 {session_data['title']}",
538
+ key=f"load_{session_id}",
539
+ use_container_width=True
540
+ ):
541
+ # Save current session before switching
542
+ if st.session_state.messages:
543
+ save_current_session()
544
+
545
+ # Load selected session
546
+ load_session(session_id)
547
+ st.rerun()
548
+
549
+ with col_delete:
550
+ if st.button("🗑️", key=f"delete_{session_id}"):
551
+ delete_session(session_id)
552
+ # If deleted session was current, start new chat
553
+ if session_id == current_session_id:
554
+ start_new_chat()
555
+ st.rerun()
556
+
557
+ # Show session info
558
+ if "updated_at" in session_data:
559
+ update_time = datetime.fromisoformat(session_data["updated_at"])
560
+ st.caption(f"Updated: {update_time.strftime('%m/%d %H:%M')}")
561
+
562
+ st.markdown("---")
563
 
564
+ else:
565
+ st.info("No previous chats yet")
566
 
567
+ # Auto-save current session periodically
568
+ if st.session_state.messages:
569
+ save_current_session()
570
 
571
+ # Auto-refresh the sidebar every few seconds to show latest sessions
572
+ if st.button("🔄", help="Refresh chat list", use_container_width=False):
573
  st.rerun()
574
 
575
  st.divider()
 
 
 
576
 
577
+ # Settings Section
578
+ st.header("Settings")
579
 
580
+ # API Status
581
+ status = check_api_status()
582
+ if status == "Connected":
583
+ st.success("🟢 API Connected")
584
+ elif status == "No API Key":
585
+ st.error("No API Key")
 
 
586
  else:
587
+ st.warning("Connection Issue")
588
+
589
+ st.divider()
590
+
591
+ # NEW: Live Users Section
592
+ st.header("👥 Who's Online")
593
+
594
+ # Update that you're online
595
+ online_count = update_online_users()
596
+
597
+ # Show live count
598
+ if online_count == 1:
599
+ st.info("🟢 Just you online")
600
+ else:
601
+ st.success(f"🟢 {online_count} people online")
602
+
603
+ # Show your session
604
+ your_id = get_user_id()
605
+ st.caption(f"You: User-{your_id}")
606
+
607
+ # Quick refresh button
608
+ if st.button("Refresh", use_container_width=True):
609
+ st.rerun()
610
+
611
+ # === NEW: DEBUG SECTION ===
612
+ with st.expander("🔍 Debug Info"):
613
+ if os.path.exists(USERS_FILE):
614
+ with open(USERS_FILE, 'r') as f:
615
+ users = json.load(f)
616
+ st.write(f"Users in file: {len(users)}")
617
+ for uid, data in users.items():
618
+ last_seen_time = datetime.fromisoformat(data['last_seen'])
619
+ time_ago = datetime.now() - last_seen_time
620
+ minutes_ago = int(time_ago.total_seconds() / 60)
621
+ st.write(f"- {uid}: {minutes_ago} min ago")
622
+ else:
623
+ st.write("No users file yet")
624
+ # === END DEBUG SECTION ===
625
+
626
+ st.divider()
627
+
628
+ # All models including new ones
629
+ models = [
630
+ ("GPT-3.5 Turbo", "openai/gpt-3.5-turbo"),
631
+ ("LLaMA 3.1 8B", "meta-llama/llama-3.1-8b-instruct"),
632
+ ("LLaMA 3.1 70B", "meta-llama/llama-3.1-70b-instruct"),
633
+ ("DeepSeek Chat v3", "deepseek/deepseek-chat-v3-0324:free"),
634
+ ("DeepSeek R1", "deepseek/deepseek-r1-0528:free"),
635
+ ("Qwen3 Coder", "qwen/qwen3-coder:free"),
636
+ ("Microsoft MAI DS R1", "microsoft/mai-ds-r1:free"),
637
+ ("Gemma 3 27B", "google/gemma-3-27b-it:free"),
638
+ ("Gemma 3 4B", "google/gemma-3-4b-it:free"),
639
+ ("Auto (Best Available)", "openrouter/auto")
640
+ ]
641
+
642
+ model_names = [name for name, _ in models]
643
+ model_ids = [model_id for _, model_id in models]
644
+
645
+ selected_index = st.selectbox("Model", range(len(model_names)),
646
+ format_func=lambda x: model_names[x],
647
+ index=0)
648
+ selected_model = model_ids[selected_index]
649
+
650
+ # Show selected model ID in green
651
+ st.markdown(
652
+ f"**Model ID:** <span class='model-id'>{selected_model}</span>", unsafe_allow_html=True)
653
+
654
+ st.divider()
655
+
656
+ # Chat History Controls
657
+ st.header("Chat History")
658
+
659
+ # Show number of messages
660
+ if st.session_state.messages:
661
+ st.info(f"Messages stored: {len(st.session_state.messages)}")
662
+
663
+ # Auto-save toggle
664
+ auto_save = st.checkbox("Auto-save messages", value=True)
665
+
666
+ # Manual save/load buttons
667
+ col1, col2 = st.columns(2)
668
+ with col1:
669
+ if st.button("Save History", use_container_width=True):
670
+ save_chat_history(st.session_state.messages)
671
+ st.success("History saved!")
672
+
673
+ with col2:
674
+ if st.button("Load History", use_container_width=True):
675
+ st.session_state.messages = load_chat_history()
676
+ st.success("History loaded!")
677
+ st.rerun()
678
+
679
+ st.divider()
680
+
681
+ # View History
682
+ if st.button("View History File", use_container_width=True):
683
+ if os.path.exists(HISTORY_FILE):
684
+ with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
685
+ history_content = f.read()
686
+ st.text_area("Chat History (JSON)", history_content, height=200)
687
+ else:
688
+ st.warning("No history file found")
689
+
690
+ # Download History
691
+ if os.path.exists(HISTORY_FILE):
692
+ with open(HISTORY_FILE, 'rb') as f:
693
+ st.download_button(
694
+ label="Download History",
695
+ data=f.read(),
696
+ file_name=f"chat_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
697
+ mime="application/json",
698
+ use_container_width=True
699
+ )
700
+
701
+ st.divider()
702
+
703
+ # Clear controls
704
+ if st.button("Clear Chat", use_container_width=True, type="secondary"):
705
+ clear_chat_history()
706
+ st.success("Chat cleared!")
707
+ st.rerun()
708
+
709
+ # Show welcome message when no messages
710
+
711
+ # Display chat messages
712
+ for message in st.session_state.messages:
713
+ with st.chat_message(message["role"]):
714
+ # Check if this is an assistant message with attribution
715
+ if message["role"] == "assistant" and "Response created by:" in message["content"]:
716
+ # Split content and attribution
717
+ parts = message["content"].split("\n\n---\n*Response created by:")
718
+ main_content = parts[0]
719
+ if len(parts) > 1:
720
+ model_name = parts[1].replace("***", "").replace("**", "")
721
+ st.markdown(main_content)
722
+ st.markdown(
723
+ f"<div class='model-attribution'>Response created by: <strong>{model_name}</strong></div>", unsafe_allow_html=True)
724
+ else:
725
+ st.markdown(message["content"])
726
+ else:
727
+ st.markdown(message["content"])
728
+
729
+ # Chat input - MUST be at the main level, not inside sidebar or columns
730
+ if prompt := st.chat_input("Chat Smarter. Chat many Brains"):
731
+ # NEW: Update online status when user sends message
732
+ update_online_users()
733
+
734
+ # Add user message
735
+ user_message = {"role": "user", "content": prompt}
736
+ st.session_state.messages.append(user_message)
737
+
738
+ # Auto-save if enabled
739
+ if 'auto_save' not in locals():
740
+ auto_save = True # Default value if not set in sidebar
741
+
742
+ if auto_save:
743
+ save_chat_history(st.session_state.messages)
744
+
745
+ # ALWAYS auto-save the current session after each user message
746
+ save_current_session()
747
+
748
+ # Display user message
749
+ with st.chat_message("user"):
750
+ st.markdown(prompt)
751
+
752
+ # Get AI response
753
+ with st.chat_message("assistant"):
754
+ placeholder = st.empty()
755
+
756
+ full_response = ""
757
+ try:
758
+ for response in get_ai_response(st.session_state.messages, selected_model):
759
+ full_response = response
760
+ placeholder.markdown(full_response + "▌")
761
+
762
+ # Remove cursor and show final response
763
+ placeholder.markdown(full_response)
764
+
765
+ except Exception as e:
766
+ error_msg = f"An error occurred: {str(e)}"
767
+ placeholder.markdown(error_msg)
768
+ full_response = error_msg
769
+
770
+ # Add AI response to messages with attribution
771
+ full_response_with_attribution = full_response + \
772
+ f"\n\n---\n*Response created by: **{model_names[selected_index]}***"
773
+ assistant_message = {"role": "assistant",
774
+ "content": full_response_with_attribution}
775
+ st.session_state.messages.append(assistant_message)
776
+
777
+ # Auto-save if enabled
778
+ if auto_save:
779
+ save_chat_history(st.session_state.messages)
780
+
781
+ # ALWAYS auto-save the current session after each AI response
782
+ save_current_session()
783
+
784
+ # Show currently using model
785
+ st.caption(f"Currently using: **{model_names[selected_index]}**")