Spaces:
Sleeping
Sleeping
import requests | |
import os | |
import json | |
import streamlit as st | |
from datetime import datetime, timedelta | |
import time | |
import uuid | |
import streamlit.components.v1 as components | |
# Page configuration | |
st.set_page_config( | |
page_title="Chat Flow 🕷", | |
page_icon="💬", | |
initial_sidebar_state="expanded" | |
) | |
# Enhanced CSS with chat history styling and BLACK NEW CHAT BUTTON | |
st.markdown(""" | |
<style> | |
.stApp { | |
background: white; | |
} | |
.main .block-container { | |
max-width: 800px; | |
} | |
#MainMenu {visibility: hidden;} | |
footer {visibility: hidden;} | |
header {visibility: hidden;} | |
.stDeployButton {display: none;} | |
.model-id { | |
color: #28a745; | |
font-family: monospace; | |
} | |
.model-attribution { | |
color: #28a745; | |
font-size: 0.8em; | |
font-style: italic; | |
} | |
/* NEW CHAT BUTTON - Black background, white text */ | |
.stButton > button[kind="primary"] { | |
background-color: #000000 !important; | |
border-color: #000000 !important; | |
color: #ffffff !important; | |
} | |
.stButton > button[kind="primary"]:hover { | |
background-color: #333333 !important; | |
border-color: #333333 !important; | |
color: #ffffff !important; | |
} | |
.stButton > button[kind="primary"]:active { | |
background-color: #1a1a1a !important; | |
border-color: #1a1a1a !important; | |
color: #ffffff !important; | |
} | |
.stButton > button[kind="primary"]:focus { | |
background-color: #000000 !important; | |
border-color: #000000 !important; | |
color: #ffffff !important; | |
box-shadow: 0 0 0 0.2rem rgba(0, 0, 0, 0.25) !important; | |
} | |
/* Location styling */ | |
.location-info { | |
background: #f8f9fa; | |
padding: 8px 12px; | |
border-radius: 6px; | |
margin: 4px 0; | |
border-left: 3px solid #28a745; | |
} | |
.location-flag { | |
font-size: 1.2em; | |
margin-right: 6px; | |
} | |
.distance-info { | |
color: #666; | |
font-size: 0.85em; | |
font-style: italic; | |
} | |
/* Chat history styling */ | |
.chat-history-item { | |
padding: 8px 12px; | |
margin: 4px 0; | |
border-radius: 8px; | |
border: 1px solid #e0e0e0; | |
background: #f8f9fa; | |
cursor: pointer; | |
transition: all 0.2s; | |
} | |
.chat-history-item:hover { | |
background: #e9ecef; | |
border-color: #28a745; | |
} | |
.chat-history-item.active { | |
background: #28a745; | |
color: white; | |
border-color: #28a745; | |
} | |
.chat-title { | |
font-weight: 500; | |
font-size: 0.9em; | |
margin-bottom: 2px; | |
} | |
.chat-date { | |
font-size: 0.75em; | |
opacity: 0.7; | |
} | |
.new-chat-btn { | |
width: 100%; | |
margin-bottom: 16px; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# File to store chat history | |
HISTORY_FILE = "chat_history.json" | |
USERS_FILE = "online_users.json" | |
SESSIONS_FILE = "chat_sessions.json" | |
# ================= REAL LOCATION DETECTION FUNCTIONS ================= | |
def get_user_location_browser(): | |
"""Get user's real location using browser geolocation API""" | |
# JavaScript code to get real user location | |
location_js = """ | |
<script> | |
function getUserLocation() { | |
if (navigator.geolocation) { | |
navigator.geolocation.getCurrentPosition( | |
function(position) { | |
const location = { | |
latitude: position.coords.latitude, | |
longitude: position.coords.longitude, | |
accuracy: position.coords.accuracy, | |
timestamp: new Date().toISOString() | |
}; | |
// Store in sessionStorage for Streamlit to access | |
sessionStorage.setItem('userLocation', JSON.stringify(location)); | |
// Trigger a Streamlit rerun | |
window.parent.postMessage({ | |
type: 'streamlit:componentReady' | |
}, '*'); | |
console.log('Location detected:', location); | |
}, | |
function(error) { | |
console.error('Geolocation error:', error.message); | |
const errorInfo = { | |
error: error.message, | |
code: error.code, | |
timestamp: new Date().toISOString() | |
}; | |
sessionStorage.setItem('locationError', JSON.stringify(errorInfo)); | |
}, | |
{ | |
enableHighAccuracy: true, | |
timeout: 10000, | |
maximumAge: 300000 // 5 minutes cache | |
} | |
); | |
} else { | |
console.error('Geolocation not supported'); | |
sessionStorage.setItem('locationError', JSON.stringify({ | |
error: 'Geolocation not supported by browser', | |
code: -1, | |
timestamp: new Date().toISOString() | |
})); | |
} | |
} | |
// Auto-start location detection | |
getUserLocation(); | |
// Also provide manual trigger | |
window.getUserLocation = getUserLocation; | |
</script> | |
<div id="location-detector"> | |
<p>🔍 Detecting your real location...</p> | |
<button onclick="getUserLocation()" style=" | |
background: #28a745; | |
color: white; | |
border: none; | |
padding: 8px 16px; | |
border-radius: 4px; | |
cursor: pointer; | |
">📍 Allow Location Access</button> | |
</div> | |
""" | |
return location_js | |
def reverse_geocode(lat, lon): | |
"""Convert coordinates to address using free APIs""" | |
try: | |
# Using Nominatim (OpenStreetMap) - Free and reliable | |
url = f"https://nominatim.openstreetmap.org/reverse?lat={lat}&lon={lon}&format=json&addressdetails=1" | |
headers = {'User-Agent': 'ChatFlow-App/1.0'} | |
response = requests.get(url, headers=headers, timeout=10) | |
if response.status_code == 200: | |
data = response.json() | |
address = data.get('address', {}) | |
location_data = { | |
'latitude': lat, | |
'longitude': lon, | |
'city': address.get('city') or address.get('town') or address.get('village'), | |
'region': address.get('state') or address.get('province'), | |
'country': address.get('country'), | |
'country_code': address.get('country_code', '').upper(), | |
'postal': address.get('postcode'), | |
'address': data.get('display_name'), | |
'accuracy': 'GPS-based (Very High)', | |
'timestamp': datetime.now().isoformat(), | |
'api_used': 'Nominatim/OpenStreetMap', | |
'source': 'Browser Geolocation' | |
} | |
return location_data | |
except Exception as e: | |
st.error(f"Reverse geocoding error: {e}") | |
return None | |
def calculate_distance(lat1, lon1, lat2, lon2): | |
"""Calculate distance between two points in kilometers""" | |
if not all([lat1, lon1, lat2, lon2]): | |
return None | |
try: | |
from math import radians, cos, sin, asin, sqrt | |
# Convert to radians | |
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) | |
# Haversine formula | |
dlat = lat2 - lat1 | |
dlon = lon2 - lon1 | |
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 | |
c = 2 * asin(sqrt(a)) | |
r = 6371 # Earth's radius in kilometers | |
distance = c * r | |
return round(distance, 1) | |
except Exception as e: | |
print(f"Distance calculation error: {e}") | |
return None | |
def get_country_flag(country_code): | |
"""Get emoji flag for country code""" | |
flags = { | |
'US': '🇺🇸', 'UK': '🇬🇧', 'CA': '🇨🇦', 'AU': '🇦🇺', 'DE': '🇩🇪', | |
'FR': '🇫🇷', 'IT': '🇮🇹', 'ES': '🇪🇸', 'JP': '🇯🇵', 'CN': '🇨🇳', | |
'IN': '🇮🇳', 'BR': '🇧🇷', 'RU': '🇷🇺', 'MX': '🇲🇽', 'NL': '🇳🇱', | |
'PK': '🇵🇰', 'BD': '🇧🇩', 'ID': '🇮🇩', 'NG': '🇳🇬', 'TR': '🇹🇷', | |
'EG': '🇪🇬', 'ZA': '🇿🇦', 'KR': '🇰🇷', 'TH': '🇹🇭', 'VN': '🇻🇳', | |
'PH': '🇵🇭', 'MY': '🇲🇾', 'SG': '🇸🇬', 'AE': '🇦🇪', 'SA': '🇸🇦' | |
} | |
return flags.get(country_code, '🌍') | |
# ================= USER FUNCTIONS WITH REAL LOCATION ================= | |
def get_user_id(): | |
"""Get unique ID for this user session""" | |
if 'user_id' not in st.session_state: | |
st.session_state.user_id = str(uuid.uuid4())[:8] | |
return st.session_state.user_id | |
def update_online_users_with_real_location(): | |
"""Update user status with REAL browser-based location""" | |
try: | |
# Load current users | |
users = {} | |
if os.path.exists(USERS_FILE): | |
with open(USERS_FILE, 'r') as f: | |
users = json.load(f) | |
user_id = get_user_id() | |
# Check if we have real location data from browser | |
location_data = st.session_state.get(f'real_location_{user_id}') | |
# Update user info with real location | |
users[user_id] = { | |
'last_seen': datetime.now().isoformat(), | |
'name': f'User-{user_id}', | |
'location': location_data, | |
'session_start': users.get(user_id, {}).get('session_start', datetime.now().isoformat()) | |
} | |
# Clean up old users (not seen in 5 minutes) | |
current_time = datetime.now() | |
active_users = {} | |
for uid, data in users.items(): | |
try: | |
last_seen = datetime.fromisoformat(data['last_seen']) | |
if current_time - last_seen < timedelta(minutes=5): | |
active_users[uid] = data | |
except: | |
continue | |
# Save updated users | |
with open(USERS_FILE, 'w') as f: | |
json.dump(active_users, f, indent=2) | |
return len(active_users) | |
except Exception as e: | |
st.error(f"Location tracking error: {e}") | |
return 1 | |
def show_real_location_detector(): | |
"""Show real location detector interface""" | |
st.header("🌍 Real Location Detection") | |
user_id = get_user_id() | |
# Check if we already have location | |
if f'real_location_{user_id}' in st.session_state: | |
location = st.session_state[f'real_location_{user_id}'] | |
if location: | |
flag = get_country_flag(location.get('country_code', '')) | |
st.success(f"✅ Location detected: {flag} {location.get('city', 'Unknown')}, {location.get('country', 'Unknown')}") | |
return True | |
# Show location detector | |
st.info("🔍 Click below to detect your real location using browser GPS") | |
# Embed JavaScript for real geolocation | |
location_component = get_user_location_browser() | |
components.html(location_component, height=100) | |
# Manual coordinate input as backup | |
with st.expander("🛠️ Manual Location Entry (Backup)"): | |
st.write("If automatic detection doesn't work, enter your coordinates:") | |
col1, col2 = st.columns(2) | |
with col1: | |
manual_lat = st.number_input("Latitude", value=0.0, format="%.6f") | |
with col2: | |
manual_lon = st.number_input("Longitude", value=0.0, format="%.6f") | |
if st.button("🎯 Use These Coordinates"): | |
if manual_lat != 0.0 and manual_lon != 0.0: | |
location_data = reverse_geocode(manual_lat, manual_lon) | |
if location_data: | |
st.session_state[f'real_location_{user_id}'] = location_data | |
st.success("✅ Manual location set!") | |
st.rerun() | |
else: | |
st.error("❌ Could not resolve coordinates to address") | |
return False | |
def process_browser_location(): | |
"""Process location data from browser JavaScript""" | |
user_id = get_user_id() | |
# This would be called when JavaScript sends location data | |
# In a real implementation, you'd use Streamlit's session state | |
# or a custom component to receive the JavaScript data | |
# For now, we'll simulate this with manual input | |
# In production, you'd replace this with actual JavaScript communication | |
pass | |
def show_user_locations(): | |
"""Display all user locations with real coordinates""" | |
st.header("🌍 Who's Online & Where") | |
try: | |
if not os.path.exists(USERS_FILE): | |
st.info("No user data yet") | |
return 0 | |
with open(USERS_FILE, 'r') as f: | |
users = json.load(f) | |
if not users: | |
st.info("No active users") | |
return 0 | |
# Get current user's location for distance calculation | |
current_user_id = get_user_id() | |
current_location = st.session_state.get(f'real_location_{current_user_id}') | |
online_count = len(users) | |
# Show count | |
if online_count == 1: | |
st.success("🟢 Just you online") | |
else: | |
st.success(f"🟢 {online_count} people online") | |
st.divider() | |
# Show each user with real location | |
for user_id, data in users.items(): | |
location = data.get('location') | |
is_current_user = (user_id == current_user_id) | |
# User header | |
if is_current_user: | |
st.markdown("**👤 You**") | |
else: | |
st.markdown(f"**👤 {data.get('name', user_id)}**") | |
if location and location.get('city'): | |
# Get flag | |
flag = get_country_flag(location.get('country_code', '')) | |
# Location string with accuracy indicator | |
accuracy = location.get('accuracy', 'Unknown') | |
location_str = f"{flag} {location['city']}, {location['country']}" | |
if 'GPS' in accuracy: | |
st.markdown(f"📍 **{location_str}** 🎯") | |
st.caption("✅ Real GPS location") | |
else: | |
st.markdown(f"📍 **{location_str}**") | |
st.caption("⚠️ Approximate location") | |
# Calculate REAL distance to other users | |
if not is_current_user and current_location and current_location.get('latitude'): | |
distance = calculate_distance( | |
current_location.get('latitude'), | |
current_location.get('longitude'), | |
location.get('latitude'), | |
location.get('longitude') | |
) | |
if distance: | |
if distance < 1: | |
st.caption("📏 Very close to you! (<1km)") | |
elif distance < 10: | |
st.caption(f"📏 ~{distance} km from you (same city)") | |
elif distance < 100: | |
st.caption(f"📏 ~{distance} km from you (nearby)") | |
else: | |
st.caption(f"📏 ~{distance} km from you") | |
# Show detailed location info | |
with st.expander(f"Real Location Details - {user_id}"): | |
col1, col2 = st.columns(2) | |
with col1: | |
st.write(f"🎯 **Coordinates:** {location.get('latitude', 'N/A'):.6f}, {location.get('longitude', 'N/A'):.6f}") | |
if location.get('region'): | |
st.write(f"🏛️ **Region:** {location['region']}") | |
if location.get('postal'): | |
st.write(f"📮 **Postal:** {location['postal']}") | |
with col2: | |
st.write(f"🔍 **Source:** {location.get('source', 'Unknown')}") | |
st.write(f"🎯 **Accuracy:** {location.get('accuracy', 'Unknown')}") | |
st.write(f"⏰ **Detected:** {location.get('timestamp', 'Unknown')[:16]}") | |
else: | |
st.caption("📍 Location not detected yet") | |
if is_current_user: | |
st.caption("👆 Use the location detector above") | |
# Show session info | |
try: | |
session_start = datetime.fromisoformat(data['session_start']) | |
duration = datetime.now() - session_start | |
minutes = int(duration.total_seconds() / 60) | |
st.caption(f"🕐 Online for {minutes} minutes") | |
except: | |
st.caption("🕐 Session time unknown") | |
st.divider() | |
return online_count | |
except Exception as e: | |
st.error(f"Error showing locations: {e}") | |
return 0 | |
# ================= CHAT FUNCTIONS (unchanged) ================= | |
# [All the existing chat functions remain the same] | |
def load_chat_history(): | |
"""Load chat history from file""" | |
try: | |
if os.path.exists(HISTORY_FILE): | |
with open(HISTORY_FILE, 'r', encoding='utf-8') as f: | |
return json.load(f) | |
except Exception as e: | |
st.error(f"Error loading chat history: {e}") | |
return [] | |
def save_chat_history(messages): | |
"""Save chat history to file""" | |
try: | |
with open(HISTORY_FILE, 'w', encoding='utf-8') as f: | |
json.dump(messages, f, ensure_ascii=False, indent=2) | |
except Exception as e: | |
st.error(f"Error saving chat history: {e}") | |
def clear_chat_history(): | |
"""Clear chat history file""" | |
try: | |
if os.path.exists(HISTORY_FILE): | |
os.remove(HISTORY_FILE) | |
st.session_state.messages = [] | |
except Exception as e: | |
st.error(f"Error clearing chat history: {e}") | |
def load_chat_sessions(): | |
"""Load all chat sessions""" | |
try: | |
if os.path.exists(SESSIONS_FILE): | |
with open(SESSIONS_FILE, 'r', encoding='utf-8') as f: | |
return json.load(f) | |
except Exception as e: | |
st.error(f"Error loading chat sessions: {e}") | |
return {} | |
def save_chat_sessions(sessions): | |
"""Save chat sessions to file""" | |
try: | |
with open(SESSIONS_FILE, 'w', encoding='utf-8') as f: | |
json.dump(sessions, f, ensure_ascii=False, indent=2) | |
except Exception as e: | |
st.error(f"Error saving chat sessions: {e}") | |
def get_session_id(): | |
"""Get or create session ID""" | |
if 'session_id' not in st.session_state: | |
st.session_state.session_id = str(uuid.uuid4()) | |
return st.session_state.session_id | |
def start_new_chat(): | |
"""Start a new chat session""" | |
if st.session_state.messages: | |
save_current_session() | |
st.session_state.messages = [] | |
st.session_state.session_id = str(uuid.uuid4()) | |
# Initialize session state | |
if "messages" not in st.session_state: | |
st.session_state.messages = load_chat_history() | |
if "session_id" not in st.session_state: | |
st.session_state.session_id = str(uuid.uuid4()) | |
# Get API key | |
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY") | |
# ================= MAIN APP WITH REAL LOCATION ================= | |
# Header | |
st.title("Chat Flow 🕷") | |
st.caption("10 powerful Models, one simple chat.") | |
# Sidebar with REAL LOCATION TRACKING | |
with st.sidebar: | |
# New Chat Button (BLACK) | |
if st.button("➕ New Chat", use_container_width=True, type="primary"): | |
start_new_chat() | |
st.rerun() | |
st.divider() | |
# REAL LOCATION DETECTION | |
show_real_location_detector() | |
st.divider() | |
# Show online users with real locations | |
online_count = show_user_locations() | |
# Update location tracking | |
update_online_users_with_real_location() | |
# Quick refresh | |
if st.button("🔄 Refresh Locations", use_container_width=True): | |
st.rerun() | |
st.divider() | |
# Rest of the sidebar (chat sessions, settings, etc.) remains the same | |
# [All existing sidebar code continues here...] | |
# Main chat area remains the same | |
# [All existing chat code continues here...] | |
# Location Status Footer - Updated for real location | |
user_id = get_user_id() | |
location = st.session_state.get(f'real_location_{user_id}') | |
if location and location.get('city'): | |
flag = get_country_flag(location.get('country_code', '')) | |
accuracy = location.get('accuracy', 'Unknown') | |
if 'GPS' in accuracy: | |
st.caption(f"📍 Your real location: {flag} {location['city']}, {location['country']} | ✅ GPS-accurate") | |
else: | |
st.caption(f"📍 Your location: {flag} {location['city']}, {location['country']} | Accuracy: {accuracy}") | |
else: | |
st.caption("📍 Real location not detected yet - click 'Allow Location Access' above") |