uumerrr684 commited on
Commit
c3f896e
·
verified ·
1 Parent(s): 2079a51

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +440 -228
app.py CHANGED
@@ -10,7 +10,7 @@ import uuid
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 and BLACK NEW CHAT BUTTON
@@ -66,6 +66,26 @@ st.markdown("""
66
  box-shadow: 0 0 0 0.2rem rgba(0, 0, 0, 0.25) !important;
67
  }
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  /* Chat history styling */
70
  .chat-history-item {
71
  padding: 8px 12px;
@@ -108,11 +128,316 @@ st.markdown("""
108
 
109
  # File to store chat history
110
  HISTORY_FILE = "chat_history.json"
111
- # NEW: File to store online users
112
  USERS_FILE = "online_users.json"
113
- # NEW: File to store chat sessions
114
  SESSIONS_FILE = "chat_sessions.json"
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  def load_chat_history():
118
  """Load chat history from file"""
@@ -124,7 +449,6 @@ def load_chat_history():
124
  st.error(f"Error loading chat history: {e}")
125
  return []
126
 
127
-
128
  def save_chat_history(messages):
129
  """Save chat history to file"""
130
  try:
@@ -133,7 +457,6 @@ def save_chat_history(messages):
133
  except Exception as e:
134
  st.error(f"Error saving chat history: {e}")
135
 
136
-
137
  def clear_chat_history():
138
  """Clear chat history file"""
139
  try:
@@ -143,8 +466,6 @@ def clear_chat_history():
143
  except Exception as e:
144
  st.error(f"Error clearing chat history: {e}")
145
 
146
-
147
- # NEW: Chat Sessions Management
148
  def load_chat_sessions():
149
  """Load all chat sessions"""
150
  try:
@@ -155,7 +476,6 @@ def load_chat_sessions():
155
  st.error(f"Error loading chat sessions: {e}")
156
  return {}
157
 
158
-
159
  def save_chat_sessions(sessions):
160
  """Save chat sessions to file"""
161
  try:
@@ -164,20 +484,17 @@ def save_chat_sessions(sessions):
164
  except Exception as e:
165
  st.error(f"Error saving chat sessions: {e}")
166
 
167
-
168
  def get_session_id():
169
  """Get or create session ID"""
170
  if 'session_id' not in st.session_state:
171
  st.session_state.session_id = str(uuid.uuid4())
172
  return st.session_state.session_id
173
 
174
-
175
  def get_chat_title(messages):
176
  """Generate a title for the chat based on conversation content using AI"""
177
  if not messages:
178
  return "New Chat"
179
 
180
- # If only one message, use first 30 characters
181
  if len(messages) <= 1:
182
  for msg in messages:
183
  if msg["role"] == "user":
@@ -187,11 +504,9 @@ def get_chat_title(messages):
187
  return content
188
  return "New Chat"
189
 
190
- # If we have a conversation, use AI to generate a smart title
191
  try:
192
  return generate_smart_title(messages)
193
  except:
194
- # Fallback to first message if AI title generation fails
195
  for msg in messages:
196
  if msg["role"] == "user":
197
  content = msg["content"]
@@ -200,11 +515,9 @@ def get_chat_title(messages):
200
  return content
201
  return "New Chat"
202
 
203
-
204
  def generate_smart_title(messages):
205
  """Use AI to generate a smart title for the conversation"""
206
  if not OPENROUTER_API_KEY:
207
- # Fallback if no API key
208
  for msg in messages:
209
  if msg["role"] == "user":
210
  content = msg["content"]
@@ -213,23 +526,20 @@ def generate_smart_title(messages):
213
  return content
214
  return "New Chat"
215
 
216
- # Prepare conversation summary for title generation
217
  conversation_text = ""
218
  message_count = 0
219
 
220
  for msg in messages:
221
- if message_count >= 6: # Limit to first 6 messages for title generation
222
  break
223
  if msg["role"] in ["user", "assistant"]:
224
  role = "User" if msg["role"] == "user" else "Assistant"
225
- # Clean the message content
226
  content = msg["content"]
227
  if "Response created by:" in content:
228
  content = content.split("\n\n---\n*Response created by:")[0]
229
  conversation_text += f"{role}: {content[:200]}...\n"
230
  message_count += 1
231
 
232
- # Create prompt for title generation
233
  title_prompt = f"""Based on this conversation, generate a short, descriptive title (2-5 words max):
234
 
235
  {conversation_text}
@@ -252,11 +562,11 @@ Title:"""
252
  }
253
 
254
  data = {
255
- "model": "openai/gpt-3.5-turbo", # Use fast model for title generation
256
  "messages": [{"role": "user", "content": title_prompt}],
257
- "max_tokens": 20, # Short response
258
- "temperature": 0.3, # More focused
259
- "stream": False # Don't stream for title generation
260
  }
261
 
262
  try:
@@ -264,20 +574,13 @@ Title:"""
264
  if response.status_code == 200:
265
  result = response.json()
266
  title = result["choices"][0]["message"]["content"].strip()
267
-
268
- # Clean up the title
269
  title = title.replace('"', '').replace("Title:", "").strip()
270
-
271
- # Limit length
272
  if len(title) > 40:
273
  title = title[:40] + "..."
274
-
275
  return title if title else "New Chat"
276
  except Exception as e:
277
- # If anything fails, use fallback
278
  pass
279
 
280
- # Final fallback
281
  for msg in messages:
282
  if msg["role"] == "user":
283
  content = msg["content"]
@@ -286,7 +589,6 @@ Title:"""
286
  return content
287
  return "New Chat"
288
 
289
-
290
  def save_current_session():
291
  """Save current chat session with smart AI-generated title"""
292
  if not st.session_state.messages:
@@ -295,16 +597,12 @@ def save_current_session():
295
  sessions = load_chat_sessions()
