Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
573 |
|
574 |
# Header
|
575 |
st.title("Chat Flow 🕷")
|
576 |
st.caption("10 powerful Models, one simple chat.")
|
577 |
|
578 |
-
# Sidebar with
|
579 |
with st.sidebar:
|
580 |
-
#
|
|
|
|
|
|
|
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 |
-
#
|
588 |
-
|
|
|
589 |
|
590 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
591 |
|
592 |
-
|
593 |
-
|
594 |
|
595 |
-
#
|
596 |
-
|
|
|
597 |
|
598 |
-
#
|
599 |
-
if st.button("🔄 Refresh
|
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 |
-
#
|
608 |
-
|
609 |
|
610 |
-
#
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
if 'GPS' in accuracy:
|
617 |
-
st.caption(f"📍 Your real location: {flag} {location['city']}, {location['country']} | ✅ GPS-accurate")
|
618 |
else:
|
619 |
-
st.
|
620 |
-
|
621 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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]}**")
|