uumerrr684 commited on
Commit
1aa5a0c
·
verified ·
1 Parent(s): 80641f3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -626
app.py CHANGED
@@ -35,7 +35,7 @@
35
  </div>
36
  <h1 class="text-xl font-semibold">Chat Flow</h1>
37
  </div>
38
- <button id="new-chat-btn" class="w-full flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors">
39
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
40
  <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
41
  </svg>
@@ -43,40 +43,22 @@
43
  </button>
44
  </div>
45
 
46
- <!-- Chat History -->
47
- <div class="flex-1 overflow-y-auto p-4 chat-scroll">
48
- <h3 class="text-sm font-medium text-gray-400 mb-3">💬 Chat History</h3>
49
- <div id="sessions-list" class="space-y-2">
50
- <p class="text-gray-500 text-sm">No previous chats yet</p>
51
- </div>
52
- </div>
53
-
54
  <!-- Settings -->
55
- <div class="p-4 border-t border-gray-700 space-y-4">
56
  <!-- Online Users -->
57
  <div>
58
  <h3 class="text-sm font-medium text-gray-400 mb-2">👥 Who's Online</h3>
59
  <div class="flex items-center gap-2 text-sm mb-2">
60
  <div class="w-2 h-2 bg-green-500 rounded-full"></div>
61
- <span id="online-status">Just you online</span>
62
  </div>
63
-
64
- <!-- User Details -->
65
- <div class="bg-gray-700 rounded-lg p-3 mb-2 max-h-32 overflow-y-auto chat-scroll">
66
- <div id="users-list" class="space-y-1 text-xs">
67
- <div class="text-green-400">🟢 <span id="current-user-info">You: Loading...</span></div>
68
- </div>
69
- </div>
70
-
71
- <button id="refresh-users-btn" class="w-full text-xs px-2 py-1 bg-gray-600 hover:bg-gray-500 rounded transition-colors">
72
- 🔄 Refresh Users
73
- </button>
74
  </div>
75
 
76
  <!-- Model Selection -->
77
  <div>
78
  <label class="block text-sm font-medium text-gray-400 mb-2">AI Model</label>
79
- <select id="model-select" class="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">
80
  <option value="openai/gpt-3.5-turbo">GPT-3.5 Turbo</option>
81
  <option value="meta-llama/llama-3.1-8b-instruct">LLaMA 3.1 8B</option>
82
  <option value="meta-llama/llama-3.1-70b-instruct">LLaMA 3.1 70B</option>
@@ -88,28 +70,23 @@
88
  <option value="google/gemma-3-4b-it:free">Gemma 3 4B</option>
89
  <option value="openrouter/auto">Auto (Best Available)</option>
90
  </select>
91
- <p class="text-xs text-green-400 mt-1 font-mono" id="model-id">openai/gpt-3.5-turbo</p>
92
  </div>
93
 
94
  <!-- API Status -->
95
  <div class="flex items-center gap-2 text-sm">
96
- <div id="api-dot" class="w-2 h-2 rounded-full bg-green-500"></div>
97
- <span id="api-status">🟢 API Connected</span>
98
  </div>
99
 
100
  <!-- Controls -->
101
  <div class="flex gap-2">
102
- <button id="download-btn" class="flex-1 flex items-center justify-center px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm" title="Download History">
103
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
104
- <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
105
- </svg>
106
- </button>
107
- <button id="clear-btn" class="flex-1 flex items-center justify-center px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm" title="Clear Chat">
108
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
109
  <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
110
  </svg>
111
  </button>
112
- <button id="refresh-btn" class="flex-1 flex items-center justify-center px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm" title="Refresh">
113
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
114
  <path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
115
  </svg>
@@ -121,9 +98,9 @@
121
  <!-- Main Chat Area -->
122
  <div class="flex-1 flex flex-col">
123
  <!-- Chat Messages -->
124
- <div id="messages-container" class="flex-1 overflow-y-auto chat-scroll">
125
  <!-- Welcome Screen -->
126
- <div id="welcome-screen" class="h-full flex items-center justify-center">
127
  <div class="text-center max-w-md mx-auto px-4">
128
  <div class="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-6">
129
  <svg class="w-8 h-8 text-blue-400" fill="currentColor" viewBox="0 0 24 24">
@@ -136,7 +113,7 @@
136
  </div>
137
 
138
  <!-- Messages Area -->
139
- <div id="messages-area" class="p-6 space-y-6 max-w-4xl mx-auto" style="display: none;">
140
  </div>
141
  </div>
142
 
@@ -144,16 +121,14 @@
144
  <div class="border-t border-gray-700 p-4">
145
  <div class="max-w-4xl mx-auto">
146
  <div class="flex gap-3">
147
- <div class="flex-1 relative">
148
- <input
149
- type="text"
150
- id="message-input"
151
- placeholder="Chat Smarter. Chat many Brains"
152
- class="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"
153
- />
154
- </div>
155
  <button
