uumerrr684 commited on
Commit
69fab8c
·
verified ·
1 Parent(s): 4348736

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -166
app.py CHANGED
@@ -200,17 +200,17 @@
200
  document.addEventListener('DOMContentLoaded', async function() {
201
  setupEventListeners();
202
 
203
- // Get user location first
204
  await getUserLocation();
205
 
206
- // Then update users and check API
207
  updateOnlineUsers();
208
  checkAPIStatus();
209
 
210
- // Track activity for online users - update every 10 seconds
211
- setInterval(updateOnlineUsers, 10000);
212
 
213
- // Track user activity
214
  document.addEventListener('click', trackActivity);
215
  document.addEventListener('keypress', trackActivity);
216
  document.addEventListener('scroll', trackActivity);
@@ -252,8 +252,6 @@
252
  messages.push(userMessage);
253
  input.value = '';
254
 
255
- updateMessagesDisplay();
256
-
257
  // Add empty assistant message for streaming
258
  const assistantMessage = {
259
  role: 'assistant',
@@ -262,21 +260,17 @@
262
  };
263
  messages.push(assistantMessage);
264
 
 
265
  setLoading(true);
266
- updateMessagesDisplay(); // Show the empty assistant message with loading
267
 
268
  try {
269
- const aiResponse = await getAIResponse(message);
270
-
271
- // Update the last message with final response
272
- messages[messages.length - 1].content = aiResponse;
273
-
274
- // Remove cursor and show final response
275
- updateMessagesDisplay();
276
 
277
  // Track activity after receiving response
278
  trackActivity();
279
  } catch (error) {
 
280
  // Replace the empty message with error
281
  messages[messages.length - 1].content = 'Sorry, I encountered an error. Please try again.';
282
  updateMessagesDisplay();
@@ -298,7 +292,7 @@
298
  "X-Title": "Chat Flow AI Assistant"
299
  };
300
 
301
- const cleanMessages = messages.map(msg => ({
302
  role: msg.role,
303
  content: msg.content.split('\n\n---\n*Response created by:')[0]
304
  }));
@@ -312,49 +306,60 @@
312
  const data = {
313
  model: selectedModel,
314
  messages: apiMessages,
315
- stream: true, // Enable streaming
316
  max_tokens: 2000,
317
  temperature: 0.7
318
  };
319
 
320
- const response = await fetch(url, {
321
- method: 'POST',
322
- headers: headers,
323
- body: JSON.stringify(data)
324
- });
 
325
 
326
- if (!response.ok) {
327
- const errorText = await response.text();
328
- throw new Error("API Error: " + response.status + " - " + errorText);
329
- }
330
 
331
- const reader = response.body.getReader();
332
- const decoder = new TextDecoder();
333
- let fullResponse = "";
334
-
335
- try {
336
  while (true) {
337
  const { done, value } = await reader.read();
338
  if (done) break;
339
 
340
- const chunk = decoder.decode(value);
341
  const lines = chunk.split('\n');
342
 
343
  for (const line of lines) {
 
 
344
  if (line.startsWith('data: ')) {
345
- const data = line.slice(6);
 
346
  if (data === '[DONE]') {
347
  const modelName = models[selectedModel] || "AI";
348
- return fullResponse + "\n\n---\n*Response created by: **" + modelName + "***";
 
 
 
 
 
349
  }
350
 
 
 
351
  try {
352
  const parsed = JSON.parse(data);
353
- const delta = parsed.choices?.[0]?.delta?.content || '';
 
354
  if (delta) {
355
  fullResponse += delta;
356
- // Update the display in real-time
357
- updateStreamingResponse(fullResponse);
358
  }
359
  } catch (e) {
360
  // Skip invalid JSON
@@ -363,24 +368,40 @@
363
  }
364
  }
365
  }
366
- } finally {
367
- reader.releaseLock();
 
 
 
 
 
 
 
 
368
  }
369
-
370
- const modelName = models[selectedModel] || "AI";
371
- return fullResponse + "\n\n---\n*Response created by: **" + modelName + "***";
372
  }
373
 
374
- function updateStreamingResponse(partialResponse) {
375
- // Find the last message area and update it with streaming text
376
- const messagesArea = document.getElementById('messages-area');
377
- const messageElements = messagesArea.children;
378
-
379
- if (messageElements.length > 0) {
380
- const lastMessage = messageElements[messageElements.length - 1];
381
- const contentDiv = lastMessage.querySelector('.flex-1 .whitespace-pre-wrap');
382
- if (contentDiv) {
383
- contentDiv.textContent = partialResponse + " ▌"; // Add cursor
 
 
 
 
 
 
 
 
 
 
 
384
  }
385
  }
386
  }
@@ -424,15 +445,33 @@
424
  let content = message.content;
425
  let attribution = '';
426
 
427
- // Handle empty assistant messages (streaming in progress)
428
- if (message.role === 'assistant' && content === '' && isLoading) {
429
- content = '';
430
- } else if (message.role === 'assistant' && content.includes('---\n*Response created by:')) {
431
- const parts = content.split('\n\n---\n*Response created by:');
432
- content = parts[0];
433
- if (parts[1]) {
434
- const modelName = parts[1].replace(/\*\*/g, '').replace(/\*/g, '');
435
- attribution = '<div class="text-xs text-gray-500 mt-2 italic">Response created by: <strong>' + modelName + '</strong></div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  }
437
  }
438
 
@@ -448,18 +487,6 @@
448
  </div>
449
  `;
450
  });
451
-
452
- // Show loading dots if streaming
453
- if (isLoading && messages.length > 0 && messages[messages.length - 1].role === 'assistant' && messages[messages.length - 1].content === '') {
454
- // Replace the last empty message with loading dots
455
- html = html.replace(/<div class="whitespace-pre-wrap text-gray-100"><\/div>/, `
456
- <div class="flex items-center gap-2 text-gray-400">
457
- <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-1"></div>
458
- <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-2"></div>
459
- <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-3"></div>
460
- </div>
461
- `);
462
- }
463
 
464
  messagesArea.innerHTML = html;
465
  messagesArea.scrollTop = messagesArea.scrollHeight;
@@ -553,15 +580,14 @@
553
  URL.revokeObjectURL(url);
554
  }
555
 
556
- // Online users tracking with real geolocation
557
- let onlineUsers = new Map(); // Map to store user details
558
- let lastActivity = Date.now();
559
- let userLocation = { city: 'Unknown', country: 'Unknown' };
560
  let isGettingLocation = false;
561
 
562
- // Get user's real location
563
  async function getUserLocation() {
564
- if (isGettingLocation) return userLocation;
565
  isGettingLocation = true;
566
 
567
  try {
@@ -581,7 +607,7 @@
581
  const response = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${latitude}&longitude=${longitude}&localityLanguage=en`);
582
  const data = await response.json();
583
 
584
- userLocation = {
585
  city: data.city || data.locality || 'Unknown',
586
  country: data.countryName || 'Unknown'
587
  };
@@ -597,85 +623,42 @@
597
  await getLocationByIP();
598
  }
599
 
600
- return userLocation;
601
  }
