awacke1 commited on
Commit
5ede71e
Β·
verified Β·
1 Parent(s): fd9ab74

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -114
app.py CHANGED
@@ -1,22 +1,12 @@
1
  import gradio as gr
2
  import asyncio
3
  import websockets
4
- import json
5
  import uuid
6
  import argparse
7
  from datetime import datetime
8
- import logging
9
- import sys
10
  import os
11
  import random
12
-
13
- # Configure logging - the gossip queen πŸ“’πŸ‘‘ sets up the chatterbox!
14
- logging.basicConfig(
15
- level=logging.INFO,
16
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
17
- handlers=[logging.StreamHandler(sys.stdout)]
18
- )
19
- logger = logging.getLogger("chat-node")
20
 
21
  # Fun usernames with emojis - the VIP list of quirky characters! πŸŽ‰πŸ˜œ
22
  FUN_USERNAMES = [
@@ -27,9 +17,12 @@ FUN_USERNAMES = [
27
  "MysticMoose 🦌", "GlitchGnome 🧚", "VortexViper 🐍", "ChronoChimp πŸ’"
28
  ]
29
 
30
- # Directory for logs - the secret vault where tales are stashed! πŸ—„οΈπŸ”’
31
- LOG_DIR = "user_logs"
32
- os.makedirs(LOG_DIR, exist_ok=True)
 
 
 
33
 
34
  # Node name - the app’s codename generator, sneaky and slick! πŸ•΅οΈβ€β™‚οΈπŸ’Ύ
35
  def get_node_name():
@@ -40,151 +33,193 @@ def get_node_name():
40
  args = parser.parse_args()
41
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
42
 
43
- # Log file generator - crafts a shiny new logbook with a timestamp twist! πŸ“…βœοΈ
44
- def get_log_file(username):
45
- """⏰ Stamps the clock and names the log after our witty hero - fresh and ready! πŸ“œ"""
46
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
47
- safe_username = username.replace(" ", "_").encode('ascii', 'ignore').decode('ascii')
48
- return os.path.join(LOG_DIR, f"{safe_username}_{timestamp}.md")
49
-
50
- # Log saver - the scribe that pens epic log tales into Markdown glory! πŸ–‹οΈπŸ“š
51
- def save_log_entry(username, entry):
52
- """πŸ–ŒοΈ Scribbles a log entry into a Markdown masterpiece - tales of triumph and woe! 🎭"""
53
- log_file = get_log_file(username)
54
  try:
55
- with open(log_file, 'a') as f:
56
- f.write(f"```log\n[{entry['timestamp']}] {entry['level']}: {entry['message']}\n```\n")
57
- logger.info(f"Saved log entry to {log_file}")
58
- return log_file
59
  except Exception as e:
60
- logger.error(f"Error saving log to {log_file}: {e}")
61
- return None
62
-
63
- # Log loader - the treasure hunter digging up the latest log loot! πŸ΄β€β˜ οΈπŸ’°
64
- def load_latest_log(username):
65
- """πŸ” Hunts down the freshest log file and dresses it up with line numbers - snazzy! ✨"""
66
- safe_username = username.replace(" ", "_").encode('ascii', 'ignore').decode('ascii')
67
- log_files = [f for f in os.listdir(LOG_DIR) if f.startswith(safe_username) and f.endswith(".md")]
68
- if not log_files:
69
- return "# No logs yet\nStart interacting to generate logs!"
70
- latest_file = max(log_files, key=lambda f: os.path.getmtime(os.path.join(LOG_DIR, f)))
71
  try:
72
- with open(os.path.join(LOG_DIR, latest_file), 'r') as f:
73
  content = f.read()
74
  lines = content.strip().split('\n')
75
- numbered_content = "\n".join(f"{i+1}. {line}" for i, line in enumerate(lines))
76
- return f"# Log for {username} ({latest_file})\n\n{numbered_content}"
77
  except Exception as e:
78
- logger.error(f"Error loading log {latest_file}: {e}")
79
- return "# Error loading log\nCheck server logs for details."
 
 
 
 
 
 
 
 
 
 
80
 
81
  active_connections = {}
82
 
83
- # WebSocket handler - the bouncer at the log party, keeping the vibe alive! πŸŽ‰πŸšͺ
84
  async def websocket_handler(websocket, path):
85
- """🎧 Listens at the door, letting log fans in and kicking out crashers! 🚨"""
86
  try:
87
  client_id = str(uuid.uuid4())
88
- room_id = "logs"
89
  active_connections.setdefault(room_id, {})[client_id] = websocket
90
- logger.info(f"Client {client_id} connected to logs")
91
 
92
  async for message in websocket:
93
  try:
94
- data = json.loads(message)
95
- data["timestamp"] = datetime.now().isoformat()
96
- await broadcast_message(data, room_id)
97
- except json.JSONDecodeError:
98
- await websocket.send(json.dumps({"type": "error", "content": "Invalid JSON", "timestamp": datetime.now().isoformat(), "sender": "system"}))
 
 
 
 
99
  except websockets.ConnectionClosed:
100
- logger.info(f"Client {client_id} disconnected from logs")
101
  finally:
102
  if room_id in active_connections and client_id in active_connections[room_id]:
103
  del active_connections[room_id][client_id]
104
  if not active_connections[room_id]:
105
  del active_connections[room_id]
106
 
107
- # Broadcaster - the town crier shouting log updates to all who’ll listen! πŸ“£πŸŒ
108
  async def broadcast_message(message, room_id):
109
- """πŸ“’ Blasts the latest log scoop to every ear in the room - no one misses out! πŸŽ™οΈ"""
110
  if room_id in active_connections:
111
  disconnected = []
112
  for client_id, ws in active_connections[room_id].items():
113
  try:
114
- await ws.send(json.dumps(message))
115
  except websockets.ConnectionClosed:
116
  disconnected.append(client_id)
117
  for client_id in disconnected:
118
  del active_connections[room_id][client_id]
119
 
120
- # WebSocket server starter - lights the fuse on the log-streaming rocket! πŸš€πŸ”₯
121
  async def start_websocket_server(host='0.0.0.0', port=8765):
122
- """🌐 Fires up the WebSocket engine, ready to zip logs at warp speed! ⚑"""
123
  server = await websockets.serve(websocket_handler, host, port)
124
- logger.info(f"WebSocket server started on ws://{host}:{port}")
125
  return server
126
 
127
- # Log handler - the sneaky spy logging every move with a witty twist! πŸ•΅οΈβ€β™€οΈπŸ“
128
- class LogBroadcastHandler(logging.Handler):
129
- def __init__(self, username):
130
- super().__init__()
131
- self.username = username
132
-
133
- def emit(self, record):
134
- """πŸ•΅οΈβ€β™‚οΈ Catches every whisper and scribbles it into the logbook with flair! ✍️"""
135
- entry = {
136
- "timestamp": datetime.now().isoformat(),
137
- "level": record.levelname,
138
- "message": self.format(record),
139
- "name": record.name
140
- }
141
- log_file = save_log_entry(self.username, entry)
142
- if log_file:
143
- asyncio.create_task(broadcast_message({
144
- "type": "log",
145
- "content": f"New log entry saved to {log_file}",
146
- "timestamp": entry["timestamp"],
147
- "sender": "system",
148
- "username": self.username
149
- }, "logs"))
150
-
151
- # Interface maker - the artist painting a live log masterpiece! πŸŽ¨πŸ–ΌοΈ
152
- def create_gradio_interface(username):
153
- """πŸ–ŒοΈ Whips up a snazzy UI canvas that updates live with log magic! 🌟"""
154
- logger.handlers = [LogBroadcastHandler(username)]
155
- def update_log_display(dummy_input):
156
- """🎭 The puppet master pulling fresh log strings for the show! 🎬"""
157
- return load_latest_log(username)
158
-
159
- with gr.Blocks(title=f"Log Viewer: {NODE_NAME}", css=".code-container { font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 10px; border-radius: 5px; }") as interface:
160
- gr.Markdown(f"# Log Viewer for {username} on Node: {NODE_NAME}")
161
- gr.Markdown("Logs update live as new entries are written - watch the action unfold! πŸŽ₯")
162
- dummy_input = gr.Textbox(visible=False, value="trigger", interactive=True)
163
- log_display = gr.Code(label="Your Latest Log", language="markdown", lines=20, elem_classes=["code-container"])
164
-
165
- # Live interface FTW! πŸš€
166
- interface = gr.Interface(
167
- fn=update_log_display,
168
- inputs=[dummy_input],
169
- outputs=[log_display],
170
- live=True,
171
- title=f"Log Viewer: {NODE_NAME}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  )
173
- return interface
174
 
175
- # Main event - the ringmaster kicking off the log circus! πŸŽͺ🀑
 
 
 
 
 
 
 
 
176
  async def main():
177
- """🎀 Drops the mic and starts the log party - it’s showtime, folks! πŸŽ‰"""
178
  global NODE_NAME
179
  NODE_NAME, port = get_node_name()
180
  await start_websocket_server()
181
 
182
- username = random.choice(FUN_USERNAMES)
183
- logger.info(f"Assigned username: {username}")
184
-
185
- interface = create_gradio_interface(username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  import uvicorn
187
- await uvicorn.Server(uvicorn.Config(interface, host="0.0.0.0", port=port)).serve()
188
 
189
  if __name__ == "__main__":
190
  asyncio.run(main())
 
1
  import gradio as gr
2
  import asyncio
3
  import websockets
 
4
  import uuid
5
  import argparse
6
  from datetime import datetime
 
 
7
  import os
8
  import random
9
+ import time
 
 
 
 
 
 
 
10
 
11
  # Fun usernames with emojis - the VIP list of quirky characters! πŸŽ‰πŸ˜œ
12
  FUN_USERNAMES = [
 
17
  "MysticMoose 🦌", "GlitchGnome 🧚", "VortexViper 🐍", "ChronoChimp πŸ’"
18
  ]
19
 
20
+ # Directory for chat logs - the secret vault where tales are stashed! πŸ—„οΈπŸ”’
21
+ CHAT_DIR = "chat_logs"
22
+ os.makedirs(CHAT_DIR, exist_ok=True)
23
+
24
+ # Persistent chat file - the grand tome of all chatter! πŸ“–βœ¨
25
+ CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
26
 
27
  # Node name - the app’s codename generator, sneaky and slick! πŸ•΅οΈβ€β™‚οΈπŸ’Ύ
28
  def get_node_name():
 
33
  args = parser.parse_args()
34
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
35
 
36
+ # Chat saver - the scribe etching epic messages into the eternal scroll! πŸ–‹οΈπŸ“œ
37
+ def save_chat_entry(username, message):
38
+ """πŸ–ŒοΈ Carves a chat line into the grand Markdown tome - history in the making! πŸ›οΈ"""
39
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
40
+ entry = f"[{timestamp}] {username}: {message}"
 
 
 
 
 
 
41
  try:
42
+ with open(CHAT_FILE, 'a') as f:
43
+ f.write(f"{entry}\n")
44
+ return True
 
45
  except Exception as e:
46
+ print(f"Oops! Failed to save chat: {e}")
47
+ return False
48
+
49
+ # Chat loader - the archaeologist unearthing the chat saga! β›οΈπŸ“š
50
+ def load_chat():
51
+ """πŸ” Digs up the chat treasure from the filesystem - tales of old and new! πŸ’°"""
52
+ if not os.path.exists(CHAT_FILE):
53
+ with open(CHAT_FILE, 'w') as f:
54
+ f.write("# Global Chat\n\nNo messages yet - start chatting! 🎀\n")
 
 
55
  try:
56
+ with open(CHAT_FILE, 'r') as f:
57
  content = f.read()
58
  lines = content.strip().split('\n')
59
+ numbered_content = "\n".join(f"{i+1}. {line}" for i, line in enumerate(lines) if line.strip())
60
+ return numbered_content
61
  except Exception as e:
62
+ print(f"Chat load hiccup: {e}")
63
+ return "# Error loading chat\nSomething went wonky! 😡"
64
+
65
+ # User list grabber - the social butterfly spotting all the chatters! πŸ¦‹πŸ‘₯
66
+ def get_user_list(chat_content):
67
+ """πŸ‘€ Peeks at the chat to spot all the cool cats talking - who’s in the club? 🎸"""
68
+ users = set()
69
+ for line in chat_content.split('\n'):
70
+ if line.strip() and ': ' in line:
71
+ user = line.split(': ')[1].split(' ')[0]
72
+ users.add(user)
73
+ return sorted(list(users))
74
 
75
  active_connections = {}
76
 
77
+ # WebSocket handler - the bouncer at the chat rave, keeping it hopping! πŸŽ‰πŸšͺ
78
  async def websocket_handler(websocket, path):
79
+ """🎧 Guards the chat gate, letting messages fly and booting crashers! 🚨"""
80
  try:
81
  client_id = str(uuid.uuid4())
82
+ room_id = "chat"
83
  active_connections.setdefault(room_id, {})[client_id] = websocket
84
+ print(f"Client {client_id} joined the chat party!")
85
 
86
  async for message in websocket:
87
  try:
88
+ # Parse message as simple text (no JSON)
89
+ parts = message.split('|', 1)
90
+ if len(parts) == 2:
91
+ username, content = parts
92
+ save_chat_entry(username, content)
93
+ await broadcast_message(f"{username}|{content}", room_id)
94
+ except Exception as e:
95
+ print(f"Message mishap: {e}")
96
+ await websocket.send(f"ERROR|Oops, bad message format! 😬")
97
  except websockets.ConnectionClosed:
98
+ print(f"Client {client_id} bailed from the chat!")
99
  finally:
100
  if room_id in active_connections and client_id in active_connections[room_id]:
101
  del active_connections[room_id][client_id]
102
  if not active_connections[room_id]:
103
  del active_connections[room_id]
104
 
105
+ # Broadcaster - the megaphone blasting chat vibes to all! πŸ“£πŸŽΆ
106
  async def broadcast_message(message, room_id):
107
+ """πŸ“’ Shouts the latest chat beat to every dancer in the room - hear it loud! 🎡"""
108
  if room_id in active_connections:
109
  disconnected = []
110
  for client_id, ws in active_connections[room_id].items():
111
  try:
112
+ await ws.send(message)
113
  except websockets.ConnectionClosed:
114
  disconnected.append(client_id)
115
  for client_id in disconnected:
116
  del active_connections[room_id][client_id]
117
 
118
+ # WebSocket starter - the DJ spinning up the chat tunes! 🎧πŸ”₯
119
  async def start_websocket_server(host='0.0.0.0', port=8765):
120
+ """🌐 Cranks up the WebSocket jukebox, ready to rock the chat scene! 🎸"""
121
  server = await websockets.serve(websocket_handler, host, port)
122
+ print(f"WebSocket server jamming on ws://{host}:{port}")
123
  return server
124
 
125
+ # Chat interface maker - the stage builder for our chat extravaganza! 🎭🏟️
126
+ def create_gradio_interface(initial_username):
127
+ """πŸ–ŒοΈ Sets up the chat stage with live updates and name-switching flair! 🌟"""
128
+ with gr.Blocks(title=f"Chat Node: {NODE_NAME}", css=".chat-box { font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 10px; border-radius: 5px; }") as demo:
129
+ # Shared state - the magic memory box all clients peek into! πŸͺ„πŸ“¦
130
+ chat_state = gr.State(value=load_chat())
131
+
132
+ gr.Markdown(f"# Chat Node: {NODE_NAME}")
133
+ gr.Markdown("Chat live, switch names, and keep the party going! πŸŽ‰")
134
+
135
+ with gr.Row():
136
+ with gr.Column(scale=3):
137
+ chat_display = gr.Code(label="Chat History", language="markdown", lines=15, elem_classes=["chat-box"])
138
+ with gr.Column(scale=1):
139
+ user_list = gr.Dropdown(label="Switch User", choices=get_user_list(chat_state.value), value=initial_username)
140
+
141
+ with gr.Row():
142
+ username_input = gr.Textbox(label="Your Name", value=initial_username)
143
+ message_input = gr.Textbox(label="Message", placeholder="Type your epic line here! ✍️")
144
+ send_button = gr.Button("Send πŸš€")
145
+
146
+ # Chat updater - the wizard keeping the chat spell alive! πŸ§™β€β™‚οΈβœ¨
147
+ def update_chat(username, message, current_chat):
148
+ """🎩 Conjures a fresh chat view with your latest quip - abracadabra! πŸŽ‡"""
149
+ if message.strip():
150
+ save_chat_entry(username, message)
151
+ new_chat = load_chat()
152
+ return new_chat, get_user_list(new_chat)
153
+
154
+ # Name switcher - the shapeshifter swapping your chat persona! πŸ¦Έβ€β™‚οΈπŸŽ­
155
+ def switch_user(new_username, current_chat):
156
+ """πŸ¦„ Transforms you into a new chat hero - new name, same game! πŸ†"""
157
+ return new_username, current_chat, get_user_list(current_chat)
158
+
159
+ # Live chat persistence - the timekeeper syncing the chat clock! β°πŸ”„
160
+ async def persist_chat():
161
+ """⏳ Keeps the chat tome in sync, scribbling updates every few ticks! πŸ“"""
162
+ while True:
163
+ await asyncio.sleep(5) # Persist every 5 seconds
164
+ with open(CHAT_FILE, 'r') as f:
165
+ current = f.read()
166
+ chat_state.value = current
167
+
168
+ # Hook up the magic! 🎣
169
+ send_button.click(
170
+ fn=update_chat,
171
+ inputs=[username_input, message_input, chat_state],
172
+ outputs=[chat_display, user_list],
173
+ _js="() => ['', document.getElementById('message_input').value, document.getElementById('chat_display').value]"
174
+ )
175
+ message_input.submit(
176
+ fn=update_chat,
177
+ inputs=[username_input, message_input, chat_state],
178
+ outputs=[chat_display, user_list]
179
+ )
180
+ user_list.change(
181
+ fn=switch_user,
182
+ inputs=[user_list, chat_state],
183
+ outputs=[username_input, chat_display, user_list]
184
  )
 
185
 
186
+ # Start with the latest chat vibes! 🎡
187
+ demo.load(lambda: (load_chat(), get_user_list(load_chat())), None, [chat_display, user_list])
188
+
189
+ # Kick off the persistence dance! πŸ’ƒ
190
+ asyncio.create_task(persist_chat())
191
+
192
+ return demo
193
+
194
+ # Main event - the ringmaster kicking off the chat circus! πŸŽͺ🀑
195
  async def main():
196
+ """🎀 Drops the mic and starts the chat party - it’s showtime, folks! πŸŽ‰"""
197
  global NODE_NAME
198
  NODE_NAME, port = get_node_name()
199
  await start_websocket_server()
200
 
201
+ # Grab username from URL or roll the dice! 🎲
202
+ initial_username = os.environ.get("QUERY_STRING", "").split("username=")[-1] if "username=" in os.environ.get("QUERY_STRING", "") else random.choice(FUN_USERNAMES)
203
+ print(f"Welcoming {initial_username} to the chat bash!")
204
+
205
+ interface = create_gradio_interface(initial_username)
206
+
207
+ # URL param magic - the gatekeeper checking your VIP pass! 🎟️
208
+ from starlette.middleware.base import BaseHTTPMiddleware
209
+ class UsernameMiddleware(BaseHTTPMiddleware):
210
+ async def dispatch(self, request, call_next):
211
+ """πŸ”‘ Snags your name from the URL VIP list - exclusive entry! 🎩"""
212
+ query_params = dict(request.query_params)
213
+ if "username" in query_params and query_params["username"] in FUN_USERNAMES:
214
+ global initial_username
215
+ initial_username = query_params["username"]
216
+ return await call_next(request)
217
+
218
+ app = gr.routes.App.create_app(interface)
219
+ app.add_middleware(UsernameMiddleware)
220
+
221
  import uvicorn
222
+ await uvicorn.Server(uvicorn.Config(app, host="0.0.0.0", port=port)).serve()
223
 
224
  if __name__ == "__main__":
225
  asyncio.run(main())