156
- id="send-button"
157
  class="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"
158
  >
159
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
@@ -163,7 +138,7 @@
163
  </button>
164
  </div>
165
  <div class="mt-2 text-center">
166
- <span class="text-xs text-gray-500">Currently using: <strong id="current-model-name">GPT-3.5 Turbo</strong></span>
167
  </div>
168
  </div>
169
  </div>
@@ -172,14 +147,12 @@
172
 
173
  <script>
174
  // 🔑 OpenRouter API Key
175
- const OPENROUTER_API_KEY = "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";
176
 
177
  // Global variables
178
  let messages = [];
179
- let sessions = [];
180
- let currentSessionId = 'default';
181
- let isLoading = false;
182
  let selectedModel = 'openai/gpt-3.5-turbo';
 
183
  let userId = 'User-' + Math.random().toString(36).substr(2, 8);
184
 
185
  const models = {
@@ -195,387 +168,175 @@
195
  'openrouter/auto': 'Auto (Best Available)'
196
  };
197
 
198
- // Initialize
199
- document.addEventListener('DOMContentLoaded', async function() {
200
- console.log('DOM loaded, initializing app...');
201
-
202
- // Setup all event listeners first
203
  setupEventListeners();
204
-
205
- // Get user location first (one time only)
206
- await getUserLocation();
207
- console.log('User location obtained:', currentUserLocation);
208
-
209
- // Initialize database with current user only
210
- updateOnlineUsers();
211
- checkAPIStatus();
212
-
213
- // Initialize send button text
214
- const sendButton = document.getElementById('send-button');
215
- if (sendButton) {
216
- sendButton.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> Send';
217
- }
218
-
219
- // Update users display every 30 seconds (like Streamlit file checking)
220
- setInterval(updateOnlineUsers, 30000);
221
-
222
- // Track user activity (but don't add fake users)
223
- document.addEventListener('click', trackActivity);
224
- document.addEventListener('keypress', trackActivity);
225
- document.addEventListener('scroll', trackActivity);
226
-
227
- console.log('App initialization complete');
228
  });
229
 
230
  function setupEventListeners() {
231
- console.log('Setting up event listeners...');
232
-
233
- // Model selection
234
- const modelSelect = document.getElementById('model-select');
235
- if (modelSelect) {
236
- modelSelect.addEventListener('change', function(e) {
237
- selectedModel = e.target.value;
238
- document.getElementById('model-id').textContent = selectedModel;
239
- document.getElementById('current-model-name').textContent = models[selectedModel];
240
- console.log('Model changed to:', selectedModel);
241
- });
242
- }
243
-
244
- // Enter key to send message
245
- const messageInput = document.getElementById('message-input');
246
- if (messageInput) {
247
- messageInput.addEventListener('keydown', function(e) {
248
- if (e.key === 'Enter' && !e.shiftKey) {
249
- e.preventDefault();
250
- console.log('Enter key pressed, sending message...');
251
- sendMessage();
252
- }
253
- });
254
- console.log('Message input event listener added');
255
- }
256
 
257
- // Send button click
258
- const sendButton = document.getElementById('send-button');
259
- if (sendButton) {
260
- sendButton.addEventListener('click', function(e) {
261
- e.preventDefault();
262
- console.log('Send button clicked, sending message...');
263
  sendMessage();
264
- });
265
- console.log('Send button event listener added');
266
- }
267
-
268
- // New chat button
269
- const newChatBtn = document.getElementById('new-chat-btn');
270
- if (newChatBtn) {
271
- newChatBtn.addEventListener('click', function(e) {
272
- e.preventDefault();
273
- console.log('New chat button clicked');
274
- startNewChat();
275
- });
276
- }
277
 
278
- // Download button
279
- const downloadBtn = document.getElementById('download-btn');
280
- if (downloadBtn) {
281
- downloadBtn.addEventListener('click', function(e) {
282
- e.preventDefault();
283
- console.log('Download button clicked');
284
- downloadHistory();
285
- });
286
- }
287
 
288
- // Clear button
289
- const clearBtn = document.getElementById('clear-btn');
290
- if (clearBtn) {
291
- clearBtn.addEventListener('click', function(e) {
292
- e.preventDefault();
293
- console.log('Clear button clicked');
294
- clearChat();
295
- });
296
- }
297
 
298
- // Refresh button
299
- const refreshBtn = document.getElementById('refresh-btn');
300
- if (refreshBtn) {
301
- refreshBtn.addEventListener('click', function(e) {
302
- e.preventDefault();
303
- console.log('Refresh button clicked');
304
- window.location.reload();
305
- });
306
- }
307
 
308
- // Refresh users button
309
- const refreshUsersBtn = document.getElementById('refresh-users-btn');
310
- if (refreshUsersBtn) {
311
- refreshUsersBtn.addEventListener('click', function(e) {
312
- e.preventDefault();
313
- console.log('Refresh users button clicked');
314
- refreshUsers();
315
- });
316
- }
317
 
318
- console.log('All event listeners set up successfully');
319
  }