602
 
603
- // Fallback: Get location by IP
604
  async function getLocationByIP() {
605
  try {
606
  const response = await fetch('https://ipapi.co/json/');
607
  const data = await response.json();
608
 
609
- userLocation = {
610
  city: data.city || 'Unknown',
611
  country: data.country_name || 'Unknown'
612
  };
613
  } catch (error) {
614
  console.log('IP location failed');
615
- userLocation = { city: 'Unknown', country: 'Unknown' };
616
  }
617
  }
618
 
619
- async function updateOnlineUsers() {
620
- // Ensure we have user location
621
- if (userLocation.city === 'Unknown') {
622
- await getUserLocation();
623
- }
624
 
625
- // Add current user with location
626
- const currentUserData = {
627
  id: userId,
628
- location: userLocation,
629
- lastSeen: Date.now(),
630
  isCurrentUser: true
631
- };
632
-
633
- onlineUsers.set(userId, currentUserData);
634
-
635
- // Simulate other realistic users based on activity
636
- const now = Date.now();
637
- const timeSinceLastActivity = now - lastActivity;
638
-
639
- // Add new users when there's recent activity
640
- if (timeSinceLastActivity < 10000 && Math.random() < 0.2) {
641
- const newUserId = 'User-' + Math.random().toString(36).substr(2, 8);
642
- const locations = [
643
- { city: 'New York', country: 'United States' },
644
- { city: 'London', country: 'United Kingdom' },
645
- { city: 'Tokyo', country: 'Japan' },
646
- { city: 'Paris', country: 'France' },
647
- { city: 'Sydney', country: 'Australia' },
648
- { city: 'Toronto', country: 'Canada' },
649
- { city: 'Berlin', country: 'Germany' },
650
- { city: 'Mumbai', country: 'India' },
651
- { city: 'São Paulo', country: 'Brazil' },
652
- { city: 'Singapore', country: 'Singapore' }
653
- ];
654
-
655
- const randomLocation = locations[Math.floor(Math.random() * locations.length)];
656
-
657
- onlineUsers.set(newUserId, {
658
- id: newUserId,
659
- location: randomLocation,
660
- lastSeen: now,
661
- isCurrentUser: false
662
- });
663
- }
664
-
665
- // Remove users who have been inactive for more than 2 minutes
666
- const cutoffTime = now - (2 * 60 * 1000);
667
- for (const [id, userData] of onlineUsers.entries()) {
668
- if (!userData.isCurrentUser && userData.lastSeen < cutoffTime) {
669
- onlineUsers.delete(id);
670
- }
671
- }
672
 
673
- // Occasionally remove a random user to simulate leaving
674
- if (onlineUsers.size > 3 && Math.random() < 0.1) {
675
- const nonCurrentUsers = Array.from(onlineUsers.entries()).filter(([id, data]) => !data.isCurrentUser);
676
- if (nonCurrentUsers.length > 0) {
677
- const randomUser = nonCurrentUsers[Math.floor(Math.random() * nonCurrentUsers.length)];
678
- onlineUsers.delete(randomUser[0]);
679
  }
680
  }
681
 
@@ -683,54 +666,59 @@
683
  }
