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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +180 -14
app.py CHANGED
@@ -19,9 +19,24 @@ logger = logging.getLogger("chat-node")
19
 
20
  # Dictionary to store active connections
21
  active_connections = {}
22
- # Dictionary to store message history for each chat room
23
  chat_history = {}
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  # Get node name from URL or command line
26
  def get_node_name():
27
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
@@ -38,6 +53,82 @@ def get_node_name():
38
 
39
  return node_name, port
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  async def websocket_handler(websocket, path):
42
  """Handle WebSocket connections."""
43
  try:
@@ -49,11 +140,13 @@ async def websocket_handler(websocket, path):
49
  client_id = str(uuid.uuid4())
50
  if room_id not in active_connections:
51
  active_connections[room_id] = {}
52
- chat_history[room_id] = []
53
 
54
  active_connections[room_id][client_id] = websocket
55
 
56
- # Send welcome message and chat history
 
 
 
57
  welcome_msg = {
58
  "type": "system",
59
  "content": f"Welcome to room '{room_id}'! Connected from node '{NODE_NAME}'",
@@ -64,7 +157,7 @@ async def websocket_handler(websocket, path):
64
  await websocket.send(json.dumps(welcome_msg))
65
 
66
  # Send chat history
67
- for msg in chat_history[room_id]:
68
  await websocket.send(json.dumps(msg))
69
 
70
  # Broadcast join notification
@@ -84,6 +177,11 @@ async def websocket_handler(websocket, path):
84
  try:
85
  data = json.loads(message)
86
 
 
 
 
 
 
87
  # Add metadata to the message
88
  data["timestamp"] = datetime.now().isoformat()
89
  data["sender_node"] = NODE_NAME
@@ -91,8 +189,11 @@ async def websocket_handler(websocket, path):
91
 
92
  # Store in history
93
  chat_history[room_id].append(data)
94
- if len(chat_history[room_id]) > 100: # Limit history to 100 messages
95
- chat_history[room_id] = chat_history[room_id][-100:]
 
 
 
96
 
97
  # Broadcast to all clients in the room
98
  await broadcast_message(data, room_id)
@@ -124,10 +225,9 @@ async def websocket_handler(websocket, path):
124
  }
125
  await broadcast_message(leave_msg, room_id)
126
 
127
- # Clean up empty rooms
128
  if not active_connections[room_id]:
129
  del active_connections[room_id]
130
- # Optionally, you might want to keep the chat history
131
 
132
  async def broadcast_message(message, room_id):
133
  """Broadcast a message to all clients in a room."""
@@ -183,32 +283,79 @@ def join_room(room_id, chat_history_output):
183
  # Sanitize the room ID
184
  room_id = urllib.parse.quote(room_id.strip())
185
 
186
- # Create the room if it doesn't exist
187
- if room_id not in chat_history:
188
- chat_history[room_id] = []
189
 
190
  # Format existing messages
191
  formatted_history = []
192
- for msg in chat_history[room_id]:
193
  if msg.get("type") == "chat":
194
- formatted_history.append(f"{msg.get('username', 'Anonymous')}: {msg.get('content', '')}")
 
 
 
 
 
 
 
 
195
  elif msg.get("type") == "system":
196
  formatted_history.append(f"System: {msg.get('content', '')}")
197
 
198
  return f"Joined room: {room_id}", formatted_history
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  def create_gradio_interface():
201
  """Create and return the Gradio interface."""
202
  with gr.Blocks(title=f"Chat Node: {NODE_NAME}") as interface:
203
  gr.Markdown(f"# Chat Node: {NODE_NAME}")
204
  gr.Markdown("Join a room by entering a room ID below or create a new one.")
205
 
 
 
 
 
 
 
 
 
 
206
  with gr.Row():
207
  room_id_input = gr.Textbox(label="Room ID", placeholder="Enter room ID")
208
  join_button = gr.Button("Join Room")
209
 
210
- chat_history_output = gr.Textbox(label="Chat History", lines=15, max_lines=15)
 
211
 
 
212
  with gr.Row():
213
  username_input = gr.Textbox(label="Username", placeholder="Enter your username", value="User")
214
  message_input = gr.Textbox(label="Message", placeholder="Type your message here")
@@ -218,6 +365,18 @@ def create_gradio_interface():
218
  current_room_display = gr.Textbox(label="Current Room", value="Not joined any room yet")
219
 
220
  # Event handlers
 
 
 
 
 
 
 
 
 
 
 
 
221
  join_button.click(
222
  join_room,
223
  inputs=[room_id_input, chat_history_output],
@@ -247,6 +406,13 @@ def create_gradio_interface():
247
  inputs=[message_input, username_input, current_room_display],
248
  outputs=[message_input, chat_history_output]
249
  )
 
 
 
 
 
 
 
250
 
251
  return interface
252
 
 
19
 
20
  # Dictionary to store active connections
21
  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)
33
+
34
+ # README.md file that won't be listed or deleted
35
+ README_PATH = os.path.join(HISTORY_DIR, "README.md")
36
+ if not os.path.exists(README_PATH):
37
+ with open(README_PATH, "w") as f:
38
+ f.write("# Chat History\n\nThis directory contains persistent chat history files.\n")
39
+
40
  # Get node name from URL or command line
41
  def get_node_name():
42
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
 