320
 
321
  async function sendMessage() {
322
- console.log('sendMessage function called');
323
-
324
- const input = document.getElementById('message-input');
325
- if (!input) {
326
- console.error('Message input not found');
327
- return;
328
- }
329
-
330
  const message = input.value.trim();
331
- console.log('Message to send:', message);
 
332
 
333
  if (!message || isLoading) {
334
- console.log('No message or already loading');
335
  return;
336
  }
337
 
338
- // Track activity when sending message
339
- trackActivity();
340
-
341
  // Add user message
342
- const userMessage = {
343
  role: 'user',
344
- content: message,
345
- timestamp: new Date().toISOString()
346
- };
347
 
348
- messages.push(userMessage);
349
  input.value = '';
350
- console.log('User message added to messages array');
351
-
352
- // Add empty assistant message for streaming
353
- const assistantMessage = {
354
- role: 'assistant',
355
- content: '',
356
- timestamp: new Date().toISOString()
357
- };
358
- messages.push(assistantMessage);
359
-
360
- updateMessagesDisplay();
361
  setLoading(true);
362
 
363
  try {
364
- console.log('Calling getAIResponse...');
365
- // The streaming will update the message in real-time
366
- await getAIResponse(message);
367
-
368
- // Track activity after receiving response
369
- trackActivity();
370
- console.log('AI response completed');
371
  } catch (error) {
372
- console.error('Error getting AI response:', error);
373
- // Replace the empty message with error
374
- messages[messages.length - 1].content = 'Sorry, I encountered an error. Please try again.';
375
- updateMessagesDisplay();
 
 
376
  }
377
 
378
  setLoading(false);
379
  }
380
 
381
  async function getAIResponse(userMessage) {
382
- if (!OPENROUTER_API_KEY || OPENROUTER_API_KEY === "YOUR_API_KEY_HERE") {
383
- throw new Error("Please add your OpenRouter API key to the code.");
384
- }
385
-
386
- const url = "https://openrouter.ai/api/v1/chat/completions";
387
- const headers = {
388
- "Content-Type": "application/json",
389
- "Authorization": "Bearer " + OPENROUTER_API_KEY,
390
- "HTTP-Referer": "https://huggingface.co/spaces",
391
- "X-Title": "Chat Flow AI Assistant"
392
- };
393
-
394
- const cleanMessages = messages.slice(0, -1).map(msg => ({
395
- role: msg.role,
396
- content: msg.content.split('\n\n---\n*Response created by:')[0]
397
- }));
398
-
399
- const apiMessages = [
400
- { role: "system", content: "You are a helpful AI assistant. Provide clear and helpful responses." }
401
- ].concat(cleanMessages).concat([
402
- { role: "user", content: userMessage }
403
- ]);
404
-
405
- const data = {
406
- model: selectedModel,
407
- messages: apiMessages,
408
- stream: true,
409
- max_tokens: 2000,
410
- temperature: 0.7
411
- };
412
-
413
- try {
414
- const response = await fetch(url, {
415
- method: 'POST',
416
- headers: headers,
417
- body: JSON.stringify(data)
418
- });
419
-
420
- if (!response.ok) {
421
- const errorText = await response.text();
422
- throw new Error("API Error: " + response.status + " - " + errorText);
423
  }
 
424
 
425
- const reader = response.body.getReader();
426
- const decoder = new TextDecoder();
427
- let fullResponse = "";
428
-
429
- while (true) {
430
- const { done, value } = await reader.read();
431
- if (done) break;
432
-
433
- const chunk = decoder.decode(value, { stream: true });
434
- const lines = chunk.split('\n');
435
-
436
- for (const line of lines) {
437
- if (line.trim() === '') continue;
438
-
439
- if (line.startsWith('data: ')) {
440
- const data = line.slice(6).trim();
441
-
442
- if (data === '[DONE]') {
443
- const modelName = models[selectedModel] || "AI";
444
- const finalResponse = fullResponse + "\n\n---\n*Response created by: **" + modelName + "***";
445
-
446
- // Update final message
447
- messages[messages.length - 1].content = finalResponse;
448
- updateMessagesDisplay();
449
- return finalResponse;
450
- }
451
-
452
- if (data === '') continue;
453
-
454
- try {
455
- const parsed = JSON.parse(data);
456
- const delta = parsed.choices?.[0]?.delta?.content;
457
-
458
- if (delta) {
459
- fullResponse += delta;
460
- // Update streaming message immediately
461
- updateStreamingMessage(fullResponse);
462
- }
463
- } catch (e) {
464
- // Skip invalid JSON
465
- continue;
466
- }
467
- }
468
- }
469
- }
470
-
471
- const modelName = models[selectedModel] || "AI";
472
- const finalResponse = fullResponse + "\n\n---\n*Response created by: **" + modelName + "***";
473
- messages[messages.length - 1].content = finalResponse;
474
- updateMessagesDisplay();
475
- return finalResponse;
476
-
477
- } catch (error) {
478
- console.error('Streaming error:', error);
479
- throw error;
480
- }
481
- }
482
 