684
 
685
  function updateUsersDisplay() {
686
- const count = onlineUsers.size;
687
  const status = count === 1 ? 'Just you online' : count + ' people online';
688
  document.getElementById('online-status').textContent = status;
689
 
690
- // Update users list
691
  const usersList = document.getElementById('users-list');
692
  const currentUserInfo = document.getElementById('current-user-info');
693
 
694
  // Update current user info
695
- currentUserInfo.textContent = `You: ${userId} (${userLocation.city}, ${userLocation.country})`;
696
 
697
- let html = `<div class="text-green-400">🟢 You: ${userId}<br><span class="text-gray-400 ml-4">${userLocation.city}, ${userLocation.country}</span></div>`;
698
 
699
- // Add other users
700
- const otherUsers = Array.from(onlineUsers.values()).filter(userData => !userData.isCurrentUser);
701
 
702
- otherUsers.forEach(userData => {
703
- const timeAgo = Math.floor((Date.now() - userData.lastSeen) / 1000);
704
- const timeText = timeAgo < 60 ? 'now' : `${Math.floor(timeAgo / 60)}m ago`;
705
-
706
- html += `
707
- <div class="text-blue-400">
708
- 🔵 ${userData.id}<br>
709
- <span class="text-gray-400 ml-4">${userData.location.city}, ${userData.location.country}</span><br>
710
- <span class="text-gray-500 ml-4 text-xs">Active ${timeText}</span>
711
- </div>
712
- `;
713
- });
 
 
 
 
714
 
715
  usersList.innerHTML = html;
716
  }