53
 
54
  return node_name, port
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."""
107
+ global chat_history
108
+
109
+ # Clear in-memory history
110
+ chat_history = {}
111
+
112
+ # Delete all history files except README.md
113
+ for file in os.listdir(HISTORY_DIR):
114
+ if file.endswith(".md") and file != "README.md":
115
+ os.remove(os.path.join(HISTORY_DIR, file))
116
+
117
+ # Broadcast clear message to all rooms
118
+ clear_msg = {
119
+ "type": "system",
120
+ "content": "🧹 All chat history has been cleared by a user",
121
+ "timestamp": datetime.now().isoformat(),
122
+ "sender": "system"
123
+ }
124
+
125
+ for room_id in list(active_connections.keys()):
126
+ clear_msg["room_id"] = room_id
127
+ await broadcast_message(clear_msg, room_id)
128
+
129
+ logger.info("All chat history cleared")
130
+ return "All chat history cleared"
131
+
132
  async def websocket_handler(websocket, path):
133
  """Handle WebSocket connections."""
134
  try:
 
140
  client_id = str(uuid.uuid4())
141
  if room_id not in active_connections:
142
  active_connections[room_id] = {}
 
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}'",
 
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))
162
 
163
  # Broadcast join notification
 
177
  try:
178
  data = json.loads(message)
179
 
180
+ # Check for clear command
181
+ if data.get("type") == "command" and data.get("command") == "clear_history":
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
 
189
 
190
  # Store in history
191
  chat_history[room_id].append(data)
192
+ if len(chat_history[room_id]) > 500: # Increased limit to 500 messages
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)
 
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]:
230
  del active_connections[room_id]
 
231
 
232
  async def broadcast_message(message, room_id):
233
  """Broadcast a message to all clients in a room."""
 
283
  # Sanitize the room ID
284
  room_id = urllib.parse.quote(room_id.strip())
285
 
286
+ # Load room history from persistent storage
287
+ history = load_room_history(room_id)
 
288
 
289
  # Format existing messages
290
  formatted_history = []
291
+ for msg in history:
292
  if msg.get("type") == "chat":
293
+ sender_node = f" [{msg.get('sender_node', 'unknown')}]" if "sender_node" in msg else ""
294
+ time_str = ""
295
+ if "timestamp" in msg:
296
+ try:
297
+ dt = datetime.fromisoformat(msg["timestamp"])
298
+ time_str = f"[{dt.strftime('%H:%M:%S')}] "
299
+ except:
300
+ pass
301
+ formatted_history.append(f"{time_str}{msg.get('username', 'Anonymous')}{sender_node}: {msg.get('content', '')}")
302
  elif msg.get("type") == "system":
303
  formatted_history.append(f"System: {msg.get('content', '')}")
304
 
305
  return f"Joined room: {room_id}", formatted_history
306
 
307
+ def send_clear_command():
308
+ """Send a command to clear all chat history."""
309
+ global message_queue
310
+
311
+ msg_data = {
312
+ "type": "command",
313
+ "command": "clear_history",
314
+ "username": "System"
315
+ }
316
+
317
+ # Add to queue for processing by the main loop
318
+ message_queue.append(msg_data)
319
+
320
+ return "🧹 Clearing all chat history..."
321
+
322
+ def list_available_rooms():
323
+ """List all available chat rooms with their last activity time."""
324
+ history_files = get_all_history_files()
325
+
326
+ if not history_files:
327
+ return "No chat rooms available yet. Create one by joining a room!"
328
+
329
+ room_list = "### Available Chat Rooms\n\n"
330
+ for room_id, file_path, mod_time in history_files:
331
+ last_activity = datetime.fromtimestamp(mod_time).strftime("%Y-%m-%d %H:%M:%S")
332
+ room_list += f"- **{room_id}**: Last activity {last_activity}\n"
333
+
334
+ return room_list
335
+
336
  def create_gradio_interface():
337
  """Create and return the Gradio interface."""
338
  with gr.Blocks(title=f"Chat Node: {NODE_NAME}") as interface:
339
  gr.Markdown(f"# Chat Node: {NODE_NAME}")
340
  gr.Markdown("Join a room by entering a room ID below or create a new one.")
341
 
342
+ # Room list and management
343
+ with gr.Row():
344
+ with gr.Column(scale=3):
345
+ room_list = gr.Markdown(value="Loading available rooms...")
346
+ refresh_button = gr.Button("🔄 Refresh Room List")
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")
 
365
  current_room_display = gr.Textbox(label="Current Room", value="Not joined any room yet")
366
 
367
  # Event handlers
368
+ refresh_button.click(
369
+ list_available_rooms,
370
+ inputs=[],
371
+ outputs=[room_list]
372
+ )
373
+
374
+ clear_button.click(
375
+ send_clear_command,
376
+ inputs=[],
377
+ outputs=[room_list]
378
+ )
379
+
380
  join_button.click(
381
  join_room,
382
  inputs=[room_id_input, chat_history_output],
 
406
  inputs=[message_input, username_input, current_room_display],
407
  outputs=[message_input, chat_history_output]
408
  )
409
+
410
+ # On load, populate room list
411
+ interface.load(
412
+ list_available_rooms,
413
+ inputs=[],
414
+ outputs=[room_list]
415
+ )
416
 
417
  return interface
418