483
- function updateStreamingMessage(partialResponse) {
484
- // Update the last message in real-time
485
- if (messages.length > 0) {
486
- const lastMessage = messages[messages.length - 1];
487
- if (lastMessage.role === 'assistant') {
488
- lastMessage.content = partialResponse;
489
-
490
- // Update display with streaming cursor
491
- const messagesArea = document.getElementById('messages-area');
492
- const messageElements = messagesArea.children;
493
-
494
- if (messageElements.length > 0) {
495
- const lastMessageElement = messageElements[messageElements.length - 1];
496
- const contentDiv = lastMessageElement.querySelector('.whitespace-pre-wrap');
497
- if (contentDiv) {
498
- contentDiv.textContent = partialResponse + " ▌";
499
- }
500
- }
501
-
502
- // Auto scroll
503
- messagesArea.scrollTop = messagesArea.scrollHeight;
504
- }
505
  }
506
- }
507
 
508
- function setLoading(loading) {
509
- isLoading = loading;
510
- const button = document.getElementById('send-button');
511
- const input = document.getElementById('message-input');
512
 
513
- if (button && input) {
514
- if (loading) {
515
- button.disabled = true;
516
- input.disabled = true;
517
- button.innerHTML = '<div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div> Loading...';
518
- } else {
519
- button.disabled = false;
520
- input.disabled = false;
521
- button.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> Send';
522
- }
523
- }
524
  }
525
 
526
- function updateMessagesDisplay() {
527
- const welcomeScreen = document.getElementById('welcome-screen');
528
- const messagesArea = document.getElementById('messages-area');
529
-
530
  if (messages.length === 0) {
531
- welcomeScreen.style.display = 'flex';
532
  messagesArea.style.display = 'none';
533
  return;
534
  }
535
 
536
- welcomeScreen.style.display = 'none';
537
  messagesArea.style.display = 'block';
538
-
539
  let html = '';
540
- messages.forEach((message, index) => {
541
- const isUser = message.role === 'user';
542
- const avatar = isUser
543
- ? '<div class="w-6 h-6 bg-blue-600 rounded-full"></div>'
544
- : '<svg class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
545
-
546
- let content = message.content;
547
  let attribution = '';
548
-
549
- // Handle streaming messages
550
- if (message.role === 'assistant') {
551
- if (content === '' && isLoading) {
552
- // Show loading dots for empty assistant message
553
- content = '';
554
- html += `
555
- <div class="flex gap-4">
556
- <div class="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
557
- ${avatar}
558
- </div>
559
- <div class="flex-1">
560
- <div class="flex items-center gap-2 text-gray-400">
561
- <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-1"></div>
562
- <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-2"></div>
563
- <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-3"></div>
564
- </div>
565
- </div>
566
- </div>
567
- `;
568
- return;
569
- } else if (content.includes('---\n*Response created by:')) {
570
- const parts = content.split('\n\n---\n*Response created by:');
571
- content = parts[0];
572
- if (parts[1]) {
573
- const modelName = parts[1].replace(/\*\*/g, '').replace(/\*/g, '');
574
- attribution = '<div class="text-xs text-gray-500 mt-2 italic">Response created by: <strong>' + modelName + '</strong></div>';
575
- }
576
  }
577
  }
578
-
579
  html += `
580
  <div class="flex gap-4">
581
  <div class="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
@@ -588,263 +349,29 @@
588
  </div>
589
  `;
590
  });
591
-
592
  messagesArea.innerHTML = html;
593
  messagesArea.scrollTop = messagesArea.scrollHeight;
594
  }
595
 