717
 
718
  function refreshUsers() {
 
719
  trackActivity();
720
- updateOnlineUsers();
721
  }
722
 
723
  function trackActivity() {
724
- lastActivity = Date.now();
725
 
726
- // Update current user's last seen time
727
- if (onlineUsers.has(userId)) {
728
- const userData = onlineUsers.get(userId);
729
- userData.lastSeen = Date.now();
730
- onlineUsers.set(userId, userData);
731
  }
732
 
733
- updateOnlineUsers();
 
734
  }
735
 
736
  async function checkAPIStatus() {
 
200
  document.addEventListener('DOMContentLoaded', async function() {
201
  setupEventListeners();
202
 
203
+ // Get user location first (one time only)
204
  await getUserLocation();
205
 
206
+ // Initialize database with current user only
207
  updateOnlineUsers();
208
  checkAPIStatus();
209
 
210
+ // Update users display every 30 seconds (like Streamlit file checking)
211
+ setInterval(updateOnlineUsers, 30000);
212
 
213
+ // Track user activity (but don't add fake users)
214
  document.addEventListener('click', trackActivity);
215
  document.addEventListener('keypress', trackActivity);
216
  document.addEventListener('scroll', trackActivity);
 
252
  messages.push(userMessage);
253
  input.value = '';
254
 
 
 
255
  // Add empty assistant message for streaming
256
  const assistantMessage = {
257
  role: 'assistant',
 
260
  };
261
  messages.push(assistantMessage);
262
 
263
+ updateMessagesDisplay();
264
  setLoading(true);
 
265
 
266
  try {
267
+ // The streaming will update the message in real-time
268
+ await getAIResponse(message);
 
 
 
 
 
269
 
270
  // Track activity after receiving response
271
  trackActivity();
272
  } catch (error) {
273
+ console.error('Error:', error);
274
  // Replace the empty message with error
275
  messages[messages.length - 1].content = 'Sorry, I encountered an error. Please try again.';
276
  updateMessagesDisplay();
 
292
  "X-Title": "Chat Flow AI Assistant"
293
  };
294
 
295
+ const cleanMessages = messages.slice(0, -1).map(msg => ({
296
  role: msg.role,
297
  content: msg.content.split('\n\n---\n*Response created by:')[0]
298
  }));
 
306
  const data = {
307
  model: selectedModel,
308
  messages: apiMessages,
309
+ stream: true,
310
  max_tokens: 2000,
311
  temperature: 0.7
312
  };
313
 
314
+ try {
315
+ const response = await fetch(url, {
316
+ method: 'POST',
317
+ headers: headers,
318
+ body: JSON.stringify(data)
319
+ });
320
 
321
+ if (!response.ok) {
322
+ const errorText = await response.text();
323
+ throw new Error("API Error: " + response.status + " - " + errorText);
324
+ }
325
 
326
+ const reader = response.body.getReader();
327
+ const decoder = new TextDecoder();
328
+ let fullResponse = "";
329
+
 
330
  while (true) {
331
  const { done, value } = await reader.read();
332
  if (done) break;
333
 
334
+ const chunk = decoder.decode(value, { stream: true });
335
  const lines = chunk.split('\n');
336
 
337
  for (const line of lines) {
338
+ if (line.trim() === '') continue;
339
+
340
  if (line.startsWith('data: ')) {
341
+ const data = line.slice(6).trim();
342
+
343
  if (data === '[DONE]') {
344
  const modelName = models[selectedModel] || "AI";
345
+ const finalResponse = fullResponse + "\n\n---\n*Response created by: **" + modelName + "***";
346
+
347
+ // Update final message
348
+ messages[messages.length - 1].content = finalResponse;
349
+ updateMessagesDisplay();
350
+ return finalResponse;
351
  }
352
 
353
+ if (data === '') continue;
354
+
355
  try {
356
  const parsed = JSON.parse(data);
357
+ const delta = parsed.choices?.[0]?.delta?.content;
358
+
359
  if (delta) {
360
  fullResponse += delta;
361
+ // Update streaming message immediately
362
+ updateStreamingMessage(fullResponse);
363
  }
364
  } catch (e) {
365
  // Skip invalid JSON
 
368
  }
369
  }
370
  }
371
+
372
+ const modelName = models[selectedModel] || "AI";
373
+ const finalResponse = fullResponse + "\n\n---\n*Response created by: **" + modelName + "***";
374
+ messages[messages.length - 1].content = finalResponse;
375
+ updateMessagesDisplay();
376
+ return finalResponse;
377
+
378
+ } catch (error) {
379
+ console.error('Streaming error:', error);
380
+ throw error;
381
  }
 
 
 
382
  }
383
 
384
+ function updateStreamingMessage(partialResponse) {
385
+ // Update the last message in real-time
386
+ if (messages.length > 0) {
387
+ const lastMessage = messages[messages.length - 1];
388
+ if (lastMessage.role === 'assistant') {
389
+ lastMessage.content = partialResponse;
390
+
391
+ // Update display with streaming cursor
392
+ const messagesArea = document.getElementById('messages-area');
393
+ const messageElements = messagesArea.children;
394
+
395
+ if (messageElements.length > 0) {
396
+ const lastMessageElement = messageElements[messageElements.length - 1];
397
+ const contentDiv = lastMessageElement.querySelector('.whitespace-pre-wrap');
398
+ if (contentDiv) {
399
+ contentDiv.textContent = partialResponse + " ▌";
400
+ }
401
+ }
402
+
403
+ // Auto scroll
404
+ messagesArea.scrollTop = messagesArea.scrollHeight;
405
  }
406
  }
407
  }
 
445
  let content = message.content;
446
  let attribution = '';
447
 
448
+ // Handle streaming messages
449
+ if (message.role === 'assistant') {
450
+ if (content === '' && isLoading) {
451
+ // Show loading dots for empty assistant message
452
+ content = '';
453
+ html += `
454
+ <div class="flex gap-4">
455
+ <div class="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
456
+ ${avatar}
457
+ </div>
458
+ <div class="flex-1">
459
+ <div class="flex items-center gap-2 text-gray-400">
460
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-1"></div>
461
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-2"></div>
462
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-3"></div>
463
+ </div>
464
+ </div>
465
+ </div>
466
+ `;
467
+ return;
468
+ } else if (content.includes('---\n*Response created by:')) {
469
+ const parts = content.split('\n\n---\n*Response created by:');
470
+ content = parts[0];
471
+ if (parts[1]) {
472
+ const modelName = parts[1].replace(/\*\*/g, '').replace(/\*/g, '');
473
+ attribution = '<div class="text-xs text-gray-500 mt-2 italic">Response created by: <strong>' + modelName + '</strong></div>';
474
+ }
475
  }
476
  }
477
 
 
487
  </div>
488
  `;
489
  });
 
 
 
 
 
 
 
 
 
 
 
 
490
 
491
  messagesArea.innerHTML = html;
492
  messagesArea.scrollTop = messagesArea.scrollHeight;
 
580
  URL.revokeObjectURL(url);
581
  }
582
 
583
+ // Real database-style user tracking (like Streamlit version)
584
+ let userDatabase = new Map(); // Simulates your database file
585
+ let currentUserLocation = { city: 'Unknown', country: 'Unknown' };
 
586
  let isGettingLocation = false;
587
 
588
+ // Get user's real location (one time only)
589
  async function getUserLocation() {
590
+ if (isGettingLocation || currentUserLocation.city !== 'Unknown') return currentUserLocation;
591
  isGettingLocation = true;
592
 
593
  try {
 
607
  const response = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${latitude}&longitude=${longitude}&localityLanguage=en`);
608
  const data = await response.json();
609
 
610
+ currentUserLocation = {
611
  city: data.city || data.locality || 'Unknown',
612
  country: data.countryName || 'Unknown'
613
  };
 
623
  await getLocationByIP();
624
  }
625
 
626
+ return currentUserLocation;
627
  }