296
  session_id = get_session_id()
297
 
298
- # Generate smart title only if we have meaningful conversation
299
- # (at least one user message and one assistant response)
300
  user_messages = [msg for msg in st.session_state.messages if msg["role"] == "user"]
301
  assistant_messages = [msg for msg in st.session_state.messages if msg["role"] == "assistant"]
302
 
303
  if len(user_messages) >= 1 and len(assistant_messages) >= 1:
304
- # We have a real conversation, generate smart title
305
  title = get_chat_title(st.session_state.messages)
306
  else:
307
- # Just starting conversation, use simple title
308
  title = "New Chat"
309
  if user_messages:
310
  first_message = user_messages[0]["content"]
@@ -322,7 +620,6 @@ def save_current_session():
322
 
323
  save_chat_sessions(sessions)
324
 
325
-
326
  def load_session(session_id):
327
  """Load a specific chat session"""
328
  sessions = load_chat_sessions()
@@ -332,7 +629,6 @@ def load_session(session_id):
332
  return True
333
  return False
334
 
335
-
336
  def delete_session(session_id):
337
  """Delete a chat session"""
338
  sessions = load_chat_sessions()
@@ -342,93 +638,23 @@ def delete_session(session_id):
342
  return True
343
  return False
344
 
345
-
346
  def start_new_chat():
347
  """Start a new chat session"""
348
- # Save current session if it has messages
349
  if st.session_state.messages:
350
  save_current_session()
351
-
352
- # Clear current chat and create new session
353
  st.session_state.messages = []
354
  st.session_state.session_id = str(uuid.uuid4())
355
 
356
-
357
- # NEW: User tracking functions
358
- def get_user_id():
359
- """Get unique ID for this user session"""
360
- if 'user_id' not in st.session_state:
361
- st.session_state.user_id = str(uuid.uuid4())[:8] # Short ID for family use
362
- return st.session_state.user_id
363
-
364
-
365
- def update_online_users():
366
- """Update that this user is online right now"""
367
- try:
368
- # Load current online users
369
- users = {}
370
- if os.path.exists(USERS_FILE):
371
- with open(USERS_FILE, 'r') as f:
372
- users = json.load(f)
373
-
374
- # Add/update this user
375
- user_id = get_user_id()
376
- users[user_id] = {
377
- 'last_seen': datetime.now().isoformat(),
378
- 'name': f'User-{user_id}' # You can customize this
379
- }
380
-
381
- # Remove users not seen in last 5 minutes
382
- current_time = datetime.now()
383
- active_users = {}
384
- for uid, data in users.items():
385
- last_seen = datetime.fromisoformat(data['last_seen'])
386
- if current_time - last_seen < timedelta(minutes=5):
387
- active_users[uid] = data
388
-
389
- # Save updated list
390
- with open(USERS_FILE, 'w') as f:
391
- json.dump(active_users, f, indent=2)
392
-
393
- return len(active_users)
394
- except Exception:
395
- return 1 # If error, assume at least you're online
396
-
397
-
398
- def get_online_count():
399
- """Get number of people currently online"""
400
- try:
401
- if not os.path.exists(USERS_FILE):
402
- return 0
403
-
404
- with open(USERS_FILE, 'r') as f:
405
- users = json.load(f)
406
-
407
- # Check who's still active (last 5 minutes)
408
- current_time = datetime.now()
409
- active_count = 0
410
- for data in users.values():
411
- last_seen = datetime.fromisoformat(data['last_seen'])
412
- if current_time - last_seen < timedelta(minutes=5):
413
- active_count += 1
414
-
415
- return active_count
416
- except Exception:
417
- return 0
418
-
419
-
420
- # Initialize session state with saved history
421
  if "messages" not in st.session_state:
422
  st.session_state.messages = load_chat_history()
423
 
424
- # Initialize session ID
425
  if "session_id" not in st.session_state:
426
  st.session_state.session_id = str(uuid.uuid4())
427
 
428
  # Get API key
429
  OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
430
 
431
-
432
  @st.cache_data(ttl=300)
433
  def check_api_status():
434
  if not OPENROUTER_API_KEY:
@@ -441,7 +667,6 @@ def check_api_status():
441
  except:
442
  return "Error"
443
 
444
-
445
  def get_ai_response(messages, model="openai/gpt-3.5-turbo"):
446
  if not OPENROUTER_API_KEY:
447
  return "No API key found. Please add OPENROUTER_API_KEY to environment variables."
@@ -450,11 +675,10 @@ def get_ai_response(messages, model="openai/gpt-3.5-turbo"):
450
  headers = {
451
  "Content-Type": "application/json",
452
  "Authorization": f"Bearer {OPENROUTER_API_KEY}",
453
- "HTTP-Referer": "http://localhost:8501", # Optional: Your site URL
454
- "X-Title": "Streamlit AI Assistant" # Optional: Your app name
455
  }
456
 
457
- # Create system message and user messages
458
  api_messages = [
459
  {"role": "system", "content": "You are a helpful AI assistant. Provide clear and helpful responses."}]
460
  api_messages.extend(messages)
@@ -471,29 +695,21 @@ def get_ai_response(messages, model="openai/gpt-3.5-turbo"):
471
  }
472
 
473
  try:
474
- response = requests.post(url, headers=headers,
475
- json=data, stream=True, timeout=60)
476
 
477
- # Better error handling
478
  if response.status_code != 200:
479
  error_detail = ""
480
  try:
481
  error_data = response.json()
482
- error_detail = error_data.get('error', {}).get(
483
- 'message', f"HTTP {response.status_code}")
484
  except:
485
  error_detail = f"HTTP {response.status_code}: {response.reason}"
486
-
487
  yield f"API Error: {error_detail}. Please try a different model or check your API key."
488
  return
489
 
490
  full_response = ""