596
- function startNewChat() {
597
- if (messages.length > 0) {
598
- const title = generateChatTitle(messages);
599
- const newSession = {
600
- id: 'session-' + Date.now(),
601
- title: title,
602
- messages: messages.slice(),
603
- createdAt: new Date().toISOString(),
604
- updatedAt: new Date().toISOString()
605
- };
606
- sessions.unshift(newSession);
607
- updateSessionsList();
608
- }
609
-
610
- messages = [];
611
- currentSessionId = 'session-' + Date.now();
612
- updateMessagesDisplay();
613
- }
614
-
615
- function generateChatTitle(msgs) {
616
- if (!msgs || msgs.length === 0) return "New Chat";
617
- const firstUserMessage = msgs.find(m => m.role === 'user');
618
- if (!firstUserMessage) return "New Chat";
619
- const content = firstUserMessage.content;
620
- return content.length > 30 ? content.substring(0, 30) + "..." : content;
621
- }
622
-
623
- function updateSessionsList() {
624
- const sessionsList = document.getElementById('sessions-list');
625
-
626
- if (sessions.length === 0) {
627
- sessionsList.innerHTML = '<p class="text-gray-500 text-sm">No previous chats yet</p>';
628
- return;
629
- }
630
-
631
- let html = '';
632
- sessions.forEach(session => {
633
- const isCurrent = session.id === currentSessionId;
634
- html += `
635
- <div class="group flex items-center gap-2">
636
- <button onclick="loadSession('${session.id}')" class="flex-1 text-left px-3 py-2 rounded-lg transition-colors ${isCurrent ? 'bg-blue-600 text-white' : 'bg-gray-700 hover:bg-gray-600 text-gray-300'}">
637
- <div class="text-sm font-medium truncate">${session.title}</div>
638
- <div class="text-xs text-gray-400">${new Date(session.updatedAt).toLocaleDateString()}</div>
639
- </button>
640
- <button onclick="deleteSession('${session.id}')" class="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all">
641
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
642
- </button>
643
- </div>
644
- `;
645
- });
646
-
647
- sessionsList.innerHTML = html;
648
- }
649
-
650
- function loadSession(sessionId) {
651
- const session = sessions.find(s => s.id === sessionId);
652
- if (!session) return;
653
-
654
- messages = session.messages.slice();
655
- currentSessionId = sessionId;
656
- updateMessagesDisplay();
657
- updateSessionsList();
658
- }
659
-
660
- function deleteSession(sessionId) {
661
- sessions = sessions.filter(s => s.id !== sessionId);
662
- if (sessionId === currentSessionId) {
663
- startNewChat();
664
- }
665
- updateSessionsList();
666
- }
667
-
668
- function clearChat() {
669
- messages = [];
670
- updateMessagesDisplay();
671
- }
672
-
673
- function downloadHistory() {
674
- const dataStr = JSON.stringify(messages, null, 2);
675
- const dataBlob = new Blob([dataStr], { type: 'application/json' });
676
- const url = URL.createObjectURL(dataBlob);
677
- const link = document.createElement('a');
678
- link.href = url;
679
- link.download = 'chat_history_' + new Date().toISOString().split('T')[0] + '.json';
680
- link.click();
681
- URL.revokeObjectURL(url);
682
- }
683
-
684
- // Real database-style user tracking (like Streamlit version)
685
- let userDatabase = new Map(); // Simulates your database file
686
- let currentUserLocation = { city: 'Unknown', country: 'Unknown' };
687
- let isGettingLocation = false;
688
-
689
- // Get user's real location (one time only)
690
- async function getUserLocation() {
691
- if (isGettingLocation || currentUserLocation.city !== 'Unknown') return currentUserLocation;
692
- isGettingLocation = true;
693
-
694
- try {
695
- // Try geolocation API first
696
- if (navigator.geolocation) {
697
- const position = await new Promise((resolve, reject) => {
698
- navigator.geolocation.getCurrentPosition(resolve, reject, {
699
- timeout: 10000,
700
- enableHighAccuracy: false
701
- });
702
- });
703
-
704
- const { latitude, longitude } = position.coords;
705
-
706
- // Use reverse geocoding to get city and country
707
- try {
708
- const response = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${latitude}&longitude=${longitude}&localityLanguage=en`);
709
- const data = await response.json();
710
-
711
- currentUserLocation = {
712
- city: data.city || data.locality || 'Unknown',
713
- country: data.countryName || 'Unknown'
714
- };
715
- } catch (geocodeError) {
716
- console.log('Geocoding failed, using IP fallback');
717
- await getLocationByIP();
718
- }
719
- } else {
720
- await getLocationByIP();
721
- }
722
- } catch (error) {
723
- console.log('Geolocation failed, using IP fallback');
724
- await getLocationByIP();
725
- }
726
-
727
- return currentUserLocation;
728
- }
729
-
730
- // Fallback: Get location by IP (one time only)
731
- async function getLocationByIP() {
732
- try {
733
- const response = await fetch('https://ipapi.co/json/');
734
- const data = await response.json();
735
-
736
- currentUserLocation = {
737
- city: data.city || 'Unknown',
738
- country: data.country_name || 'Unknown'
739
- };
740
- } catch (error) {
741
- console.log('IP location failed');
742
- currentUserLocation = { city: 'Unknown', country: 'Unknown' };
743
- }
744
- }
745
-
746
- // Database-style user management (exactly like your Streamlit)
747
- function updateOnlineUsers() {
748
- const now = Date.now();
749
-
750
- // Add/update current user in database
751
- userDatabase.set(userId, {
752
- id: userId,
753
- location: currentUserLocation,
754
- lastSeen: now,
755
- isCurrentUser: true
756
- });
757
-
758
- // Clean up users not seen in last 5 minutes (like your Streamlit HISTORY_FILE logic)
759
- const fiveMinutesAgo = now - (5 * 60 * 1000);
760
- for (const [id, userData] of userDatabase.entries()) {
761
- if (!userData.isCurrentUser && userData.lastSeen < fiveMinutesAgo) {
762
- userDatabase.delete(id);
763
- }
764
- }
765
-
766
- updateUsersDisplay();
767
- }
768
-
769
- function updateUsersDisplay() {
770
- const count = userDatabase.size;
771
- const status = count === 1 ? 'Just you online' : count + ' people online';
772
- document.getElementById('online-status').textContent = status;
773
-
774
- // Update users list - show ONLY real database users
775
- const usersList = document.getElementById('users-list');
776
- const currentUserInfo = document.getElementById('current-user-info');
777
-
778
- // Update current user info
779
- currentUserInfo.textContent = `You: ${userId} (${currentUserLocation.city}, ${currentUserLocation.country})`;
780
-
781
- let html = `<div class="text-green-400">🟢 You: ${userId}<br><span class="text-gray-400 ml-4">${currentUserLocation.city}, ${currentUserLocation.country}</span></div>`;
782
 
783
- // Add ONLY other users from database (no fake users)
784
- const otherUsers = Array.from(userDatabase.values()).filter(userData => !userData.isCurrentUser);
785
 
786
- if (otherUsers.length === 0) {
787
- html += '<div class="text-gray-500 text-xs mt-2">No other users currently online</div>';
788
  } else {
789
- otherUsers.forEach(userData => {
790
- const timeAgo = Math.floor((Date.now() - userData.lastSeen) / 1000);
791
- const timeText = timeAgo < 60 ? 'now' : `${Math.floor(timeAgo / 60)}m ago`;
792
-
793
- html += `
794
- <div class="text-blue-400 mt-2">
795
- 🔵 ${userData.id}<br>
796
- <span class="text-gray-400 ml-4">${userData.location.city}, ${userData.location.country}</span><br>
797
- <span class="text-gray-500 ml-4 text-xs">Active ${timeText}</span>
798
- </div>
799
- `;
800
- });
801
  }
802
-
803
- usersList.innerHTML = html;
804
- }
805
-
806
- function refreshUsers() {
807
- // Just refresh display, don't add fake users
808
- trackActivity();
809
  }
810
 
811
- function trackActivity() {
812
- const now = Date.now();
813
-
814
- // Update current user's last seen time in database
815
- if (userDatabase.has(userId)) {
816
- const userData = userDatabase.get(userId);
817
- userData.lastSeen = now;
818
- userDatabase.set(userId, userData);
819
- }
820
-
821
- // Only update display, no fake user generation
822
- updateUsersDisplay();
823
- }
824
-
825
- async function checkAPIStatus() {
826
- try {
827
- if (!OPENROUTER_API_KEY || OPENROUTER_API_KEY === "YOUR_API_KEY_HERE") {
828
- document.getElementById('api-status').textContent = '🔴 No API Key';
829
- document.getElementById('api-dot').className = 'w-2 h-2 rounded-full bg-red-500';
830
- return;
831
- }
832
-
833
- const response = await fetch("https://openrouter.ai/api/v1/models", {
834
- headers: { "Authorization": "Bearer " + OPENROUTER_API_KEY }
835
- });
836
-
837
- if (response.ok) {
838
- document.getElementById('api-status').textContent = '🟢 API Connected';
839
- document.getElementById('api-dot').className = 'w-2 h-2 rounded-full bg-green-500';
840
- } else {
841
- document.getElementById('api-status').textContent = '🔴 Connection Issue';
842
- document.getElementById('api-dot').className = 'w-2 h-2 rounded-full bg-red-500';
843
- }
844
- } catch (error) {
845
- document.getElementById('api-status').textContent = '🔴 Connection Issue';
846
- document.getElementById('api-dot').className = 'w-2 h-2 rounded-full bg-red-500';
847
- }
848
  }
849
  </script>
850
  </body>
 
35
  </div>
36
  <h1 class="text-xl font-semibold">Chat Flow</h1>
37
  </div>
38
+ <button id="newChatBtn" class="w-full flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors">
39
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
40
  <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
41
  </svg>
 
43
  </button>
44
  </div>
45
 
 
 
 
 
 
 
 
 
46
  <!-- Settings -->
47
+ <div class="p-4 border-t border-gray-700 space-y-4 flex-1">
48
  <!-- Online Users -->
49
  <div>
50
  <h3 class="text-sm font-medium text-gray-400 mb-2">👥 Who's Online</h3>
51
  <div class="flex items-center gap-2 text-sm mb-2">
52
  <div class="w-2 h-2 bg-green-500 rounded-full"></div>
53
+ <span id="onlineStatus">Just you online</span>
54
  </div>
55
+ <div class="text-xs text-gray-500" id="currentUser">You: Loading...</div>
 
 
 
 
 
 
 
 
 
 
56
  </div>
57
 
58
  <!-- Model Selection -->
59
  <div>
60
  <label class="block text-sm font-medium text-gray-400 mb-2">AI Model</label>
61
+ <select id="modelSelect" class="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">
62
  <option value="openai/gpt-3.5-turbo">GPT-3.5 Turbo</option>
63
  <option value="meta-llama/llama-3.1-8b-instruct">LLaMA 3.1 8B</option>
64
  <option value="meta-llama/llama-3.1-70b-instruct">LLaMA 3.1 70B</option>
 
70
  <option value="google/gemma-3-4b-it:free">Gemma 3 4B</option>
71
  <option value="openrouter/auto">Auto (Best Available)</option>
72
  </select>
73
+ <p class="text-xs text-green-400 mt-1 font-mono" id="modelId">openai/gpt-3.5-turbo</p>
74
  </div>
75
 
76
  <!-- API Status -->
77
  <div class="flex items-center gap-2 text-sm">
78
+ <div id="apiDot" class="w-2 h-2 rounded-full bg-green-500"></div>
79
+ <span id="apiStatus">🟢 API Connected</span>
80
  </div>
81
 
82
  <!-- Controls -->
83
  <div class="flex gap-2">
84
+ <button id="clearBtn" class="flex-1 flex items-center justify-center px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm" title="Clear Chat">
 
 
 
 
 
85
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
86
  <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
87
  </svg>
88
  </button>
89
+ <button id="refreshBtn" class="flex-1 flex items-center justify-center px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm" title="Refresh">
90
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
91
  <path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
92
  </svg>
 
98
  <!-- Main Chat Area -->
99
  <div class="flex-1 flex flex-col">
100
  <!-- Chat Messages -->
101
+ <div id="messagesContainer" class="flex-1 overflow-y-auto chat-scroll">
102
  <!-- Welcome Screen -->
103
+ <div id="welcomeScreen" class="h-full flex items-center justify-center">
104
  <div class="text-center max-w-md mx-auto px-4">
105
  <div class="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-6">
106
  <svg class="w-8 h-8 text-blue-400" fill="currentColor" viewBox="0 0 24 24">
 
113
  </div>
114
 
115
  <!-- Messages Area -->
116
+ <div id="messagesArea" class="p-6 space-y-6 max-w-4xl mx-auto" style="display: none;">
117
  </div>
118
  </div>
119
 
 
121
  <div class="border-t border-gray-700 p-4">
122
  <div class="max-w-4xl mx-auto">
123
  <div class="flex gap-3">
124
+ <input
125
+ type="text"
126
+ id="messageInput"
127
+ placeholder="Chat Smarter. Chat many Brains"
128
+ class="flex-1 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"
129
+ />
 
 
130
  <button
131
+ id="sendBtn"
132
  class="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"
133
  >
134
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
 
138
  </button>
139
  </div>
140
  <div class="mt-2 text-center">
141
+ <span class="text-xs text-gray-500">Currently using: <strong id="currentModelName">GPT-3.5 Turbo</strong></span>
142
  </div>
143
  </div>
144
  </div>
 
147
 
148
  <script>
149
  // 🔑 OpenRouter API Key
150
+ const API_KEY = "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";
151
 
152
  // Global variables
153
  let messages = [];
 
 
 
154
  let selectedModel = 'openai/gpt-3.5-turbo';
155
+ let isLoading = false;
156
  let userId = 'User-' + Math.random().toString(36).substr(2, 8);
157
 
158
  const models = {
 
168
  'openrouter/auto': 'Auto (Best Available)'
169
  };
170
 
171
+ // Initialize app
172
+ document.addEventListener('DOMContentLoaded', function() {
173
+ console.log('App starting...');
 
 
174
  setupEventListeners();
175
+ updateUserDisplay();
176
+ console.log('App ready!');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  });
178
 
179
  function setupEventListeners() {
180
+ // Send button
181
+ document.getElementById('sendBtn').onclick = function() {
182
+ console.log('Send button clicked');
183
+ sendMessage();
184
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
+ // Enter key
187
+ document.getElementById('messageInput').onkeypress = function(e) {
188
+ if (e.key === 'Enter') {
189
+ console.log('Enter pressed');
 
 
190
  sendMessage();
191
+ }
192
+ };
 
 
 
 
 
 
 
 
 
 
 
193
 
194
+ // Model selection
195
+ document.getElementById('modelSelect').onchange = function(e) {
196
+ selectedModel = e.target.value;
197
+ document.getElementById('modelId').textContent = selectedModel;
198
+ document.getElementById('currentModelName').textContent = models[selectedModel];
199
+ console.log('Model changed to:', selectedModel);
200
+ };
 
 
201
 
202
+ // New chat
203
+ document.getElementById('newChatBtn').onclick = function() {
204
+ messages = [];
205
+ updateDisplay();
206
+ console.log('New chat started');
207
+ };
 
 
 
208
 
209
+ // Clear chat
210
+ document.getElementById('clearBtn').onclick = function() {
211
+ messages = [];
212
+ updateDisplay();
213
+ console.log('Chat cleared');
214
+ };
 
 
 
215
 
216
+ // Refresh
217
+ document.getElementById('refreshBtn').onclick = function() {
218
+ window.location.reload();
219
+ };
 
 
 
 
 
220
 
221
+ console.log('Event listeners setup complete');
222
  }
223
 
224
  async function sendMessage() {
225
+ const input = document.getElementById('messageInput');
 
 
 
 
 
 
 
226
  const message = input.value.trim();
227
+
228
+ console.log('Sending message:', message);
229
 
230
  if (!message || isLoading) {
231
+ console.log('No message or loading');
232
  return;
233
  }
234
 
 
 
 
235
  // Add user message
236
+ messages.push({
237
  role: 'user',
238
+ content: message
239
+ });
 
240
 
 
241
  input.value = '';
242
+ updateDisplay();
 
 
 
 
 
 
 
 
 
 
243
  setLoading(true);
244
 
245
  try {
246
+ const response = await getAIResponse(message);
247
+ messages.push({
248
+ role: 'assistant',
249
+ content: response
250
+ });
251
+ updateDisplay();
 
252
  } catch (error) {
253
+ console.error('Error:', error);
254
+ messages.push({
255
+ role: 'assistant',
256
+ content: 'Sorry, there was an error. Please try again.'
257
+ });
258
+ updateDisplay();
259
  }
260
 
261
  setLoading(false);
262
  }
263
 
264
  async function getAIResponse(userMessage) {
265
+ console.log('Getting AI response...');
266
+
267
+ const apiMessages = [{
268
+ role: "system",
269
+ content: "You are a helpful AI assistant."
270
+ }];
271
+
272
+ messages.forEach(msg => {
273
+ if (msg.role !== 'assistant' || !msg.content.includes('Response created by:')) {
274
+ apiMessages.push({
275
+ role: msg.role,
276
+ content: msg.content.split('\n\n---\n*Response created by:')[0]
277
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  }
279
+ });
280
 
281
+ const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
282
+ method: 'POST',
283
+ headers: {
284
+ "Content-Type": "application/json",
285
+ "Authorization": "Bearer " + API_KEY,
286
+ "HTTP-Referer": "https://huggingface.co/spaces",
287
+ "X-Title": "Chat Flow AI Assistant"
288
+ },
289
+ body: JSON.stringify({
290
+ model: selectedModel,
291
+ messages: apiMessages,
292
+ max_tokens: 2000,
293
+ temperature: 0.7
294
+ })
295
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
+ if (!response.ok) {
298
+ throw new Error('API Error: ' + response.status);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  }
 
300
 
301
+ const data = await response.json();
302
+ const aiResponse = data.choices[0].message.content;
303
+ const modelName = models[selectedModel] || "AI";
 
304
 
305
+ return aiResponse + "\n\n---\n*Response created by: **" + modelName + "***";
 
 
 
 
 
 
 
 
 
 
306
  }
307
 
308
+ function updateDisplay() {
309
+ const welcome = document.getElementById('welcomeScreen');
310
+ const messagesArea = document.getElementById('messagesArea');
311
+
312
  if (messages.length === 0) {
313
+ welcome.style.display = 'flex';
314
  messagesArea.style.display = 'none';
315
  return;
316
  }
317
 
318
+ welcome.style.display = 'none';
319
  messagesArea.style.display = 'block';
320
+
321
  let html = '';
322
+ messages.forEach(msg => {
323
+ const isUser = msg.role === 'user';
324
+ const avatar = isUser ?
325
+ '<div class="w-6 h-6 bg-blue-600 rounded-full"></div>' :
326
+ '<svg class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
327
+
328
+ let content = msg.content;
329
  let attribution = '';
330
+
331
+ if (msg.role === 'assistant' && content.includes('---\n*Response created by:')) {
332
+ const parts = content.split('\n\n---\n*Response created by:');
333
+ content = parts[0];
334
+ if (parts[1]) {
335
+ const modelName = parts[1].replace(/\*\*/g, '').replace(/\*/g, '');
336
+ attribution = '<div class="text-xs text-gray-500 mt-2 italic">Response created by: <strong>' + modelName + '</strong></div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  }
338
  }
339
+
340
  html += `
341
  <div class="flex gap-4">
342
  <div class="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
 
349
  </div>
350
  `;
351
  });
352
+
353
  messagesArea.innerHTML = html;
354
  messagesArea.scrollTop = messagesArea.scrollHeight;
355
  }
356
 
357
+ function setLoading(loading) {
358
+ isLoading = loading;
359
+ const btn = document.getElementById('sendBtn');
360
+ const input = document.getElementById('messageInput');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
+ btn.disabled = loading;
363
+ input.disabled = loading;
364
 
365
+ if (loading) {
366
+ btn.innerHTML = '<div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div> Loading...';
367
  } else {
368
+ btn.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> Send';
 
 
 
 
 
 
 
 
 
 
 
369
  }
 
 
 
 
 
 
 
370
  }
371
 
372
+ function updateUserDisplay() {
373
+ document.getElementById('currentUser').textContent = 'You: ' + userId;
374
+ document.getElementById('onlineStatus').textContent = 'Just you online';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  }
376
  </script>
377
  </body>