awacke1 commited on
Commit
a58b36c
·
verified ·
1 Parent(s): d597cda

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +266 -45
app.py CHANGED
@@ -22,11 +22,22 @@ active_connections = {}
22
  # Dictionary to store message history for each chat room (in-memory cache)
23
  chat_history = {}
24
 
 
 
 
 
 
 
 
 
 
 
25
  # Directory to store persistent chat history
26
  HISTORY_DIR = "chat_history"
27
  import os
28
  import shutil
29
  from pathlib import Path
 
30
 
31
  # Create history directory if it doesn't exist
32
  os.makedirs(HISTORY_DIR, exist_ok=True)
@@ -55,52 +66,169 @@ def get_node_name():
55
 
56
  def get_room_history_file(room_id):
57
  """Get the filename for a room's history."""
58
- return os.path.join(HISTORY_DIR, f"{room_id}.md")
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  def load_room_history(room_id):
61
- """Load chat history for a room from persistent storage."""
62
  if room_id not in chat_history:
63
  chat_history[room_id] = []
64
 
65
- # Try to load from file
66
- history_file = get_room_history_file(room_id)
67
- if os.path.exists(history_file):
 
 
 
 
 
 
 
 
68
  try:
69
  with open(history_file, 'r') as f:
70
- history_json = f.read()
71
- if history_json.strip():
72
- loaded_history = json.loads(history_json)
73
- chat_history[room_id] = loaded_history
74
- logger.info(f"Loaded {len(loaded_history)} messages from history for room {room_id}")
 
 
 
75
  except Exception as e:
76
- logger.error(f"Error loading history for room {room_id}: {e}")
 
 
 
 
 
 
 
 
 
 
77
 
78
  return chat_history[room_id]
79
 
80
- def save_room_history(room_id):
81
- """Save chat history for a room to persistent storage."""
82
- if room_id in chat_history and chat_history[room_id]:
 
 
 
 
83
  history_file = get_room_history_file(room_id)
84
- try:
85
- with open(history_file, 'w') as f:
86
- json.dump(chat_history[room_id], f)
87
- logger.info(f"Saved {len(chat_history[room_id])} messages to history for room {room_id}")
88
- except Exception as e:
89
- logger.error(f"Error saving history for room {room_id}: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- def get_all_history_files():
92
- """Get a list of all chat history files, sorted by modification time (newest first)."""
93
- history_files = []
 
 
94
  for file in os.listdir(HISTORY_DIR):
95
- if file.endswith(".md") and file != "README.md":
96
  file_path = os.path.join(HISTORY_DIR, file)
97
- mod_time = os.path.getmtime(file_path)
98
- room_id = file[:-3] # Remove .md extension
99
- history_files.append((room_id, file_path, mod_time))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- # Sort by modification time (newest first)
102
- history_files.sort(key=lambda x: x[2], reverse=True)
103
- return history_files
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
  async def clear_all_history():
106
  """Clear all chat history for all rooms."""
@@ -143,19 +271,37 @@ async def websocket_handler(websocket, path):
143
 
144
  active_connections[room_id][client_id] = websocket
145
 
 
 
 
 
 
 
 
 
146
  # Load or initialize chat history
147
  room_history = load_room_history(room_id)
148
 
149
  # Send welcome message
150
  welcome_msg = {
151
  "type": "system",
152
- "content": f"Welcome to room '{room_id}'! Connected from node '{NODE_NAME}'",
153
  "timestamp": datetime.now().isoformat(),
154
  "sender": "system",
155
  "room_id": room_id
156
  }
157
  await websocket.send(json.dumps(welcome_msg))
158
 
 
 
 
 
 
 
 
 
 
 
159
  # Send chat history
160
  for msg in room_history:
161
  await websocket.send(json.dumps(msg))
@@ -163,14 +309,15 @@ async def websocket_handler(websocket, path):
163
  # Broadcast join notification
164
  join_msg = {
165
  "type": "system",
166
- "content": f"User joined the room",
167
  "timestamp": datetime.now().isoformat(),
168
  "sender": "system",
169
  "room_id": room_id
170
  }
171
  await broadcast_message(join_msg, room_id)
 
172
 
173
- logger.info(f"New client {client_id} connected to room {room_id}")
174
 
175
  # Handle messages from this client
176
  async for message in websocket:
@@ -182,6 +329,18 @@ async def websocket_handler(websocket, path):
182
  result = await clear_all_history()
183
  continue
184
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  # Add metadata to the message
186
  data["timestamp"] = datetime.now().isoformat()
187
  data["sender_node"] = NODE_NAME
@@ -193,7 +352,7 @@ async def websocket_handler(websocket, path):
193
  chat_history[room_id] = chat_history[room_id][-500:]
194
 
195
  # Save to persistent storage
196
- save_room_history(room_id)
197
 
198
  # Broadcast to all clients in the room
199
  await broadcast_message(data, room_id)
@@ -215,15 +374,23 @@ async def websocket_handler(websocket, path):
215
  if room_id in active_connections and client_id in active_connections[room_id]:
216
  del active_connections[room_id][client_id]
217
 
 
 
 
 
 
 
 
218
  # Broadcast leave notification
219
  leave_msg = {
220
  "type": "system",
221
- "content": f"User left the room",
222
  "timestamp": datetime.now().isoformat(),
223
  "sender": "system",
224
  "room_id": room_id
225
  }
226
  await broadcast_message(leave_msg, room_id)
 
227
 
228
  # Clean up empty rooms (but keep history)
229
  if not active_connections[room_id]:
@@ -347,19 +514,32 @@ def create_gradio_interface():
347
  with gr.Column(scale=1):
348
  clear_button = gr.Button("🧹 Clear All Chat History", variant="stop")
349
 
350
- # Join room controls
351
  with gr.Row():
352
- room_id_input = gr.Textbox(label="Room ID", placeholder="Enter room ID")
353
- join_button = gr.Button("Join Room")
 
 
 
 
 
 
354
 
355
- # Chat area
356
  chat_history_output = gr.Textbox(label="Chat History", lines=20, max_lines=20)
357
 
358
- # Message controls
359
  with gr.Row():
360
  username_input = gr.Textbox(label="Username", placeholder="Enter your username", value="User")
361
- message_input = gr.Textbox(label="Message", placeholder="Type your message here")
362
- send_button = gr.Button("Send")
 
 
 
 
 
 
 
363
 
364
  # Current room display
365
  current_room_display = gr.Textbox(label="Current Room", value="Not joined any room yet")
@@ -377,6 +557,22 @@ def create_gradio_interface():
377
  outputs=[room_list]
378
  )