628
 
629
+ // Fallback: Get location by IP (one time only)
630
  async function getLocationByIP() {
631
  try {
632
  const response = await fetch('https://ipapi.co/json/');
633
  const data = await response.json();
634
 
635
+ currentUserLocation = {
636
  city: data.city || 'Unknown',
637
  country: data.country_name || 'Unknown'
638
  };
639
  } catch (error) {
640
  console.log('IP location failed');
641
+ currentUserLocation = { city: 'Unknown', country: 'Unknown' };
642
  }
643
  }
644
 
645
+ // Database-style user management (exactly like your Streamlit)
646
+ function updateOnlineUsers() {
647
+ const now = Date.now();
 
 
648
 
649
+ // Add/update current user in database
650
+ userDatabase.set(userId, {
651
  id: userId,
652
+ location: currentUserLocation,
653
+ lastSeen: now,
654
  isCurrentUser: true
655
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
 
657
+ // Clean up users not seen in last 5 minutes (like your Streamlit HISTORY_FILE logic)
658
+ const fiveMinutesAgo = now - (5 * 60 * 1000);
659
+ for (const [id, userData] of userDatabase.entries()) {
660
+ if (!userData.isCurrentUser && userData.lastSeen < fiveMinutesAgo) {
661
+ userDatabase.delete(id);
 
662
  }
663
  }
664
 
 
666
  }
667
 
668
  function updateUsersDisplay() {
669
+ const count = userDatabase.size;
670
  const status = count === 1 ? 'Just you online' : count + ' people online';
671
  document.getElementById('online-status').textContent = status;
672
 
673
+ // Update users list - show ONLY real database users
674
  const usersList = document.getElementById('users-list');
675
  const currentUserInfo = document.getElementById('current-user-info');
676
 
677
  // Update current user info
678
+ currentUserInfo.textContent = `You: ${userId} (${currentUserLocation.city}, ${currentUserLocation.country})`;
679
 
680
+ let html = `<div class="text-green-400">🟢 You: ${userId}<br><span class="text-gray-400 ml-4">${currentUserLocation.city}, ${currentUserLocation.country}</span></div>`;
681
 
682
+ // Add ONLY other users from database (no fake users)
683
+ const otherUsers = Array.from(userDatabase.values()).filter(userData => !userData.isCurrentUser);
684
 
685
+ if (otherUsers.length === 0) {
686
+ html += '<div class="text-gray-500 text-xs mt-2">No other users currently online</div>';
687
+ } else {
688
+ otherUsers.forEach(userData => {
689
+ const timeAgo = Math.floor((Date.now() - userData.lastSeen) / 1000);
690
+ const timeText = timeAgo < 60 ? 'now' : `${Math.floor(timeAgo / 60)}m ago`;
691
+
692
+ html += `
693
+ <div class="text-blue-400 mt-2">
694
+ 🔵 ${userData.id}<br>
695
+ <span class="text-gray-400 ml-4">${userData.location.city}, ${userData.location.country}</span><br>
696
+ <span class="text-gray-500 ml-4 text-xs">Active ${timeText}</span>
697
+ </div>
698
+ `;
699
+ });
700
+ }
701
 
702
  usersList.innerHTML = html;
703
  }
704
 
705
  function refreshUsers() {
706
+ // Just refresh display, don't add fake users
707
  trackActivity();
 
708
  }
709
 
710
  function trackActivity() {
711
+ const now = Date.now();
712
 
713
+ // Update current user's last seen time in database
714
+ if (userDatabase.has(userId)) {
715
+ const userData = userDatabase.get(userId);
716
+ userData.lastSeen = now;
717
+ userDatabase.set(userId, userData);
718
  }
719
 
720
+ // Only update display, no fake user generation
721
+ updateUsersDisplay();
722
  }
723
 
724
  async function checkAPIStatus() {