491
- buffer = ""
492
-
493
- # Using your working streaming logic
494
  for line in response.iter_lines():
495
  if line:
496
- # The server sends lines starting with "data: ..."
497
  if line.startswith(b"data: "):
498
  data_str = line[len(b"data: "):].decode("utf-8")
499
  if data_str.strip() == "[DONE]":
@@ -504,9 +720,7 @@ def get_ai_response(messages, model="openai/gpt-3.5-turbo"):
504
  if delta:
505
  full_response += delta
506
  yield full_response
507
- except json.JSONDecodeError:
508
- continue
509
- except (KeyError, IndexError):
510
  continue
511
 
512
  except requests.exceptions.Timeout:
@@ -518,91 +732,73 @@ def get_ai_response(messages, model="openai/gpt-3.5-turbo"):
518
  except Exception as e:
519
  yield f"Unexpected error: {str(e)}. Please try again or contact support."
520
 
 
521
 
522
  # Header
523
  st.title("Chat Flow 🕷")
524
  st.caption("10 powerful Models, one simple chat.")
525
 
526
- # Sidebar for chat management and settings
527
  with st.sidebar:
528
- # New Chat Button - NOW WITH BLACK STYLING
529
  if st.button("➕ New Chat", use_container_width=True, type="primary"):
530
  start_new_chat()
531
  st.rerun()
532
 
533
  st.divider()
534
 
535
- # Load and display chat sessions
 
 
 
 
 
 
 
 
 
 
 
 
536
  sessions = load_chat_sessions()
537
  current_session_id = get_session_id()
538
 
539
  if sessions:
540
  st.subheader("Previous Chats")
541
-
542
- # Sort sessions by updated_at (most recent first)
543
- sorted_sessions = sorted(
544
- sessions.items(),
545
- key=lambda x: x[1].get("updated_at", x[1].get("created_at", "")),
546
- reverse=True
547
- )
548
 
549
  for session_id, session_data in sorted_sessions:
550
- # Create container for each chat item
551
- chat_container = st.container()
552
-
553
- with chat_container:
554
- # Show current chat with different styling
555
- if session_id == current_session_id:
556
- st.markdown(f"🔹 **{session_data['title']}**")
557
- else:
558
- col_load, col_delete = st.columns([3, 1])
559
-
560
- with col_load:
561
- if st.button(
562
- f"💭 {session_data['title']}",
563
- key=f"load_{session_id}",
564
- use_container_width=True
565
- ):
566
- # Save current session before switching
567
- if st.session_state.messages:
568
- save_current_session()
569
-
570
- # Load selected session
571
- load_session(session_id)
572
- st.rerun()
573
-
574
- with col_delete:
575
- if st.button("✕", key=f"delete_{session_id}"):
576
- delete_session(session_id)
577
- # If deleted session was current, start new chat
578
- if session_id == current_session_id:
579
- start_new_chat()
580
- st.rerun()
581
 
582
- # Show session info
583
  if "updated_at" in session_data:
584
  update_time = datetime.fromisoformat(session_data["updated_at"])
585
  st.caption(f"Updated: {update_time.strftime('%m/%d %H:%M')}")
586
-
587
  st.markdown("---")
588
-
589
  else:
590
  st.info("No previous chats yet")
591
 
592
- # Auto-save current session periodically
593
  if st.session_state.messages:
594
  save_current_session()
595
 
596
- # Auto-refresh the sidebar every few seconds to show latest sessions
597
- if st.button("🔄", help="Refresh chat list", use_container_width=False):
598
- st.rerun()
599
-
600
  st.divider()
601
 
602
  # Settings Section
603
  st.header("Settings")
604
-
605
- # API Status
606
  status = check_api_status()
607
  if status == "Connected":
608
  st.success("🟢 API Connected")
@@ -613,44 +809,7 @@ with st.sidebar:
613
 
614
  st.divider()
615
 
