uumerrr684 commited on
Commit
ea943de
·
verified ·
1 Parent(s): f1eb0ab

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +466 -778
app.py CHANGED
@@ -1,785 +1,473 @@
1
- import requests
2
- import os
3
- import json
4
- import streamlit as 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 {
20
- background: white;
21
- }
22
-
23
- .main .block-container {
24
- max-width: 800px;
25
- }
26
-
27
- #MainMenu {visibility: hidden;}
28
- footer {visibility: hidden;}
29
- header {visibility: hidden;}
30
- .stDeployButton {display: none;}
31
-
32
- .model-id {
33
- color: #28a745;
34
- font-family: monospace;
35
- }
36
-
37
- .model-attribution {
38
- color: #28a745;
39
- font-size: 0.8em;
40
- font-style: italic;
41
- }
42
-
43
- /* Chat history styling */
44
- .chat-history-item {
45
- padding: 8px 12px;
46
- margin: 4px 0;
47
- border-radius: 8px;
48
- border: 1px solid #e0e0e0;
49
- background: #f8f9fa;
50
- cursor: pointer;
51
- transition: all 0.2s;
52
- }
53
-
54
- .chat-history-item:hover {
55
- background: #e9ecef;
56
- border-color: #28a745;
57
- }
58
-
59
- .chat-history-item.active {
60
- background: #28a745;
61
- color: white;
62
- border-color: #28a745;
63
- }
64
-
65
- .chat-title {
66
- font-weight: 500;
67
- font-size: 0.9em;
68
- margin-bottom: 2px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
-
71
- .chat-date {
72
- font-size: 0.75em;
73
- opacity: 0.7;
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
-
76
- .new-chat-btn {
77
- width: 100%;
78
- margin-bottom: 16px;
 
 
 
 
79
  }
80
- </style>
81
- """, unsafe_allow_html=True)
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"""
93
- try:
94
- if os.path.exists(HISTORY_FILE):
95
- with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
96
- return json.load(f)
97
- except Exception as e:
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:
105
- with open(HISTORY_FILE, 'w', encoding='utf-8') as f:
106
- json.dump(messages, f, ensure_ascii=False, indent=2)
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:
114
- if os.path.exists(HISTORY_FILE):
115
- os.remove(HISTORY_FILE)
116
- st.session_state.messages = []
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:
125
- if os.path.exists(SESSIONS_FILE):
126
- with open(SESSIONS_FILE, 'r', encoding='utf-8') as f:
127
- return json.load(f)
128
- except Exception as e:
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:
136
- with open(SESSIONS_FILE, 'w', encoding='utf-8') as f:
137
- json.dump(sessions, f, ensure_ascii=False, indent=2)
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]}**")
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Send, Plus, MessageSquare, Settings, Users, Download, Trash2, RefreshCw, Bot } from 'lucide-react';
3
+
4
+ const ChatFlowApp = () => {
5
+ const [messages, setMessages] = useState([]);
6
+ const [currentMessage, setCurrentMessage] = useState('');
7
+ const [selectedModel, setSelectedModel] = useState('openai/gpt-3.5-turbo');
8
+ const [isLoading, setIsLoading] = useState(false);
9
+ const [sessions, setSessions] = useState([]);
10
+ const [currentSessionId, setCurrentSessionId] = useState('default');
11
+ const [onlineUsers, setOnlineUsers] = useState(1);
12
+ const [apiStatus, setApiStatus] = useState('Connected');
13
+ const [autoSave, setAutoSave] = useState(true);
14
+ const messagesEndRef = useRef(null);
15
+ const [userId] = useState('User-' + Math.random().toString(36).substr(2, 8));
16
+
17
+ const models = [
18
+ { name: "GPT-3.5 Turbo", id: "openai/gpt-3.5-turbo" },
19
+ { name: "LLaMA 3.1 8B", id: "meta-llama/llama-3.1-8b-instruct" },
20
+ { name: "LLaMA 3.1 70B", id: "meta-llama/llama-3.1-70b-instruct" },
21
+ { name: "DeepSeek Chat v3", id: "deepseek/deepseek-chat-v3-0324:free" },
22
+ { name: "DeepSeek R1", id: "deepseek/deepseek-r1-0528:free" },
23
+ { name: "Qwen3 Coder", id: "qwen/qwen3-coder:free" },
24
+ { name: "Microsoft MAI DS R1", id: "microsoft/mai-ds-r1:free" },
25
+ { name: "Gemma 3 27B", id: "google/gemma-3-27b-it:free" },
26
+ { name: "Gemma 3 4B", id: "google/gemma-3-4b-it:free" },
27
+ { name: "Auto (Best Available)", id: "openrouter/auto" }
28
+ ];
29
+
30
+ const scrollToBottom = () => {
31
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
32
+ };
33
+
34
+ useEffect(() => {
35
+ scrollToBottom();
36
+ }, [messages]);
37
+
38
+ useEffect(() => {
39
+ // Simulate online users update
40
+ const interval = setInterval(() => {
41
+ setOnlineUsers(Math.floor(Math.random() * 5) + 1);
42
+ }, 10000);
43
+ return () => clearInterval(interval);
44
+ }, []);
45
+
46
+ useEffect(() => {
47
+ // Check API status on component mount
48
+ const checkAPIStatus = async () => {
49
+ try {
50
+ const OPENROUTER_API_KEY = process.env.REACT_APP_OPENROUTER_API_KEY ||
51
+ window.OPENROUTER_API_KEY ||
52
+ process.env.OPENROUTER_API_KEY;
53
+
54
+ if (!OPENROUTER_API_KEY) {
55
+ setApiStatus('No API Key');
56
+ return;
57
+ }
58
+
59
+ const response = await fetch("https://openrouter.ai/api/v1/models", {
60
+ headers: { "Authorization": `Bearer ${OPENROUTER_API_KEY}` }
61
+ });
62
+
63
+ setApiStatus(response.ok ? 'Connected' : 'Error');
64
+ } catch {
65
+ setApiStatus('Error');
66
+ }
67
+ };
68
+
69
+ checkAPIStatus();
70
+ }, []);
71
+
72
+ const generateChatTitle = (msgs) => {
73
+ if (!msgs || msgs.length === 0) return "New Chat";
74
+ const firstUserMessage = msgs.find(m => m.role === 'user');
75
+ if (!firstUserMessage) return "New Chat";
76
+ const content = firstUserMessage.content;
77
+ return content.length > 30 ? content.substring(0, 30) + "..." : content;
78
+ };
79
+
80
+ const startNewChat = () => {
81
+ if (messages.length > 0) {
82
+ const newSession = {
83
+ id: 'session-' + Date.now(),
84
+ title: generateChatTitle(messages),
85
+ messages: [...messages],
86
+ createdAt: new Date().toISOString(),
87
+ updatedAt: new Date().toISOString()
88
+ };
89
+ setSessions(prev => [newSession, ...prev]);
90
  }
91
+ setMessages([]);
92
+ setCurrentSessionId('session-' + Date.now());
93
+ };
94
+
95
+ const loadSession = (session) => {
96
+ if (messages.length > 0) {
97
+ const currentSession = {
98
+ id: currentSessionId,
99
+ title: generateChatTitle(messages),
100
+ messages: [...messages],
101
+ createdAt: new Date().toISOString(),
102
+ updatedAt: new Date().toISOString()
103
+ };
104
+ setSessions(prev => {
105
+ const filtered = prev.filter(s => s.id !== currentSessionId);
106
+ return [currentSession, ...filtered];
107
+ });
108
  }
109
+ setMessages(session.messages);
110
+ setCurrentSessionId(session.id);
111
+ };
112
+
113
+ const deleteSession = (sessionId) => {
114
+ setSessions(prev => prev.filter(s => s.id !== sessionId));
115
+ if (sessionId === currentSessionId) {
116
+ startNewChat();
117
  }
118
+ };
119
+
120
+ // OpenRouter API integration
121
+ const getAIResponse = async (userMessage) => {
122
+ setIsLoading(true);
123
+
124
+ try {
125
+ // Get API key from environment variables (Hugging Face Spaces secrets)
126
+ const OPENROUTER_API_KEY = process.env.REACT_APP_OPENROUTER_API_KEY ||
127
+ window.OPENROUTER_API_KEY ||
128
+ process.env.OPENROUTER_API_KEY;
129
+
130
+ if (!OPENROUTER_API_KEY) {
131
+ throw new Error("No API key found. Please add OPENROUTER_API_KEY to environment variables.");
132
+ }
133
+
134
+ const url = "https://openrouter.ai/api/v1/chat/completions";
135
+ const headers = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  "Content-Type": "application/json",
137
+ "Authorization": `Bearer ${OPENROUTER_API_KEY}`,
138
+ "HTTP-Referer": "https://huggingface.co/spaces",
139
+ "X-Title": "Chat Flow AI Assistant"
140
+ };
141
+
142
+ // Prepare messages for API
143
+ const apiMessages = [
144
+ { role: "system", content: "You are a helpful AI assistant. Provide clear and helpful responses." },
145
+ ...messages.map(msg => ({
146
+ role: msg.role,
147
+ content: msg.content.split('\n\n---\n*Response created by:')[0] // Remove attribution from content
148
+ })),
149
+ { role: "user", content: userMessage }
150
+ ];
151
+
152
+ const data = {
153
+ model: selectedModel,
154
+ messages: apiMessages,
155
+ stream: false, // Set to false for simpler handling in React
156
+ max_tokens: 2000,
157
+ temperature: 0.7,
158
+ top_p: 1,
159
+ frequency_penalty: 0,
160
+ presence_penalty: 0
161
+ };
162
+
163
+ const response = await fetch(url, {
164
+ method: 'POST',
165
+ headers: headers,
166
+ body: JSON.stringify(data)
167
+ });
168
+
169
+ if (!response.ok) {
170
+ let errorDetail = "";
171
+ try {
172
+ const errorData = await response.json();
173
+ errorDetail = errorData.error?.message || `HTTP ${response.status}`;
174
+ } catch {
175
+ errorDetail = `HTTP ${response.status}: ${response.statusText}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
+ throw new Error(`API Error: ${errorDetail}. Please try a different model or check your API key.`);
178
+ }
179
+
180
+ const result = await response.json();
181
+ const aiResponse = result.choices[0].message.content;
182
+ const selectedModelName = models.find(m => m.id === selectedModel)?.name || "AI";
183
+
184
+ setIsLoading(false);
185
+ return aiResponse + `\n\n---\n*Response created by: **${selectedModelName}***`;
186
+
187
+ } catch (error) {
188
+ setIsLoading(false);
189
+ console.error('API Error:', error);
190
+
191
+ if (error.message.includes('timeout')) {
192
+ return "Request timed out. Please try again with a shorter message or different model.";
193
+ } else if (error.message.includes('Connection')) {
194
+ return "Connection error. Please check your internet connection and try again.";
195
+ } else {
196
+ return `Error: ${error.message}`;
197
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  }
199
+ };
200
+
201
+ const handleSendMessage = async (e) => {
202
+ e.preventDefault();
203
+ if (!currentMessage.trim() || isLoading) return;
204
+
205
+ const userMessage = {
206
+ role: 'user',
207
+ content: currentMessage.trim(),
208
+ timestamp: new Date().toISOString()
209
+ };
210
+
211
+ setMessages(prev => [...prev, userMessage]);
212
+ const messageToSend = currentMessage.trim();
213
+ setCurrentMessage('');
214
+
215
+ try {
216
+ const aiResponse = await getAIResponse(messageToSend);
217
+ const assistantMessage = {
218
+ role: 'assistant',
219
+ content: aiResponse,
220
+ timestamp: new Date().toISOString()
221
+ };
222
+ setMessages(prev => [...prev, assistantMessage]);
223
+ } catch (error) {
224
+ const errorMessage = {
225
+ role: 'assistant',
226
+ content: 'Sorry, I encountered an error. Please try again.',
227
+ timestamp: new Date().toISOString()
228
+ };
229
+ setMessages(prev => [...prev, errorMessage]);
230
  }
231
+ };
232
+
233
+ const clearChat = () => {
234
+ setMessages([]);
235
+ };
236
+
237
+ const downloadHistory = () => {
238
+ const dataStr = JSON.stringify(messages, null, 2);
239
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
240
+ const url = URL.createObjectURL(dataBlob);
241
+ const link = document.createElement('a');
242
+ link.href = url;
243
+ link.download = `chat_history_${new Date().toISOString().split('T')[0]}.json`;
244
+ link.click();
245
+ URL.revokeObjectURL(url);
246
+ };
247
+
248
+ const getSelectedModelName = () => {
249
+ return models.find(m => m.id === selectedModel)?.name || "GPT-3.5 Turbo";
250
+ };
251
+
252
+ return (
253
+ <div className="flex h-screen bg-gray-900 text-white">
254
+ {/* Sidebar */}
255
+ <div className="w-80 bg-gray-800 border-r border-gray-700 flex flex-col">
256
+ {/* Header */}
257
+ <div className="p-4 border-b border-gray-700">
258
+ <div className="flex items-center gap-3 mb-4">
259
+ <div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
260
+ <Bot className="w-5 h-5" />
261
+ </div>
262
+ <h1 className="text-xl font-semibold">Chat Flow</h1>
263
+ </div>
264
+ <button
265
+ onClick={startNewChat}
266
+ className="w-full flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
267
+ >
268
+ <Plus className="w-4 h-4" />
269
+ New Chat
270
+ </button>
271
+ </div>
272
+
273
+ {/* Chat History */}
274
+ <div className="flex-1 overflow-y-auto p-4">
275
+ <h3 className="text-sm font-medium text-gray-400 mb-3">💬 Chat History</h3>
276
+ {sessions.length > 0 ? (
277
+ <div className="space-y-2">
278
+ {sessions.map((session) => (
279
+ <div key={session.id} className="group flex items-center gap-2">
280
+ <button
281
+ onClick={() => loadSession(session)}
282
+ className={`flex-1 text-left px-3 py-2 rounded-lg transition-colors ${
283
+ session.id === currentSessionId
284
+ ? 'bg-blue-600 text-white'
285
+ : 'bg-gray-700 hover:bg-gray-600 text-gray-300'
286
+ }`}
287
+ >
288
+ <div className="text-sm font-medium truncate">{session.title}</div>
289
+ <div className="text-xs text-gray-400">
290
+ {new Date(session.updatedAt).toLocaleDateString()}
291
+ </div>
292
+ </button>
293
+ <button
294
+ onClick={() => deleteSession(session.id)}
295
+ className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all"
296
+ >
297
+ <Trash2 className="w-4 h-4" />
298
+ </button>
299
+ </div>
300
+ ))}
301
+ </div>
302
+ ) : (
303
+ <p className="text-gray-500 text-sm">No previous chats yet</p>
304
+ )}
305
+ </div>
306
+
307
+ {/* Settings */}
308
+ <div className="p-4 border-t border-gray-700 space-y-4">
309
+ {/* Online Users */}
310
+ <div>
311
+ <h3 className="text-sm font-medium text-gray-400 mb-2">👥 Who's Online</h3>
312
+ <div className="flex items-center gap-2 text-sm">
313
+ <div className="w-2 h-2 bg-green-500 rounded-full"></div>
314
+ <span>{onlineUsers === 1 ? 'Just you online' : `${onlineUsers} people online`}</span>
315
+ </div>
316
+ <p className="text-xs text-gray-500 mt-1">You: {userId}</p>
317
+ </div>
318
+
319
+ {/* Model Selection */}
320
+ <div>
321
+ <label className="block text-sm font-medium text-gray-400 mb-2">AI Model</label>
322
+ <select
323
+ value={selectedModel}
324
+ onChange={(e) => setSelectedModel(e.target.value)}
325
+ className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
326
+ >
327
+ {models.map((model) => (
328
+ <option key={model.id} value={model.id}>
329
+ {model.name}
330
+ </option>
331
+ ))}
332
+ </select>
333
+ <p className="text-xs text-green-400 mt-1 font-mono">{selectedModel}</p>
334
+ </div>
335
+
336
+ {/* API Status */}
337
+ <div className="flex items-center gap-2 text-sm">
338
+ <div className={`w-2 h-2 rounded-full ${apiStatus === 'Connected' ? 'bg-green-500' : 'bg-red-500'}`}></div>
339
+ <span>{apiStatus === 'Connected' ? '🟢 API Connected' : '🔴 Connection Issue'}</span>
340
+ </div>
341
+
342
+ {/* Controls */}
343
+ <div className="flex gap-2">
344
+ <button
345
+ onClick={downloadHistory}
346
+ className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
347
+ title="Download History"
348
+ >
349
+ <Download className="w-4 h-4" />
350
+ </button>
351
+ <button
352
+ onClick={clearChat}
353
+ className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
354
+ title="Clear Chat"
355
+ >
356
+ <Trash2 className="w-4 h-4" />
357
+ </button>
358
+ <button
359
+ onClick={() => window.location.reload()}
360
+ className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
361
+ title="Refresh"
362
+ >
363
+ <RefreshCw className="w-4 h-4" />
364
+ </button>
365
+ </div>
366
+ </div>
367
+ </div>
368
+
369
+ {/* Main Chat Area */}
370
+ <div className="flex-1 flex flex-col">
371
+ {/* Chat Messages */}
372
+ <div className="flex-1 overflow-y-auto">
373
+ {messages.length === 0 ? (
374
+ <div className="h-full flex items-center justify-center">
375
+ <div className="text-center max-w-md mx-auto px-4">
376
+ <div className="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-6">
377
+ <Bot className="w-8 h-8 text-blue-400" />
378
+ </div>
379
+ <h2 className="text-2xl font-semibold mb-4 text-gray-100">Your personal assistant</h2>
380
+ <p className="text-gray-400 leading-relaxed">
381
+ A personal assistant streamlines your life by managing tasks, schedules,
382
+ and communications efficiently.
383
+ </p>
384
+ </div>
385
+ </div>
386
+ ) : (
387
+ <div className="p-6 space-y-6 max-w-4xl mx-auto">
388
+ {messages.map((message, index) => (
389
+ <div key={index} className="flex gap-4">
390
+ <div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
391
+ {message.role === 'user' ? (
392
+ <div className="w-6 h-6 bg-blue-600 rounded-full"></div>
393
+ ) : (
394
+ <Bot className="w-5 h-5 text-blue-400" />
395
+ )}
396
+ </div>
397
+ <div className="flex-1 min-w-0">
398
+ <div className="prose prose-invert max-w-none">
399
+ {message.role === 'assistant' && message.content.includes('---\n*Response created by:') ? (
400
+ <>
401
+ <div className="whitespace-pre-wrap text-gray-100">
402
+ {message.content.split('\n\n---\n*Response created by:')[0]}
403
+ </div>
404
+ <div className="text-xs text-gray-500 mt-2 italic">
405
+ Response created by: <strong>{message.content.split('**')[1]}</strong>
406
+ </div>
407
+ </>
408
+ ) : (
409
+ <div className="whitespace-pre-wrap text-gray-100">{message.content}</div>
410
+ )}
411
+ </div>
412
+ </div>
413
+ </div>
414
+ ))}
415
+ {isLoading && (
416
+ <div className="flex gap-4">
417
+ <div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
418
+ <Bot className="w-5 h-5 text-blue-400" />
419
+ </div>
420
+ <div className="flex-1">
421
+ <div className="flex items-center gap-2 text-gray-400">
422
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
423
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{animationDelay: '0.1s'}}></div>
424
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{animationDelay: '0.2s'}}></div>
425
+ </div>
426
+ </div>
427
+ </div>
428
+ )}
429
+ <div ref={messagesEndRef} />
430
+ </div>
431
+ )}
432
+ </div>
433
+
434
+ {/* Message Input */}
435
+ <div className="border-t border-gray-700 p-4">
436
+ <div className="max-w-4xl mx-auto">
437
+ <div className="flex gap-3">
438
+ <div className="flex-1 relative">
439
+ <input
440
+ type="text"
441
+ value={currentMessage}
442
+ onChange={(e) => setCurrentMessage(e.target.value)}
443
+ onKeyDown={(e) => {
444
+ if (e.key === 'Enter' && !e.shiftKey) {
445
+ e.preventDefault();
446
+ handleSendMessage(e);
447
+ }
448
+ }}
449
+ placeholder="Chat Smarter. Chat many Brains"
450
+ className="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
451
+ disabled={isLoading}
452
+ />
453
+ </div>
454
+ <button
455
+ onClick={handleSendMessage}
456
+ disabled={!currentMessage.trim() || isLoading}
457
+ className="px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg transition-colors flex items-center gap-2"
458
+ >
459
+ <Send className="w-4 h-4" />
460
+ Send
461
+ </button>
462
+ </div>
463
+ <div className="mt-2 text-center">
464
+ <span className="text-xs text-gray-500">Currently using: <strong>{getSelectedModelName()}</strong></span>
465
+ </div>
466
+ </div>
467
+ </div>
468
+ </div>
469
+ </div>
470
+ );
471
+ };
472
+
473
+ export default ChatFlowApp;