379
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  join_button.click(
381
  join_room,
382
  inputs=[room_id_input, chat_history_output],
@@ -388,7 +584,16 @@ def create_gradio_interface():
388
  return "Please join a room first", message
389
 
390
  actual_room_id = room_id.replace("Joined room: ", "").strip()
391
- formatted_msg = send_message(message, username, actual_room_id)
 
 
 
 
 
 
 
 
 
392
 
393
  if formatted_msg:
394
  return "", formatted_msg
@@ -400,9 +605,25 @@ def create_gradio_interface():
400
  outputs=[message_input, chat_history_output]
401
  )
402
 
403
- # Enter key to send message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  message_input.submit(
405
- send_and_clear,
406
  inputs=[message_input, username_input, current_room_display],
407
  outputs=[message_input, chat_history_output]
408
  )
 
22
  # Dictionary to store message history for each chat room (in-memory cache)
23
  chat_history = {}
24
 
25
+ # Dictionary to track file modification times
26
+ file_modification_times = {}
27
+
28
+ # Dictionary to track users in each room/sector
29
+ sector_users = {}
30
+
31
+ # Grid dimensions for 2D sector map
32
+ GRID_WIDTH = 10
33
+ GRID_HEIGHT = 10
34
+
35
  # Directory to store persistent chat history
36
  HISTORY_DIR = "chat_history"
37
  import os
38
  import shutil
39
  from pathlib import Path
40
+ import time
41
 
42
  # Create history directory if it doesn't exist
43
  os.makedirs(HISTORY_DIR, exist_ok=True)
 
66
 
67
  def get_room_history_file(room_id):
68
  """Get the filename for a room's history."""
69
+ # Create timestamp-based log files
70
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
71
+ return os.path.join(HISTORY_DIR, f"{room_id}_{timestamp}.jsonl")
72
+
73
+ def get_all_room_history_files(room_id):
74
+ """Get all history files for a specific room."""
75
+ files = []
76
+ for file in os.listdir(HISTORY_DIR):
77
+ if file.startswith(f"{room_id}_") and file.endswith(".jsonl"):
78
+ files.append(os.path.join(HISTORY_DIR, file))
79
+ # Sort by modification time (newest first)
80
+ files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
81
+ return files
82
 
83
  def load_room_history(room_id):
84
+ """Load chat history for a room from all persistent storage files."""
85
  if room_id not in chat_history:
86
  chat_history[room_id] = []
87
 
88
+ # Get all history files for this room
89
+ history_files = get_all_room_history_files(room_id)
90
+
91
+ # Track file modification times
92
+ for file in history_files:
93
+ if file not in file_modification_times:
94
+ file_modification_times[file] = os.path.getmtime(file)
95
+
96
+ # Load messages from all files
97
+ messages = []
98
+ for history_file in history_files:
99
  try:
100
  with open(history_file, 'r') as f:
101
+ for line in f:
102
+ line = line.strip()
103
+ if line: # Skip empty lines
104
+ try:
105
+ data = json.loads(line)
106
+ messages.append(data)
107
+ except json.JSONDecodeError:
108
+ logger.error(f"Error parsing JSON line in {history_file}")
109
  except Exception as e:
110
+ logger.error(f"Error loading history from {history_file}: {e}")
111
+
112
+ # Sort by timestamp
113
+ messages.sort(key=lambda x: x.get("timestamp", ""), reverse=False)
114
+ chat_history[room_id] = messages
115
+
116
+ logger.info(f"Loaded {len(messages)} messages from {len(history_files)} files for room {room_id}")
117
+
118
+ # Track users in this sector
119
+ if room_id not in sector_users:
120
+ sector_users[room_id] = set()
121
 
122
  return chat_history[room_id]
123
 
124
+ def save_message_to_history(room_id, message):
125
+ """Save a single message to the newest history file for a room."""
126
+ # Get the newest history file or create a new one
127
+ history_files = get_all_room_history_files(room_id)
128
+
129
+ if not history_files:
130
+ # Create a new file
131
  history_file = get_room_history_file(room_id)
132
+ else:
133
+ # Use the newest file if it's less than 1 MB, otherwise create a new one
134
+ newest_file = history_files[0]
135
+ if os.path.getsize(newest_file) > 1024 * 1024: # 1 MB
136
+ history_file = get_room_history_file(room_id)
137
+ else:
138
+ history_file = newest_file
139
+
140
+ try:
141
+ # Append the message as a single line of JSON
142
+ with open(history_file, 'a') as f:
143
+ f.write(json.dumps(message) + '\n')
144
+
145
+ # Update modification time
146
+ file_modification_times[history_file] = os.path.getmtime(history_file)
147
+
148
+ logger.debug(f"Saved message to {history_file}")
149
+ except Exception as e:
150
+ logger.error(f"Error saving message to {history_file}: {e}")
151
 
152
+ def check_for_new_messages():
153
+ """Check for new messages in all history files."""
154
+ updated_rooms = set()
155
+
156
+ # Check all files in the history directory
157
  for file in os.listdir(HISTORY_DIR):
158
+ if file.endswith(".jsonl"):
159
  file_path = os.path.join(HISTORY_DIR, file)
160
+ current_mtime = os.path.getmtime(file_path)
161
+
162
+ # Check if this file is new or has been modified
163
+ if file_path not in file_modification_times or current_mtime > file_modification_times[file_path]:
164
+ # Extract room_id from filename
165
+ parts = file.split('_', 1)
166
+ if len(parts) > 0:
167
+ room_id = parts[0]
168
+ updated_rooms.add(room_id)
169
+
170
+ # Update tracked modification time
171
+ file_modification_times[file_path] = current_mtime
172
+
173
+ # Reload history for updated rooms
174
+ for room_id in updated_rooms:
175
+ if room_id in chat_history:
176
+ # Remember we had this room loaded
177
+ old_history_len = len(chat_history[room_id])
178
+ # Clear and reload
179
+ chat_history[room_id] = []
180
+ load_room_history(room_id)
181
+ new_history_len = len(chat_history[room_id])
182
+
183
+ if new_history_len > old_history_len:
184
+ logger.info(f"Found {new_history_len - old_history_len} new messages for room {room_id}")
185
 
