Spaces:
Running
Running
Update app.py
Browse files
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 |
-
//
|
207 |
updateOnlineUsers();
|
208 |
checkAPIStatus();
|
209 |
|
210 |
-
//
|
211 |
-
setInterval(updateOnlineUsers,
|
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 |
-
|
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,
|
316 |
max_tokens: 2000,
|
317 |
temperature: 0.7
|
318 |
};
|
319 |
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
|
|
325 |
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
|
331 |
-
|
332 |
-
|
333 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
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
|
357 |
-
|
358 |
}
|
359 |
} catch (e) {
|
360 |
// Skip invalid JSON
|
@@ -363,24 +368,40 @@
|
|
363 |
}
|
364 |
}
|
365 |
}
|
366 |
-
|
367 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
368 |
}
|
369 |
-
|
370 |
-
const modelName = models[selectedModel] || "AI";
|
371 |
-
return fullResponse + "\n\n---\n*Response created by: **" + modelName + "***";
|
372 |
}
|
373 |
|
374 |
-
function
|
375 |
-
//
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
384 |
}
|
385 |
}
|
386 |
}
|
@@ -424,15 +445,33 @@
|
|
424 |
let content = message.content;
|
425 |
let attribution = '';
|
426 |
|
427 |
-
// Handle
|
428 |
-
if (message.role === 'assistant'
|
429 |
-
content
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
//
|
557 |
-
let
|
558 |
-
let
|
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
|
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 |
-
|
585 |
city: data.city || data.locality || 'Unknown',
|
586 |
country: data.countryName || 'Unknown'
|
587 |
};
|
@@ -597,85 +623,42 @@
|
|
597 |
await getLocationByIP();
|
598 |
}
|
599 |
|
600 |
-
return
|
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 |
-
|
610 |
city: data.city || 'Unknown',
|
611 |
country: data.country_name || 'Unknown'
|
612 |
};
|
613 |
} catch (error) {
|
614 |
console.log('IP location failed');
|
615 |
-
|
616 |
}
|
617 |
}
|
618 |
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
await getUserLocation();
|
623 |
-
}
|
624 |
|
625 |
-
// Add current user
|
626 |
-
|
627 |
id: userId,
|
628 |
-
location:
|
629 |
-
lastSeen:
|
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 |
-
//
|
674 |
-
|
675 |
-
|
676 |
-
if (
|
677 |
-
|
678 |
-
onlineUsers.delete(randomUser[0]);
|
679 |
}
|
680 |
}
|
681 |
|
@@ -683,54 +666,59 @@
|
|
683 |
}
|
684 |
|
685 |
function updateUsersDisplay() {
|
686 |
-
const count =
|
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} (${
|
696 |
|
697 |
-
let html = `<div class="text-green-400">🟢 You: ${userId}<br><span class="text-gray-400 ml-4">${
|
698 |
|
699 |
-
// Add other users
|
700 |
-
const otherUsers = Array.from(
|
701 |
|
702 |
-
otherUsers.
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
<
|
708 |
-
|
709 |
-
|
710 |
-
<
|
711 |
-
|
712 |
-
|
713 |
-
|
|
|
|
|
|
|
|
|
714 |
|
715 |
usersList.innerHTML = html;
|
716 |
}
|
717 |
|
718 |
function refreshUsers() {
|
|
|
719 |
trackActivity();
|
720 |
-
updateOnlineUsers();
|
721 |
}
|
722 |
|
723 |
function trackActivity() {
|
724 |
-
|
725 |
|
726 |
-
// Update current user's last seen time
|
727 |
-
if (
|
728 |
-
const userData =
|
729 |
-
userData.lastSeen =
|
730 |
-
|
731 |
}
|
732 |
|
733 |
-
|
|
|
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() {
|