awacke1 commited on
Commit
ca4200f
·
verified ·
1 Parent(s): 962f379

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -1
app.py CHANGED
@@ -204,4 +204,249 @@ async def websocket_handler(websocket, path):
204
  chat_history[room_id].append(data)
205
  if len(chat_history[room_id]) > 500:
206
  chat_history[room_id] = chat_history[room_id][-500:]
207
- save_message_to_history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  chat_history[room_id].append(data)
205
  if len(chat_history[room_id]) > 500:
206
  chat_history[room_id] = chat_history[room_id][-500:]
207
+ save_message_to_history(room_id, data)
208
+ await broadcast_message(data, room_id)
209
+ except json.JSONDecodeError:
210
+ await websocket.send(json.dumps({"type": "error", "content": "Invalid JSON", "timestamp": datetime.now().isoformat(), "sender": "system", "room_id": room_id}))
211
+ except websockets.ConnectionClosed:
212
+ logger.info(f"Client {client_id} disconnected from room {room_id}")
213
+ finally:
214
+ if room_id in active_connections and client_id in active_connections[room_id]:
215
+ del active_connections[room_id][client_id]
216
+ sector_users[room_id].discard(client_id)
217
+ x, y = get_sector_coordinates(room_id)
218
+ leave_msg = {"type": "system", "content": f"User left (Sector {x},{y}) - {len(sector_users[room_id])} users", "timestamp": datetime.now().isoformat(), "sender": "system", "room_id": room_id}
219
+ await broadcast_message(leave_msg, room_id)
220
+ save_message_to_history(room_id, leave_msg)
221
+ if not active_connections[room_id]:
222
+ del active_connections[room_id]
223
+
224
+ async def broadcast_message(message, room_id):
225
+ if room_id in active_connections:
226
+ disconnected = []
227
+ for client_id, ws in active_connections[room_id].items():
228
+ try:
229
+ await ws.send(json.dumps(message))
230
+ except websockets.ConnectionClosed:
231
+ disconnected.append(client_id)
232
+ for client_id in disconnected:
233
+ del active_connections[room_id][client_id]
234
+
235
+ async def start_websocket_server(host='0.0.0.0', port=8765):
236
+ server = await websockets.serve(websocket_handler, host, port)
237
+ logger.info(f"WebSocket server started on ws://{host}:{port}")
238
+ return server
239
+
240
+ main_event_loop = None
241
+ message_queue = []
242
+
243
+ def send_message(message, username, room_id):
244
+ if not message.strip():
245
+ return None
246
+ msg_data = {"type": "chat", "content": message, "username": username, "room_id": room_id}
247
+ message_queue.append(msg_data)
248
+ return f"{username}: {message}"
249
+
250
+ def join_room(room_id, chat_history_output):
251
+ if not room_id.strip():
252
+ return "Please enter a valid room ID", chat_history_output
253
+ room_id = urllib.parse.quote(room_id.strip())
254
+ history = load_room_history(room_id)
255
+ formatted_history = [
256
+ f"[{datetime.fromisoformat(msg['timestamp']).strftime('%H:%M:%S')}] {msg.get('username', 'Anonymous')} [{'sender_node' in msg and msg['sender_node'] or 'unknown'}]: {msg['content']}"
257
+ if msg.get("type") == "chat" else f"System: {msg['content']}"
258
+ for msg in history
259
+ ]
260
+ return f"Joined room: {room_id}", "\n".join(formatted_history)
261
+
262
+ def list_available_rooms():
263
+ history_files = list_all_history_files()
264
+ return "No chat rooms available yet. Join one to create it!" if not history_files else "### Available Chat Rooms\n\n" + "\n".join(
265
+ f"- **{room_id}**: Last activity {datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d %H:%M:%S')}" for room_id, _, mod_time in history_files
266
+ )
267
+
268
+ def create_gradio_interface():
269
+ with gr.Blocks(title=f"Chat Node: {NODE_NAME}") as interface:
270
+ # Custom CSS and JS
271
+ interface.css = """
272
+ #log-overlay {
273
+ position: fixed; top: 10px; right: 10px; width: 300px; height: 200px;
274
+ background: rgba(0, 0, 0, 0.8); color: white; padding: 10px; overflow-y: auto;
275
+ border-radius: 5px; z-index: 1000; display: none;
276
+ }
277
+ #threejs-container { width: 100%; height: 400px; }
278
+ .chat-container { margin-top: 20px; }
279
+ """
280
+ interface.js = """
281
+ async () => {
282
+ // Log Overlay
283
+ let logOverlay = document.createElement('div');
284
+ logOverlay.id = 'log-overlay';
285
+ document.body.appendChild(logOverlay);
286
+ let wsLogs = new WebSocket('ws://' + window.location.hostname + ':8765/logs');
287
+ wsLogs.onmessage = (event) => {
288
+ let data = JSON.parse(event.data);
289
+ if (data.type === 'log') {
290
+ logOverlay.style.display = 'block';
291
+ logOverlay.innerHTML += `<p>${data.timestamp} - ${data.content}</p>`;
292
+ logOverlay.scrollTop = logOverlay.scrollHeight;
293
+ }
294
+ };
295
+ wsLogs.onerror = () => console.error('Log WebSocket error');
296
+ wsLogs.onclose = () => console.log('Log WebSocket closed');
297
+
298
+ // Three.js Sector Map
299
+ let container = document.getElementById('threejs-container');
300
+ if (!container) return;
301
+ const THREE = await import('https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js');
302
+ const scene = new THREE.Scene();
303
+ const camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
304
+ const renderer = new THREE.WebGLRenderer();
305
+ renderer.setSize(container.clientWidth, container.clientHeight);
306
+ container.appendChild(renderer.domElement);
307
+
308
+ const gridSize = 10;
309
+ const geometry = new THREE.BoxGeometry(0.8, 0.8, 0.8);
310
+ const edges = new THREE.EdgesGeometry(geometry);
311
+ for (let x = 0; x < gridSize; x++) {
312
+ for (let y = 0; y < gridSize; y++) {
313
+ const hasUsers = document.querySelector(`[data-room="${x},${y}"]`)?.dataset.users > 0;
314
+ const material = new THREE.MeshBasicMaterial({ color: hasUsers ? 0x00ff00 : 0x333333 });
315
+ const cube = new THREE.Mesh(geometry, material);
316
+ cube.position.set(x - gridSize / 2, 0, y - gridSize / 2);
317
+ scene.add(cube);
318
+ if (hasUsers) {
319
+ const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xffffff }));
320
+ line.position.set(x - gridSize / 2, 0, y - gridSize / 2);
321
+ scene.add(line);
322
+ }
323
+ }
324
+ }
325
+ camera.position.set(0, 10, 10);
326
+ camera.lookAt(0, 0, 0);
327
+
328
+ function animate() {
329
+ requestAnimationFrame(animate);
330
+ renderer.render(scene, camera);
331
+ }
332
+ animate();
333
+ }
334
+ """
335
+
336
+ gr.Markdown(f"# Chat Node: {NODE_NAME}")
337
+ gr.Markdown("Welcome! You've auto-joined a room with a fun username.")
338
+
339
+ with gr.Row():
340
+ with gr.Column(scale=3):
341
+ room_list = gr.Markdown(value=list_available_rooms())
342
+ refresh_button = gr.Button("🔄 Refresh Room List")
343
+ with gr.Column(scale=1):
344
+ clear_button = gr.Button("🧹 Clear All Chat History", variant="stop")
345
+
346
+ gr.HTML('<div id="threejs-container"></div>') # Three.js container
347
+
348
+ with gr.Row():
349
+ room_id_input = gr.Textbox(label="Room ID", placeholder="Change room (e.g., '0,0')")
350
+ join_button = gr.Button("Join Room")
351
+
352
+ chat_history_output = gr.Textbox(label="Chat History", lines=20, max_lines=20, elem_classes=["chat-container"])
353
+
354
+ with gr.Row():
355
+ username_input = gr.Textbox(label="Username", value=random.choice(FUN_USERNAMES))
356
+ with gr.Column(scale=3):
357
+ message_input = gr.Textbox(label="Message", placeholder="Type here...", lines=3)
358
+ with gr.Column(scale=1):
359
+ send_button = gr.Button("Send")
360
+ map_button = gr.Button("🗺️ Show Map")
361
+
362
+ current_room_display = gr.Textbox(label="Current Room", value="Auto-joining...")
363
+
364
+ # Event handlers
365
+ refresh_button.click(list_available_rooms, [], [room_list])
366
+ clear_button.click(lambda: (message_queue.append({"type": "command", "command": "clear_history", "username": "System"}), "🧹 Clearing..."), [], [room_list])
367
+ join_button.click(join_room, [room_id_input, chat_history_output], [current_room_display, chat_history_output])
368
+
369
+ def send_and_clear(message, username, room_id):
370
+ if not room_id.startswith("Joined room:"):
371
+ return "", "Please join a room first"
372
+ actual_room_id = room_id.replace("Joined room: ", "").strip()
373
+ formatted_msg = send_message(message, username, actual_room_id)
374
+ return "", formatted_msg or ""
375
+
376
+ send_button.click(send_and_clear, [message_input, username_input, current_room_display], [message_input, chat_history_output])
377
+ message_input.submit(send_and_clear, [message_input, username_input, current_room_display], [message_input, chat_history_output])
378
+ map_button.click(lambda room_id: generate_sector_map() if room_id.startswith("Joined room:") else "Join a room first", [current_room_display], [chat_history_output])
379
+
380
+ # Auto-join on load
381
+ def auto_join():
382
+ random_room = f"{random.randint(0, GRID_WIDTH-1)},{random.randint(0, GRID_HEIGHT-1)}"
383
+ return join_room(random_room, "")
384
+ interface.load(auto_join, [], [current_room_display, chat_history_output])
385
+
386
+ return interface
387
+
388
+ async def process_message_queue():
389
+ while True:
390
+ if message_queue:
391
+ msg_data = message_queue.pop(0)
392
+ await broadcast_message(msg_data, msg_data["room_id"])
393
+ await asyncio.sleep(0.1)
394
+
395
+ async def process_logs():
396
+ while True:
397
+ global log_history
398
+ updated = False
399
+ for file in os.listdir(LOG_DIR):
400
+ if file.endswith(".jsonl"):
401
+ file_path = os.path.join(LOG_DIR, file)
402
+ current_mtime = os.path.getmtime(file_path)
403
+ if file_path not in file_modification_times or current_mtime > file_modification_times[file_path]:
404
+ updated = True
405
+ file_modification_times[file_path] = current_mtime
406
+ if updated:
407
+ log_history = []
408
+ for file in sorted([f for f in os.listdir(LOG_DIR) if f.endswith(".jsonl")], key=lambda x: os.path.getmtime(os.path.join(LOG_DIR, x))):
409
+ try:
410
+ with open(os.path.join(LOG_DIR, file), 'r') as f:
411
+ for line in f:
412
+ if line.strip():
413
+ log_history.append(json.loads(line))
414
+ except Exception as e:
415
+ logger.error(f"Error loading logs from {file}: {e}")
416
+ log_msg = {"type": "log", "content": "\n".join(entry["message"] for entry in log_history[-10:]), "timestamp": datetime.now().isoformat(), "sender": "system", "room_id": "logs"}
417
+ await broadcast_message(log_msg, "logs")
418
+ await asyncio.sleep(1)
419
+
420
+ class LogBroadcastHandler(logging.Handler):
421
+ def emit(self, record):
422
+ entry = {"timestamp": datetime.now().isoformat(), "level": record.levelname, "message": self.format(record), "name": record.name}
423
+ save_log_entry(entry)
424
+ log_history.append(entry)
425
+
426
+ logger.addHandler(LogBroadcastHandler())
427
+
428
+ async def main():
429
+ global NODE_NAME, main_event_loop
430
+ NODE_NAME, port = get_node_name()
431
+ main_event_loop = asyncio.get_running_loop()
432
+ await start_websocket_server()
433
+ asyncio.create_task(process_message_queue())
434
+ asyncio.create_task(process_logs())
435
+ interface = create_gradio_interface()
436
+
437
+ from starlette.middleware.base import BaseHTTPMiddleware
438
+ class NodeNameMiddleware(BaseHTTPMiddleware):
439
+ async def dispatch(self, request, call_next):
440
+ global NODE_NAME
441
+ if "node_name" in dict(request.query_params):
442
+ NODE_NAME = dict(request.query_params)["node_name"]
443
+ logger.info(f"Node name set to {NODE_NAME} from URL")
444
+ return await call_next(request)
445
+
446
+ app = gr.routes.App.create_app(interface)
447
+ app.add_middleware(NodeNameMiddleware)
448
+ import uvicorn
449
+ await uvicorn.Server(uvicorn.Config(app, host="0.0.0.0", port=port)).serve()
450
+
451
+ if __name__ == "__main__":
452
+ asyncio.run(main())