186
+ return updated_rooms
187
+
188
+ def get_sector_coordinates(room_id):
189
+ """Convert a room ID to grid coordinates, or assign new ones."""
190
+ try:
191
+ # Try to parse room ID as "x,y"
192
+ if ',' in room_id:
193
+ x, y = map(int, room_id.split(','))
194
+ return max(0, min(x, GRID_WIDTH-1)), max(0, min(y, GRID_HEIGHT-1))
195
+ except:
196
+ pass
197
+
198
+ # Hash the room_id string to get stable coordinates
199
+ hash_val = hash(room_id)
200
+ x = abs(hash_val) % GRID_WIDTH
201
+ y = abs(hash_val >> 8) % GRID_HEIGHT
202
+
203
+ return x, y
204
+
205
+ def generate_sector_map():
206
+ """Generate an ASCII representation of the sector map."""
207
+ # Initialize empty grid
208
+ grid = [[' ' for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
209
+
210
+ # Place active rooms with user counts
211
+ for room_id, users in sector_users.items():
212
+ if users: # Only show rooms with users
213
+ x, y = get_sector_coordinates(room_id)
214
+ user_count = len(users)
215
+ grid[y][x] = str(min(user_count, 9)) if user_count < 10 else '+'
216
+
217
+ # Create ASCII representation
218
+ header = ' ' + ''.join([str(i % 10) for i in range(GRID_WIDTH)])
219
+ map_str = header + '\n'
220
+
221
+ for y in range(GRID_HEIGHT):
222
+ row = f"{y % 10}|"
223
+ for x in range(GRID_WIDTH):
224
+ row += grid[y][x]
225
+ row += '|'
226
+ map_str += row + '\n'
227
+
228
+ footer = ' ' + ''.join([str(i % 10) for i in range(GRID_WIDTH)])
229
+ map_str += footer
230
+
231
+ return f"```\n{map_str}\n```\n\nLegend: Number indicates users in sector. '+' means 10+ users."
232
 
233
  async def clear_all_history():
234
  """Clear all chat history for all rooms."""
 
271
 
272
  active_connections[room_id][client_id] = websocket
273
 
274
+ # Add user to sector map
275
+ if room_id not in sector_users:
276
+ sector_users[room_id] = set()
277
+ sector_users[room_id].add(client_id)
278
+
279
+ # Get sector coordinates
280
+ x, y = get_sector_coordinates(room_id)
281
+
282
  # Load or initialize chat history
283
  room_history = load_room_history(room_id)
284
 
285
  # Send welcome message
286
  welcome_msg = {
287
  "type": "system",
288
+ "content": f"Welcome to room '{room_id}' (Sector {x},{y})! Connected from node '{NODE_NAME}'",
289
  "timestamp": datetime.now().isoformat(),
290
  "sender": "system",
291
  "room_id": room_id
292
  }
293
  await websocket.send(json.dumps(welcome_msg))
294
 
295
+ # Send sector map
296
+ map_msg = {
297
+ "type": "system",
298
+ "content": f"Sector Map:\n{generate_sector_map()}",
299
+ "timestamp": datetime.now().isoformat(),
300
+ "sender": "system",
301
+ "room_id": room_id
302
+ }
303
+ await websocket.send(json.dumps(map_msg))
304
+
305
  # Send chat history
306
  for msg in room_history:
307
  await websocket.send(json.dumps(msg))
 
309
  # Broadcast join notification
310
  join_msg = {
311
  "type": "system",
312
+ "content": f"User joined the room (Sector {x},{y}) - {len(sector_users[room_id])} users now present",
313
  "timestamp": datetime.now().isoformat(),
314
  "sender": "system",
315
  "room_id": room_id
316
  }
317
  await broadcast_message(join_msg, room_id)
318
+ save_message_to_history(room_id, join_msg)
319
 
320
+ logger.info(f"New client {client_id} connected to room {room_id} (Sector {x},{y})")
321
 
322
  # Handle messages from this client
323
  async for message in websocket:
 
329
  result = await clear_all_history()
330
  continue
331
 
332
+ # Check for map request
333
+ if data.get("type") == "command" and data.get("command") == "show_map":
334
+ map_msg = {
335
+ "type": "system",
336
+ "content": f"Sector Map:\n{generate_sector_map()}",
337
+ "timestamp": datetime.now().isoformat(),
338
+ "sender": "system",
339
+ "room_id": room_id
340
+ }
341
+ await websocket.send(json.dumps(map_msg))
342
+ continue
343
+
344
  # Add metadata to the message
345
  data["timestamp"] = datetime.now().isoformat()
346
  data["sender_node"] = NODE_NAME
 
352
  chat_history[room_id] = chat_history[room_id][-500:]
353
 
354
  # Save to persistent storage
355
+ save_message_to_history(room_id, data)
356
 
357
  # Broadcast to all clients in the room
358
  await broadcast_message(data, room_id)
 
374
  if room_id in active_connections and client_id in active_connections[room_id]:
375
  del active_connections[room_id][client_id]
376
 
377
+ # Remove user from sector map
378
+ if room_id in sector_users and client_id in sector_users[room_id]:
379
+ sector_users[room_id].remove(client_id)
380
+
381
+ # Get sector coordinates
382
+ x, y = get_sector_coordinates(room_id)
383
+
384
  # Broadcast leave notification
385
  leave_msg = {
386
  "type": "system",
387
+ "content": f"User left the room (Sector {x},{y}) - {len(sector_users.get(room_id, set()))} users remaining",
388
  "timestamp": datetime.now().isoformat(),
389
  "sender": "system",
390
  "room_id": room_id
391
  }
392
  await broadcast_message(leave_msg, room_id)
393
+ save_message_to_history(room_id, leave_msg)
394
 
395
  # Clean up empty rooms (but keep history)
396
  if not active_connections[room_id]:
 
514
  with gr.Column(scale=1):
515
  clear_button = gr.Button("🧹 Clear All Chat History", variant="stop")
516
 
517
+ # Join room controls with 2D grid input
518
  with gr.Row():
519
+ with gr.Column(scale=2):
520
+ room_id_input = gr.Textbox(label="Room ID", placeholder="Enter room ID or use x,y coordinates")
521
+ join_button = gr.Button("Join Room")
522
+ with gr.Column(scale=1):
523
+ with gr.Row():
524
+ x_coord = gr.Number(label="X", value=0, minimum=0, maximum=GRID_WIDTH-1, step=1)
525
+ y_coord = gr.Number(label="Y", value=0, minimum=0, maximum=GRID_HEIGHT-1, step=1)
526
+ grid_join_button = gr.Button("Join by Coordinates")
527
 
528
+ # Chat area with multiline support
529
  chat_history_output = gr.Textbox(label="Chat History", lines=20, max_lines=20)
530
 
531
+ # Message controls with multiline support
532
  with gr.Row():
533
  username_input = gr.Textbox(label="Username", placeholder="Enter your username", value="User")
534
+ with gr.Column(scale=3):
535
+ message_input = gr.Textbox(
536
+ label="Message",
537
+ placeholder="Type your message here. Press Shift+Enter for new line, Enter to send.",
538
+ lines=3
539
+ )
540
+ with gr.Column(scale=1):
541
+ send_button = gr.Button("Send")
542
+ map_button = gr.Button("🗺️ Show Map")
543
 
544
  # Current room display
545
  current_room_display = gr.Textbox(label="Current Room", value="Not joined any room yet")
 
557
  outputs=[room_list]
558
  )
559
 
560
+ def join_by_coordinates(x, y):
561
+ """Join a room using grid coordinates."""
562
+ room_id = f"{int(x)},{int(y)}"
563
+ return room_id
564
+
565
+ # Link grid coordinates to room ID
566
+ grid_join_button.click(
567
+ join_by_coordinates,
568
+ inputs=[x_coord, y_coord],
569
+ outputs=[room_id_input]
570
+ ).then(
571
+ join_room,
572
+ inputs=[room_id_input, chat_history_output],
573
+ outputs=[current_room_display, chat_history_output]
574
+ )
575
+
576
  join_button.click(
577
  join_room,
578
  inputs=[room_id_input, chat_history_output],
 
584
  return "Please join a room first", message
585
 
586
  actual_room_id = room_id.replace("Joined room: ", "").strip()
587
+
588
+ # Support for multi-line messages
589
+ message_lines = message.strip().split("\n")
590
+ formatted_msg = ""
591
+
592
+ for line in message_lines:
593
+ if line.strip(): # Skip empty lines
594
+ sent_msg = send_message(line.strip(), username, actual_room_id)
595
+ if sent_msg:
596
+ formatted_msg += sent_msg + "\n"
597
 
598
  if formatted_msg:
599
  return "", formatted_msg
 
605
  outputs=[message_input, chat_history_output]
606
  )
607
 
608
+ def show_sector_map(room_id):
609
+ if not room_id.startswith("Joined room:"):
610
+ return "Please join a room first to view the map"
611
+
612
+ return generate_sector_map()
613
+
614
+ map_button.click(
615
+ show_sector_map,
616
+ inputs=[current_room_display],
617
+ outputs=[chat_history_output]
618
+ )
619
+
620
+ # Handle Enter key for sending, Shift+Enter for new line
621
+ def on_message_submit(message, username, room_id):
622
+ # Simply call send_and_clear
623
+ return send_and_clear(message, username, room_id)
624
+
625
  message_input.submit(
626
+ on_message_submit,
627
  inputs=[message_input, username_input, current_room_display],
628
  outputs=[message_input, chat_history_output]
629
  )