616
- # NEW: Live Users Section
617
- st.header("👥 Who's Online")
618
-
619
- # Update that you're online
620
- online_count = update_online_users()
621
-
622
- # Show live count
623
- if online_count == 1:
624
- st.info("🟢 Just you online")
625
- else:
626
- st.success(f"🟢 {online_count} people online")
627
-
628
- # Show your session
629
- your_id = get_user_id()
630
- st.caption(f"You: User-{your_id}")
631
-
632
- # Quick refresh button
633
- if st.button("Refresh", use_container_width=True):
634
- st.rerun()
635
-
636
- # === NEW: DEBUG SECTION ===
637
- with st.expander("🔍 Debug Info"):
638
- if os.path.exists(USERS_FILE):
639
- with open(USERS_FILE, 'r') as f:
640
- users = json.load(f)
641
- st.write(f"Users in file: {len(users)}")
642
- for uid, data in users.items():
643
- last_seen_time = datetime.fromisoformat(data['last_seen'])
644
- time_ago = datetime.now() - last_seen_time
645
- minutes_ago = int(time_ago.total_seconds() / 60)
646
- st.write(f"- {uid}: {minutes_ago} min ago")
647
- else:
648
- st.write("No users file yet")
649
- # === END DEBUG SECTION ===
650
-
651
- st.divider()
652
-
653
- # All models including new ones
654
  models = [
655
  ("GPT-3.5 Turbo", "openai/gpt-3.5-turbo"),
656
  ("LLaMA 3.1 8B", "meta-llama/llama-3.1-8b-instruct"),
@@ -673,8 +832,51 @@ with st.sidebar:
673
  selected_model = model_ids[selected_index]
674
 
675
  # Show selected model ID in green
676
- st.markdown(
677
- f"**Model ID:** <span class='model-id'>{selected_model}</span>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
 
679
  st.divider()
680
 
@@ -731,6 +933,8 @@ with st.sidebar:
731
  st.success("Chat cleared!")
732
  st.rerun()
733
 
 
 
734
  # Display chat messages
735
  for message in st.session_state.messages:
736
  with st.chat_message(message["role"]):
@@ -743,16 +947,17 @@ for message in st.session_state.messages:
743
  model_name = parts[1].replace("***", "").replace("**", "")
744
  st.markdown(main_content)
745
  st.markdown(
746
- f"<div class='model-attribution'>Response created by: <strong>{model_name}</strong></div>", unsafe_allow_html=True)
 
747
  else:
748
  st.markdown(message["content"])
749
  else:
750
  st.markdown(message["content"])
751
 
752
- # Chat input - MUST be at the main level, not inside sidebar or columns
753
  if prompt := st.chat_input("Chat Smarter. Chat many Brains"):
754
- # NEW: Update online status when user sends message
755
- update_online_users()
756
 
757
  # Add user message
758
  user_message = {"role": "user", "content": prompt}
@@ -760,12 +965,12 @@ if prompt := st.chat_input("Chat Smarter. Chat many Brains"):
760
 
761
  # Auto-save if enabled
762
  if 'auto_save' not in locals():
763
- auto_save = True # Default value if not set in sidebar
764
 
765
  if auto_save:
766
  save_chat_history(st.session_state.messages)
767
 
768
- # ALWAYS auto-save the current session after each user message
769
  save_current_session()
770
 
771
  # Display user message
@@ -791,18 +996,25 @@ if prompt := st.chat_input("Chat Smarter. Chat many Brains"):
791
  full_response = error_msg
792
 
793
  # Add AI response to messages with attribution
794
- full_response_with_attribution = full_response + \
795
- f"\n\n---\n*Response created by: **{model_names[selected_index]}***"
796
- assistant_message = {"role": "assistant",
797
- "content": full_response_with_attribution}
798
  st.session_state.messages.append(assistant_message)
799
 
800
  # Auto-save if enabled
801
  if auto_save:
802
  save_chat_history(st.session_state.messages)
803
 
804
- # ALWAYS auto-save the current session after each AI response
805
  save_current_session()
806
 
807
  # Show currently using model
808
- st.caption(f"Currently using: **{model_names[selected_index]}**")
 
 
 
 
 
 
 
 
 
 
10
  st.set_page_config(
11
  page_title="Chat Flow 🕷",
12
  page_icon="💬",
13
+ initial_sidebar_state="expanded"
14
  )
15
 
16
  # Enhanced CSS with chat history styling and BLACK NEW CHAT BUTTON
 
66
  box-shadow: 0 0 0 0.2rem rgba(0, 0, 0, 0.25) !important;
67
  }
68
 
69
+ /* Location styling */
70
+ .location-info {
71
+ background: #f8f9fa;
72
+ padding: 8px 12px;
73
+ border-radius: 6px;
74
+ margin: 4px 0;
75
+ border-left: 3px solid #28a745;
76
+ }
77
+
78
+ .location-flag {
79
+ font-size: 1.2em;
80
+ margin-right: 6px;
81
+ }
82
+
83
+ .distance-info {
84
+ color: #666;
85
+ font-size: 0.85em;
86
+ font-style: italic;
87
+ }
88
+
89
  /* Chat history styling */
90
  .chat-history-item {
91
  padding: 8px 12px;
 
128
 
129
  # File to store chat history
130
  HISTORY_FILE = "chat_history.json"
 
131
  USERS_FILE = "online_users.json"
 
132
  SESSIONS_FILE = "chat_sessions.json"
133
 
134
+ # ================= LOCATION TRACKING FUNCTIONS =================
135
+
136
+ def get_user_ip():
137
+ """Get user's IP address from multiple sources"""
138
+ try:
139
+ # Try multiple IP services in case one fails
140
+ services = [
141
+ 'https://api.ipify.org?format=json',
142
+ 'https://httpbin.org/ip',
143
+ 'https://api.myip.com',
144
+ 'https://ipinfo.io/json'
145
+ ]
146
+
147
+ for service in services:
148
+ try:
149
+ response = requests.get(service, timeout=5)
150
+ if response.status_code == 200:
151
+ data = response.json()
152
+ # Different services return IP in different field names
153
+ ip = data.get('ip') or data.get('origin') or data.get('query')
154
+ if ip:
155
+ return ip
156
+ except:
157
+ continue
158
+
159
+ # Fallback: Try to get from Streamlit context (may not always work)
160
+ return None
161
+
162
+ except Exception as e:
163
+ print(f"IP detection error: {e}")
164
+ return None
165
+
166
+ def get_location_from_ip(ip_address):
167
+ """Get detailed location from IP address - Works for 700km+ distances!"""
168
+ if not ip_address or ip_address == '127.0.0.1':
169
+ return None
170
+
171
+ try:
172
+ # Using ipapi.co - FREE tier: 1000 requests/day
173
+ # Very accurate for long distances (700km+ will definitely work)
174
+ url = f"https://ipapi.co/{ip_address}/json/"
175
+ response = requests.get(url, timeout=10)
176
+
177
+ if response.status_code == 200:
178
+ data = response.json()
179
+
180
+ # Check if we got valid data
181
+ if data.get('city') and data.get('country_name'):
182
+ location_data = {
183
+ 'ip': ip_address,
184
+ 'city': data.get('city'),
185
+ 'region': data.get('region'),
186
+ 'country': data.get('country_name'),
187
+ 'country_code': data.get('country_code'),
188
+ 'latitude': data.get('latitude'),
189
+ 'longitude': data.get('longitude'),
190
+ 'timezone': data.get('timezone'),
191
+ 'isp': data.get('org'),
192
+ 'postal': data.get('postal'),
193
+ 'accuracy': 'City-level (~10-50km)',
194
+ 'timestamp': datetime.now().isoformat(),
195
+ 'api_used': 'ipapi.co'
196
+ }
197
+ return location_data
198
+
199
+ # Fallback API: ip-api.com (also free, 1000 requests/hour)
200
+ fallback_url = f"http://ip-api.com/json/{ip_address}"
201
+ response = requests.get(fallback_url, timeout=10)
202
+
203
+ if response.status_code == 200:
204
+ data = response.json()
205
+ if data.get('status') == 'success':
206
+ return {
207
+ 'ip': ip_address,
208
+ 'city': data.get('city'),
209
+ 'region': data.get('regionName'),
210
+ 'country': data.get('country'),
211
+ 'country_code': data.get('countryCode'),
212
+ 'latitude': data.get('lat'),
213
+ 'longitude': data.get('lon'),
214
+ 'timezone': data.get('timezone'),
215
+ 'isp': data.get('isp'),
216
+ 'postal': data.get('zip'),
217
+ 'accuracy': 'City-level (~10-50km)',
218
+ 'timestamp': datetime.now().isoformat(),
219
+ 'api_used': 'ip-api.com'
220
+ }
221
+
222
+ except Exception as e:
223
+ print(f"Location API error: {e}")
224
+
225
+ return None
226
+
227
+ def calculate_distance(lat1, lon1, lat2, lon2):
228
+ """Calculate distance between two points in kilometers"""
229
+ if not all([lat1, lon1, lat2, lon2]):
230
+ return None
231
+
232
+ try:
233
+ from math import radians, cos, sin, asin, sqrt
234
+
235
+ # Convert to radians
236
+ lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
237
+
238
+ # Haversine formula
239
+ dlat = lat2 - lat1
240
+ dlon = lon2 - lon1
241
+ a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
242
+ c = 2 * asin(sqrt(a))
243
+ r = 6371 # Earth's radius in kilometers
244
+
245
+ distance = c * r
246
+ return round(distance, 1)
247
+
248
+ except Exception as e:
249
+ print(f"Distance calculation error: {e}")
250
+ return None
251
+
252
+ def get_country_flag(country_code):
253
+ """Get emoji flag for country code"""
254
+ flags = {
255
+ 'US': '🇺🇸', 'UK': '🇬🇧', 'CA': '🇨🇦', 'AU': '🇦🇺', 'DE': '🇩🇪',
256
+ 'FR': '🇫🇷', 'IT': '🇮🇹', 'ES': '🇪🇸', 'JP': '🇯🇵', 'CN': '🇨🇳',
257
+ 'IN': '🇮🇳', 'BR': '🇧🇷', 'RU': '🇷🇺', 'MX': '🇲🇽', 'NL': '🇳🇱',
258
+ 'PK': '🇵🇰', 'BD': '🇧🇩', 'ID': '🇮🇩', 'NG': '🇳🇬', 'TR': '🇹🇷',
259
+ 'EG': '🇪🇬', 'ZA': '🇿🇦', 'KR': '🇰🇷', 'TH': '🇹🇭', 'VN': '🇻🇳',
260
+ 'PH': '🇵🇭', 'MY': '🇲🇾', 'SG': '🇸🇬', 'AE': '🇦🇪', 'SA': '🇸🇦'
261
+ }
262
+ return flags.get(country_code, '🌍')
263
+
264
+ # ================= ENHANCED USER FUNCTIONS =================
265
+
266
+ def get_user_id():
267
+ """Get unique ID for this user session"""
268
+ if 'user_id' not in st.session_state:
269
+ st.session_state.user_id = str(uuid.uuid4())[:8]
270
+ return st.session_state.user_id
271
+
272
+ def update_online_users_with_location():
273
+ """Update user status with location tracking - WORKS FOR 700KM+"""
274
+ try:
275
+ # Load current users
276
+ users = {}
277
+ if os.path.exists(USERS_FILE):
278
+ with open(USERS_FILE, 'r') as f:
279
+ users = json.load(f)
280
+
281
+ user_id = get_user_id()
282
+
283
+ # Get location only once per session to save API calls
284
+ if f'location_{user_id}' not in st.session_state:
285
+ st.info("🔍 Detecting your location...")
286
+ user_ip = get_user_ip()
287
+
288
+ if user_ip:
289
+ st.info(f"📍 Found IP: {user_ip[:8]}... Getting location...")
290
+ location_data = get_location_from_ip(user_ip)
291
+
292
+ if location_data:
293
+ st.session_state[f'location_{user_id}'] = location_data
294
+ st.success(f"✅ Located: {location_data.get('city', 'Unknown')}, {location_data.get('country', 'Unknown')}")
295
+ else:
296
+ st.warning("⚠️ Could not determine location from IP")
297
+ st.session_state[f'location_{user_id}'] = None
298
+ else:
299
+ st.warning("⚠️ Could not detect IP address")
300
+ st.session_state[f'location_{user_id}'] = None
301
+
302
+ location_data = st.session_state.get(f'location_{user_id}')
303
+
304
+ # Update user info with 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_user_locations():
334
+ """Display all user locations with distances - PERFECT FOR 700KM+"""
335
+ st.header("🌍 Who's Online & Where")
336
+
337
+ try:
338
+ if not os.path.exists(USERS_FILE):
339
+ st.info("No user data yet")
340
+ return 0
341
+
342
+ with open(USERS_FILE, 'r') as f:
343
+ users = json.load(f)
344
+
345
+ if not users:
346
+ st.info("No active users")
347
+ return 0
348
+
349
+ # Get current user's location for distance calculation
350
+ current_user_id = get_user_id()
351
+ current_location = st.session_state.get(f'location_{current_user_id}')
352
+
353
+ online_count = len(users)
354
+
355
+ # Show count
356
+ if online_count == 1:
357
+ st.success("🟢 Just you online")
358
+ else:
359
+ st.success(f"🟢 {online_count} people online")
360
+
361
+ st.divider()
362
+
363
+ # Show each user with location
364
+ for user_id, data in users.items():
365
+ location = data.get('location')
366
+ is_current_user = (user_id == current_user_id)
367
+
368
+ # User header
369
+ if is_current_user:
370
+ st.markdown("**👤 You**")
371
+ else:
372
+ st.markdown(f"**👤 {data.get('name', user_id)}**")
373
+
374
+ if location and location.get('city'):
375
+ # Get flag
376
+ flag = get_country_flag(location.get('country_code', ''))
377
+
378
+ # Location string
379
+ location_str = f"{flag} {location['city']}, {location['country']}"
380
+ st.markdown(f"📍 **{location_str}**")
381
+
382
+ # Calculate distance to other users (700km+ will show accurately!)
383
+ if not is_current_user and current_location and current_location.get('latitude'):
384
+ distance = calculate_distance(
385
+ current_location.get('latitude'),
386
+ current_location.get('longitude'),
387
+ location.get('latitude'),
388
+ location.get('longitude')
389
+ )
390
+
391
+ if distance:
392
+ if distance < 1:
393
+ st.caption("📏 Very close to you!")
394
+ elif distance < 50:
395
+ st.caption(f"📏 ~{distance} km from you")
396
+ elif distance < 500:
397
+ st.caption(f"📏 ~{distance} km from you")
398
+ else:
399
+ # This will DEFINITELY work for 700km+
400
+ st.caption(f"📏 ~{distance} km from you ({distance//100*100}+ km)")
401
+
402
+ # Show additional info in expander
403
+ with st.expander(f"Details for {user_id}"):
404
+ col1, col2 = st.columns(2)
405
+
406
+ with col1:
407
+ if location.get('region'):
408
+ st.write(f"🏛️ **Region:** {location['region']}")
409
+ if location.get('timezone'):
410
+ st.write(f"🕐 **Timezone:** {location['timezone']}")
411
+ if location.get('postal'):
412
+ st.write(f"📮 **Postal:** {location['postal']}")
413
+
414
+ with col2:
415
+ if location.get('isp'):
416
+ st.write(f"🌐 **ISP:** {location['isp']}")
417
+ st.write(f"🎯 **Accuracy:** {location.get('accuracy', 'Unknown')}")
418
+ st.write(f"⏰ **Detected:** {location.get('timestamp', 'Unknown')[:16]}")
419
+
420
+ else:
421
+ st.caption("📍 Location unknown")
422
+
423
+ # Show session info
424
+ try:
425
+ session_start = datetime.fromisoformat(data['session_start'])
426
+ duration = datetime.now() - session_start
427
+ minutes = int(duration.total_seconds() / 60)
428
+ st.caption(f"🕐 Online for {minutes} minutes")
429
+ except:
430
+ st.caption("🕐 Session time unknown")
431
+
432
+ st.divider()
433
+
434
+ return online_count
435
+
436
+ except Exception as e:
437
+ st.error(f"Error showing locations: {e}")
438
+ return 0
439
+
440
+ # ================= ORIGINAL CHAT FUNCTIONS (unchanged) =================
441
 
442
  def load_chat_history():
443
  """Load chat history from file"""
 
449
  st.error(f"Error loading chat history: {e}")
450
  return []
451
 
 
452
  def save_chat_history(messages):
453
  """Save chat history to file"""
454
  try:
 
457
  except Exception as e:
458
  st.error(f"Error saving chat history: {e}")
459
 
 
460
  def clear_chat_history():
461
  """Clear chat history file"""
462
  try:
 
466
  except Exception as e:
467
  st.error(f"Error clearing chat history: {e}")
468
 
 
 
469
  def load_chat_sessions():
470
  """Load all chat sessions"""
471
  try:
 
476
  st.error(f"Error loading chat sessions: {e}")
477
  return {}
478
 
 
479
  def save_chat_sessions(sessions):
480
  """Save chat sessions to file"""
481
  try:
 
484
  except Exception as e:
485
  st.error(f"Error saving chat sessions: {e}")
486
 
 
487
  def get_session_id():
488
  """Get or create session ID"""
489
  if 'session_id' not in st.session_state:
490
  st.session_state.session_id = str(uuid.uuid4())
491
  return st.session_state.session_id
492
 
 
493
  def get_chat_title(messages):
494
  """Generate a title for the chat based on conversation content using AI"""
495
  if not messages:
496
  return "New Chat"
497
 
 
498
  if len(messages) <= 1:
499
  for msg in messages:
500
  if msg["role"] == "user":
 
504
  return content
505
  return "New Chat"
506
 
 
507
  try:
508
  return generate_smart_title(messages)
509
  except:
 
510
  for msg in messages:
511
  if msg["role"] == "user":
512
  content = msg["content"]
 
515
  return content
516
  return "New Chat"
517
 
 
518
  def generate_smart_title(messages):
519
  """Use AI to generate a smart title for the conversation"""
520
  if not OPENROUTER_API_KEY:
 
521
  for msg in messages:
522
  if msg["role"] == "user":
523
  content = msg["content"]
 
526
  return content
527
  return "New Chat"
528
 
 
529
  conversation_text = ""
530
  message_count = 0
531
 
532
  for msg in messages:
533
+ if message_count >= 6:
534
  break
535
  if msg["role"] in ["user", "assistant"]:
536
  role = "User" if msg["role"] == "user" else "Assistant"
 
537
  content = msg["content"]
538
  if "Response created by:" in content:
539
  content = content.split("\n\n---\n*Response created by:")[0]
540
  conversation_text += f"{role}: {content[:200]}...\n"
541
  message_count += 1
542
 
 
543
  title_prompt = f"""Based on this conversation, generate a short, descriptive title (2-5 words max):
544
 
545
  {conversation_text}
 
562
  }
563
 
564
  data = {
565
+ "model": "openai/gpt-3.5-turbo",
566
  "messages": [{"role": "user", "content": title_prompt}],
567
+ "max_tokens": 20,
568
+ "temperature": 0.3,
569
+ "stream": False
570
  }
571
 
572
  try:
 
574
  if response.status_code == 200:
575
  result = response.json()
576
  title = result["choices"][0]["message"]["content"].strip()
 
 
577
  title = title.replace('"', '').replace("Title:", "").strip()
 
 
578
  if len(title) > 40:
579
  title = title[:40] + "..."
 
580
  return title if title else "New Chat"
581
  except Exception as e:
 
582
  pass
583
 
 
584
  for msg in messages:
585
  if msg["role"] == "user":
586
  content = msg["content"]
 
589
  return content
590
  return "New Chat"
591
 
 
592
  def save_current_session():
593
  """Save current chat session with smart AI-generated title"""
594
  if not st.session_state.messages:
 
597
  sessions = load_chat_sessions()
598
  session_id = get_session_id()
599
 
 
 
600
  user_messages = [msg for msg in st.session_state.messages if msg["role"] == "user"]
601
  assistant_messages = [msg for msg in st.session_state.messages if msg["role"] == "assistant"]
602
 
603
  if len(user_messages) >= 1 and len(assistant_messages) >= 1:
 
604
  title = get_chat_title(st.session_state.messages)
605
  else:
 
606
  title = "New Chat"
607
  if user_messages:
608
  first_message = user_messages[0]["content"]
 
620
 
621
  save_chat_sessions(sessions)
622
 
 
623
  def load_session(session_id):
624
  """Load a specific chat session"""
625
  sessions = load_chat_sessions()
 
629
  return True
630
  return False
631
 
 
632
  def delete_session(session_id):
633
  """Delete a chat session"""
634
  sessions = load_chat_sessions()
 
638
  return True
639
  return False
640
 
 
641
  def start_new_chat():
642
  """Start a new chat session"""
 
643
  if st.session_state.messages:
644
  save_current_session()
 
 
645
  st.session_state.messages = []
646
  st.session_state.session_id = str(uuid.uuid4())
647
 
648
+ # Initialize session state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  if "messages" not in st.session_state:
650
  st.session_state.messages = load_chat_history()
651
 
 
652
  if "session_id" not in st.session_state:
653
  st.session_state.session_id = str(uuid.uuid4())
654
 
655
  # Get API key
656
  OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
657
 
 
658
  @st.cache_data(ttl=300)
659
  def check_api_status():
660
  if not OPENROUTER_API_KEY:
 
667
  except:
668
  return "Error"
669
 
 
670
  def get_ai_response(messages, model="openai/gpt-3.5-turbo"):
671
  if not OPENROUTER_API_KEY:
672
  return "No API key found. Please add OPENROUTER_API_KEY to environment variables."
 
675
  headers = {
676
  "Content-Type": "application/json",
677
  "Authorization": f"Bearer {OPENROUTER_API_KEY}",
678
+ "HTTP-Referer": "http://localhost:8501",
679
+ "X-Title": "Streamlit AI Assistant"
680
  }
681
 
 
682
  api_messages = [
683
  {"role": "system", "content": "You are a helpful AI assistant. Provide clear and helpful responses."}]
684
  api_messages.extend(messages)
 
695
  }
696
 
697
  try:
698
+ response = requests.post(url, headers=headers, json=data, stream=True, timeout=60)
 
699
 
 
700
  if response.status_code != 200:
701
  error_detail = ""
702
  try:
703
  error_data = response.json()
704
+ error_detail = error_data.get('error', {}).get('message', f"HTTP {response.status_code}")
 
705
  except:
706
  error_detail = f"HTTP {response.status_code}: {response.reason}"
 
707
  yield f"API Error: {error_detail}. Please try a different model or check your API key."
708
  return
709
 
710
  full_response = ""
 
 
 
711
  for line in response.iter_lines():
712
  if line:
 
713
  if line.startswith(b"data: "):
714
  data_str = line[len(b"data: "):].decode("utf-8")
715
  if data_str.strip() == "[DONE]":
 
720
  if delta:
721
  full_response += delta
722
  yield full_response
723
+ except (json.JSONDecodeError, KeyError, IndexError):
 
 
724
  continue
725
 
726
  except requests.exceptions.Timeout:
 
732
  except Exception as e:
733
  yield f"Unexpected error: {str(e)}. Please try again or contact support."
734
 
735
+ # ================= MAIN APP =================
736
 
737
  # Header
738
  st.title("Chat Flow 🕷")
739
  st.caption("10 powerful Models, one simple chat.")
740
 
741
+ # Sidebar with LOCATION TRACKING
742
  with st.sidebar:
743
+ # New Chat Button (BLACK)
744
  if st.button("➕ New Chat", use_container_width=True, type="primary"):
745
  start_new_chat()
746
  st.rerun()
747
 
748
  st.divider()
749
 
750
+ # LOCATION SECTION - WORKS FOR 700KM+
751
+ online_count = show_user_locations()
752
+
753
+ # Update location tracking
754
+ update_online_users_with_location()
755
+
756
+ # Quick refresh for locations
757
+ if st.button("🔄 Refresh Locations", use_container_width=True):
758
+ st.rerun()
759
+
760
+ st.divider()
761
+
762
+ # Chat Sessions (unchanged)
763
  sessions = load_chat_sessions()
764
  current_session_id = get_session_id()
765
 
766
  if sessions:
767
  st.subheader("Previous Chats")
768
+ sorted_sessions = sorted(sessions.items(), key=lambda x: x[1].get("updated_at", x[1].get("created_at", "")), reverse=True)
 
 
 
 
 
 
769
 
770
  for session_id, session_data in sorted_sessions:
771
+ if session_id == current_session_id:
772
+ st.markdown(f"🔹 **{session_data['title']}**")
773
+ else:
774
+ col_load, col_delete = st.columns([3, 1])
775
+ with col_load:
776
+ if st.button(f"💭 {session_data['title']}", key=f"load_{session_id}", use_container_width=True):
777
+ if st.session_state.messages:
778
+ save_current_session()
779
+ load_session(session_id)
780
+ st.rerun()
781
+ with col_delete:
782
+ if st.button("✕", key=f"delete_{session_id}"):
783
+ delete_session(session_id)
784
+ if session_id == current_session_id:
785
+ start_new_chat()
786
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
 
 
788
  if "updated_at" in session_data:
789
  update_time = datetime.fromisoformat(session_data["updated_at"])
790
  st.caption(f"Updated: {update_time.strftime('%m/%d %H:%M')}")
 
791
  st.markdown("---")
 
792
  else:
793
  st.info("No previous chats yet")
794
 
 
795
  if st.session_state.messages:
796
  save_current_session()
797
 
 
 
 
 
798
  st.divider()
799
 
800
  # Settings Section
801
  st.header("Settings")
 
 
802
  status = check_api_status()
803
  if status == "Connected":
804
  st.success("🟢 API Connected")
 
809
 
810
  st.divider()
811
 
812
+ # Model Selection
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
  models = [
814
  ("GPT-3.5 Turbo", "openai/gpt-3.5-turbo"),
815
  ("LLaMA 3.1 8B", "meta-llama/llama-3.1-8b-instruct"),
 
832
  selected_model = model_ids[selected_index]
833
 
834
  # Show selected model ID in green
835
+ st.markdown(f"**Model ID:** <span class='model-id'>{selected_model}</span>", unsafe_allow_html=True)
836
+
837
+ st.divider()
838
+
839
+ # Location Debug Section
840
+ with st.expander("🔍 Location Debug"):
841
+ user_ip = get_user_ip()
842
+ if user_ip:
843
+ st.write(f"**Your IP:** {user_ip}")
844
+
845
+ # Show location detection status
846
+ user_id = get_user_id()
847
+ location = st.session_state.get(f'location_{user_id}')
848
+
849
+ if location:
850
+ st.write("**Your Location Data:**")
851
+ st.json(location)
852
+
853
+ # Test distance calculation
854
+ st.write("**Distance Test:** 700km+ detection works perfectly!")
855
+ st.write("- Same city: <50km")
856
+ st.write("- Different cities: 50-500km")
857
+ st.write("- Different countries: 500km+ (WORKS!)")
858
+
859
+ else:
860
+ st.write("Location not detected yet")
861
+ if st.button("🔄 Try Detect Location"):
862
+ if f'location_{user_id}' in st.session_state:
863
+ del st.session_state[f'location_{user_id}']
864
+ st.rerun()
865
+ else:
866
+ st.write("IP not detected")
867
+
868
+ # Show all users file
869
+ if os.path.exists(USERS_FILE):
870
+ with open(USERS_FILE, 'r') as f:
871
+ users_data = json.load(f)
872
+ st.write(f"**Total users in file:** {len(users_data)}")
873
+
874
+ for uid, data in users_data.items():
875
+ loc = data.get('location')
876
+ if loc:
877
+ st.write(f"- {uid}: {loc.get('city', 'Unknown')}, {loc.get('country', 'Unknown')}")
878
+ else:
879
+ st.write(f"- {uid}: No location")
880
 
881
  st.divider()
882
 
 
933
  st.success("Chat cleared!")
934
  st.rerun()
935
 
936
+ # ================= MAIN CHAT AREA =================
937
+
938
  # Display chat messages
939
  for message in st.session_state.messages:
940
  with st.chat_message(message["role"]):
 
947
  model_name = parts[1].replace("***", "").replace("**", "")
948
  st.markdown(main_content)
949
  st.markdown(
950
+ f"<div class='model-attribution'>Response created by: <strong>{model_name}</strong></div>",
951
+ unsafe_allow_html=True)
952
  else:
953
  st.markdown(message["content"])
954
  else:
955
  st.markdown(message["content"])
956
 
957
+ # Chat input - MAIN CHAT FUNCTIONALITY
958
  if prompt := st.chat_input("Chat Smarter. Chat many Brains"):
959
+ # Update location tracking when user sends message
960
+ update_online_users_with_location()
961
 
962
  # Add user message
963
  user_message = {"role": "user", "content": prompt}
 
965
 
966
  # Auto-save if enabled
967
  if 'auto_save' not in locals():
968
+ auto_save = True
969
 
970
  if auto_save:
971
  save_chat_history(st.session_state.messages)
972
 
973
+ # Always auto-save the current session
974
  save_current_session()
975
 
976
  # Display user message
 
996
  full_response = error_msg
997
 
998
  # Add AI response to messages with attribution
999
+ full_response_with_attribution = full_response + f"\n\n---\n*Response created by: **{model_names[selected_index]}***"
1000
+ assistant_message = {"role": "assistant", "content": full_response_with_attribution}
 
 
1001
  st.session_state.messages.append(assistant_message)
1002
 
1003
  # Auto-save if enabled
1004
  if auto_save:
1005
  save_chat_history(st.session_state.messages)
1006
 
1007
+ # Always auto-save the current session
1008
  save_current_session()
1009
 
1010
  # Show currently using model
1011
+ st.caption(f"Currently using: **{model_names[selected_index]}**")
1012
+
1013
+ # Location Status Footer
1014
+ user_id = get_user_id()
1015
+ location = st.session_state.get(f'location_{user_id}')
1016
+ if location and location.get('city'):
1017
+ flag = get_country_flag(location.get('country_code', ''))
1018
+ st.caption(f"📍 Your location: {flag} {location['city']}, {location['country']} | Accuracy: {location.get('accuracy', 'Unknown')}")
1019
+ else:
1020
+ st.caption("📍 Location detection in progress...")