awacke1 commited on
Commit
3d01981
·
verified ·
1 Parent(s): b198154

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +710 -472
app.py CHANGED
@@ -10,126 +10,232 @@ import time
10
  import hashlib
11
  from PIL import Image
12
  import glob
13
- import re
14
- from urllib.parse import quote
15
  import base64
16
  import io
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- # App Configuration
19
- Site_Name = '🤖🧠MMO Chat Brain📝🔬'
20
- title = "🤖🧠MMO Chat Brain📝🔬"
21
  icons = '🤖🧠🔬📝'
22
  START_ROOM = "Sector 🌌"
23
 
 
24
  st.set_page_config(
25
- page_title=title,
26
  page_icon=icons,
27
  layout="wide",
28
  initial_sidebar_state="auto"
29
  )
30
 
31
- # Fun usernames with emojis and rhyming labels - the VIP list of quirky characters! 🎉😜
32
  FUN_USERNAMES = {
33
- "CosmicJester 🌌": "Jest Best",
34
- "PixelPanda 🐼": "Panda Dandy",
35
- "QuantumQuack 🦆": "Quack Pack",
36
- "StellarSquirrel 🐿️": "Squirrel Whirl",
37
- "GizmoGuru ⚙️": "Guru Brew",
38
- "NebulaNinja 🌠": "Ninja Zinger",
39
- "ByteBuster 💾": "Buster Cluster",
40
- "GalacticGopher 🌍": "Gopher Loafer",
41
- "RocketRaccoon 🚀": "Raccoon Zoom",
42
- "EchoElf 🧝": "Elf Shelf",
43
- "PhantomFox 🦊": "Fox Rocks",
44
- "WittyWizard 🧙": "Wizard Blizzard",
45
- "LunarLlama 🌙": "Llama Drama",
46
- "SolarSloth ☀️": "Sloth Growth",
47
- "AstroAlpaca 🦙": "Alpaca Plaque",
48
- "CyberCoyote 🐺": "Coyote Byte",
49
- "MysticMoose 🦌": "Moose Juice",
50
- "GlitchGnome 🧚": "Gnome Roam",
51
- "VortexViper 🐍": "Viper Hyper",
52
- "ChronoChimp 🐒": "Chimp Whimp"
53
  }
54
 
55
- # Directories for chat and votes - the secret vaults where treasures are stashed! 🗄️🔒
56
  CHAT_DIR = "chat_logs"
57
  VOTE_DIR = "vote_logs"
58
  STATE_FILE = "user_state.txt"
 
 
 
59
  os.makedirs(CHAT_DIR, exist_ok=True)
60
  os.makedirs(VOTE_DIR, exist_ok=True)
 
 
 
61
 
62
- # Persistent files - the grand tomes of chatter and votes! 📖✨
63
  CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
64
  QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
65
- IMAGE_VOTES_FILE = os.path.join(VOTE_DIR, "image_votes.md")
66
- HISTORY_FILE = os.path.join(VOTE_DIR, "vote_history.md")
67
 
68
- # Unicode digit conversion table - the magic map for emoji numbers! 🔢✨
69
- UNICODE_DIGITS = {
70
- 0: "0️⃣", 1: "1️⃣", 2: "2️⃣", 3: "3️⃣", 4: "4️⃣",
71
- 5: "5️⃣", 6: "6️⃣", 7: "7️⃣", 8: "8️⃣", 9: "9️⃣"
72
- }
73
 
74
- # Unicode font examples - the stylish wardrobe of text flair! 👗📝
75
  UNICODE_FONTS = [
76
  ("Normal", lambda x: x),
77
  ("Bold", lambda x: "".join(chr(ord(c) + 0x1D400 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D41A - 0x61) if 'a' <= c <= 'z' else c for c in x)),
78
  ("Italic", lambda x: "".join(chr(ord(c) + 0x1D434 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D44E - 0x61) if 'a' <= c <= 'z' else c for c in x)),
 
79
  ("Script", lambda x: "".join(chr(ord(c) + 0x1D49C - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D4B6 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
 
80
  ("Fraktur", lambda x: "".join(chr(ord(c) + 0x1D504 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D51E - 0x61) if 'a' <= c <= 'z' else c for c in x)),
 
81
  ("Double Struck", lambda x: "".join(chr(ord(c) + 0x1D538 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D552 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
82
  ("Sans Serif", lambda x: "".join(chr(ord(c) + 0x1D5A0 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D5BA - 0x61) if 'a' <= c <= 'z' else c for c in x)),
83
- ("Sans Bold", lambda x: "".join(chr(ord(c) + 0x1D5D4 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D5EE - 0x61) if 'a' <= c <= 'z' else c for c in x)),
84
- ("Mono", lambda x: "".join(chr(ord(c) + 0x1D670 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D68A - 0x61) if 'a' <= c <= 'z' else c for c in x)),
85
- ("Circle", lambda x: "".join(chr(ord(c) + 0x24B6 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x24D0 - 0x61) if 'a' <= c <= 'z' else c for c in x))
 
 
 
 
 
 
86
  ]
87
 
88
- # Global WebSocket server flag
89
- server_running = False
90
- server_task = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- # Node name - the app’s codename generator, sneaky and slick! 🕵️‍♂️💾
93
  def get_node_name():
94
- """🎲 Spins the wheel of fate to name our node - a random alias or user pick! 🏷️"""
95
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
96
- parser.add_argument('--node-name', type=str, default=None, help='Name for this chat node')
97
- parser.add_argument('--port', type=int, default=8501, help='Port to run the Streamlit interface on')
98
  args = parser.parse_args()
 
 
99
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
100
 
101
- # Chat saver - the scribe etching epic messages into the eternal scroll! 🖋️📜
102
- def save_chat_entry(username, message):
103
- """🖌️ Carves a chat line into the grand Markdown tome - history in the making! 🏛️"""
104
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
105
- entry = f"[{timestamp}] {username}: {message}"
106
- try:
107
- with open(CHAT_FILE, 'a') as f:
108
- f.write(f"{entry}\n")
109
- return True
110
- except Exception as e:
111
- print(f"Oops! Failed to save chat: {e}")
112
- return False
113
-
114
- # Chat loader - the archaeologist unearthing the chat saga! ⛏️📚
115
- def load_chat():
116
- """🔍 Digs up the chat treasure from the filesystem - tales of old and new! 💰"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  if not os.path.exists(CHAT_FILE):
118
- with open(CHAT_FILE, 'w') as f:
119
- f.write(f"# {START_ROOM} Chat\n\nWelcome to the cosmic hub - start chatting! 🎤\n")
120
- try:
121
- with open(CHAT_FILE, 'r') as f:
122
- content = f.read()
123
- lines = content.strip().split('\n')
124
- numbered_content = "\n".join(f"{i+1}. {line}" for i, line in enumerate(lines) if line.strip())
125
- return numbered_content
126
- except Exception as e:
127
- print(f"Chat load hiccup: {e}")
128
- return f"# Error loading {START_ROOM} chat\nSomething went wonky! 😵"
129
-
130
- # User list grabber - the social butterfly spotting all the chatters! 🦋👥
131
- def get_user_list(chat_content):
132
- """👀 Peeks at the chat to spot all the cool cats talking - who’s in the club? 🎸"""
133
  users = set()
134
  for line in chat_content.split('\n'):
135
  if line.strip() and ': ' in line:
@@ -137,129 +243,107 @@ def get_user_list(chat_content):
137
  users.add(user)
138
  return sorted(list(users))
139
 
140
- # Message suggestion loader - the oracle of past chats! 🔮💬
141
- def get_message_suggestions(chat_content, prefix):
142
- """🔮 Pulls past messages from the cosmic archives for autocomplete magic! ✨"""
 
 
 
 
 
 
 
143
  lines = chat_content.split('\n')
144
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
145
- return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5] # Top 5 matches
146
-
147
- # Quote loader - the sage pulling wisdom from the ages! 📜🧙
148
- def load_quotes(source="famous"):
149
- """📚 Grabs a stack of wise words from famous folks or custom quips! 🗣️"""
150
- famous_quotes = [
151
- "The true sign of intelligence is not knowledge but imagination. – Albert Einstein",
152
- "I have not failed. I've just found 10,000 ways that won't work. – Thomas Edison",
153
- "Innovation distinguishes between a leader and a follower. – Steve Jobs",
154
- "Research is what I'm doing when I don't know what I'm doing. – Wernher von Braun",
155
- "The only way to discover the limits of the possible is to go beyond them into the impossible. – Arthur C. Clarke",
156
- "Success is a science; if you have the conditions, you get the result. – Oscar Wilde",
157
- "An expert is a person who has made all the mistakes that can be made in a very narrow field. – Niels Bohr",
158
- "The important thing is to not stop questioning. Curiosity has its own reason for existing. – Albert Einstein",
159
- "The best way to predict the future is to invent it. – Alan Kay",
160
- "If I have seen further it is by standing on the shoulders of Giants. – Isaac Newton",
161
- "Logic will get you from A to B. Imagination will take you everywhere. – Albert Einstein",
162
- "Imagination is more important than knowledge. Knowledge is limited. Imagination encircles the world. – Albert Einstein",
163
- "Science is a way of thinking much more than it is a body of knowledge. – Carl Sagan",
164
- "We cannot solve our problems with the same thinking we used when we created them. – Albert Einstein",
165
- "The true method of knowledge is experiment. – William Blake",
166
- "The scientist is not a person who gives the right answers, he's one who asks the right questions. – Claude Levi-Strauss",
167
- "It's kind of fun to do the impossible. – Walt Disney",
168
- "Any sufficiently advanced technology is indistinguishable from magic. – Arthur C. Clarke",
169
- "Creativity is intelligence having fun. – Albert Einstein",
170
- "To invent, you need a good imagination and a pile of junk. – Thomas Edison"
171
- ]
172
- custom_quotes = [
173
- "Every age unfolds a new lesson. Life's chapters evolve, each teaching us anew.",
174
- "From infancy to twilight, our journey is painted in growth. Every stage shines with its own wisdom.",
175
- "Love is the universal language, transcending boundaries and touching souls.",
176
- "Through love, we find connection, unity, and the essence of existence."
177
- ]
178
- quotes = famous_quotes if source == "famous" else custom_quotes
179
- return quotes if quotes else ["No quotes available - check back later! 📭"]
180
-
181
- # Vote saver - the tally keeper counting thumbs up! 👍📊
182
- def save_vote(file, item, user_hash):
183
- """✍️ Tallies a vote in the grand ledger - your opinion matters! 🗳️"""
184
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
185
  entry = f"[{timestamp}] {user_hash} voted for {item}"
186
- try:
187
- with open(file, 'a') as f:
188
- f.write(f"{entry}\n")
189
- with open(HISTORY_FILE, 'a') as f:
190
- f.write(f"- {timestamp} - User {user_hash} voted for {item}\n")
191
- return True
192
- except Exception as e:
193
- print(f"Vote save flop: {e}")
194
- return False
195
-
196
- # Vote loader - the scorekeeper tallying the crowd’s cheers! 🎉🏅
197
- def load_votes(file):
198
- """📈 Counts the votes from the ledger - who’s winning the popularity contest? 🏆"""
199
  if not os.path.exists(file):
200
- with open(file, 'w') as f:
201
- f.write("# Vote Tally\n\nNo votes yet - get clicking! 🖱️\n")
202
- try:
203
- with open(file, 'r') as f:
204
- lines = f.read().strip().split('\n')
205
- votes = {}
206
- for line in lines[2:]: # Skip header
207
- if line.strip() and 'voted for' in line:
208
- item = line.split('voted for ')[1]
 
 
 
209
  votes[item] = votes.get(item, 0) + 1
210
- return votes
211
- except Exception as e:
212
- print(f"Vote load oopsie: {e}")
213
- return {}
214
-
215
- # User hash generator - the secret agent giving you a cool code! 🕵️‍♂️🔑
216
- def generate_user_hash():
217
- """🕵️ Crafts a snazzy 8-digit ID badge - you’re in the club now! 🎟️"""
218
  if 'user_hash' not in st.session_state:
219
- session_id = str(random.getrandbits(128))
220
- hash_object = hashlib.md5(session_id.encode())
221
- st.session_state['user_hash'] = hash_object.hexdigest()[:8]
222
- return st.session_state['user_hash']
223
-
224
- # Username persistence - save and load across refreshes! 💾🔄
225
- def save_username(username):
226
- """💾 Stashes your cosmic alias before the refresh void claims it! 🌌"""
227
- try:
228
- with open(STATE_FILE, 'w') as f:
229
- f.write(username)
230
- except Exception as e:
231
- print(f"Failed to save username: {e}")
232
-
233
- def load_username():
234
- """🔄 Resurrects your cosmic alias from the refresh abyss! 🌠"""
235
- if os.path.exists(STATE_FILE):
236
- try:
237
- with open(STATE_FILE, 'r') as f:
238
- return f.read().strip()
239
- except Exception as e:
240
- print(f"Failed to load username: {e}")
241
- return None
242
-
243
- # Image paste handler - the cosmic courier delivering pasted pics! 📸🚀
244
- def save_pasted_image(image_data):
245
- """📸 Saves a pasted image to the root vault with a cosmic name! 🌠"""
246
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
247
- filename = f"paste_{timestamp}.png"
248
- filepath = os.path.join('./', filename) # Root directory
249
  try:
250
- if ',' in image_data:
251
- image_data = image_data.split(',')[1]
252
- img_bytes = base64.b64decode(image_data)
253
- img = Image.open(io.BytesIO(img_bytes))
254
- img.save(filepath, "PNG")
255
- return filename
256
- except Exception as e:
257
- print(f"Failed to save pasted image: {e}")
258
  return None
259
 
260
- # Media HTML generators - IVA’s multimedia magic! 🎥🎶🖼️
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  def get_video_html(video_path, width="100%"):
262
- """🎬 Rolls out the red carpet for videos - autoplay and loop like a star! 🌟"""
263
  video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
264
  return f'''
265
  <video width="{width}" controls autoplay muted loop>
@@ -268,320 +352,474 @@ def get_video_html(video_path, width="100%"):
268
  </video>
269
  '''
270
 
271
- def get_audio_html(audio_path, width="100%"):
272
- """🎶 Drops a beat with audio - your personal DJ in action! 🎧"""
273
- audio_url = f"data:audio/mpeg;base64,{base64.b64encode(open(audio_path, 'rb').read()).decode()}"
274
- return f'''
275
- <audio controls style="width: {width};">
276
- <source src="{audio_url}" type="audio/mpeg">
277
- Your browser does not support the audio element.
278
- </audio>
279
- '''
280
-
281
- active_connections = {}
282
 
283
- # WebSocket handler - the bouncer at the Sector rave, keeping it hopping! 🎉🚪
284
  async def websocket_handler(websocket, path):
285
- """🎧 Guards the cosmic gate, welcoming all to Sector and booting crashers! 🚨"""
 
286
  try:
287
  client_id = str(uuid.uuid4())
288
- room_id = "chat" # Single room "Sector 🌌"
289
- active_connections.setdefault(room_id, {})[client_id] = websocket
290
- print(f"Client {client_id} joined the Sector party!")
291
  username = st.session_state.get('username', random.choice(list(FUN_USERNAMES.keys())))
292
- save_chat_entry("System 🌟", f"{username} has joined {START_ROOM}!")
293
-
294
  async for message in websocket:
295
- try:
296
- parts = message.split('|', 1)
297
- if len(parts) == 2:
298
- username, content = parts
299
- save_chat_entry(username, content)
300
- await broadcast_message(f"{username}|{content}", room_id)
301
- except Exception as e:
302
- print(f"Message mishap: {e}")
303
- await websocket.send(f"ERROR|Oops, bad message format! 😬")
304
  except websockets.ConnectionClosed:
305
- print(f"Client {client_id} bailed from Sector!")
306
- save_chat_entry("System 🌟", f"{username} has left {START_ROOM}!")
307
  finally:
308
- if room_id in active_connections and client_id in active_connections[room_id]:
309
- del active_connections[room_id][client_id]
310
- if not active_connections[room_id]:
311
- del active_connections[room_id]
312
 
313
- # Broadcaster - the megaphone blasting Sector vibes to all! 📣🎶
314
  async def broadcast_message(message, room_id):
315
- """📢 Shouts the latest Sector beat to every cosmic dancer - hear it loud! 🎵"""
316
- if room_id in active_connections:
 
317
  disconnected = []
318
- for client_id, ws in active_connections[room_id].items():
319
  try:
320
  await ws.send(message)
321
  except websockets.ConnectionClosed:
322
  disconnected.append(client_id)
323
  for client_id in disconnected:
324
- del active_connections[room_id][client_id]
325
 
326
- # WebSocket server runner - the DJ spinning up the Sector tunes once! 🎧🔥
327
  async def run_websocket_server():
328
- """🌐 Cranks up the WebSocket jukebox once, keeping Sector rocking! 🎸"""
329
- global server_running, server_task
330
- if not server_running:
331
  server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
332
- print(f"WebSocket server jamming on ws://0.0.0.0:8765")
333
- server_running = True
334
  await server.wait_closed()
335
 
336
- # Chat interface maker - the stage builder for our Sector extravaganza! 🎭🏟️
337
- def create_streamlit_interface(initial_username):
338
- """🖌️ Sets up the Sector stage with pulsing timers, voting, and IVA media flair! 🌟"""
339
- # Custom CSS and JS for chat box, timer, and paste detection
340
- st.markdown("""
341
- <style>
342
- .chat-box {
343
- font-family: monospace;
344
- background: #1e1e1e;
345
- color: #d4d4d4;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  padding: 10px;
347
- border-radius: 5px;
348
- height: 300px;
349
- overflow-y: auto;
350
  }
351
- .timer {
352
- font-size: 24px;
353
- color: #ffcc00;
354
- text-align: center;
355
- animation: pulse 1s infinite;
 
 
 
 
356
  }
357
- @keyframes pulse {
358
- 0% { transform: scale(1); }
359
- 50% { transform: scale(1.1); }
360
- 100% { transform: scale(1); }
361
  }
362
- </style>
363
- <script>
364
- document.addEventListener('paste', function(e) {
365
- const items = (e.clipboardData || window.clipboardData).items;
366
- for (let i = 0; i < items.length; i++) {
367
- if (items[i].type.indexOf('image') !== -1) {
368
- const blob = items[i].getAsFile();
369
- const reader = new FileReader();
370
- reader.onload = function(event) {
371
- const imageData = event.target.result;
372
- sessionStorage.setItem('pastedImage', imageData);
373
- document.getElementById('message_input').value = 'PastedImage:' + blob.name;
374
- document.getElementById('send_button').click();
375
- };
376
- reader.readAsDataURL(blob);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  }
378
- }
379
- });
380
- document.getElementById('message_input')?.addEventListener('keypress', function(e) {
381
- if (e.key === 'Enter') {
382
- document.getElementById('send_button').click();
383
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  });
385
- </script>
386
- """, unsafe_allow_html=True)
387
-
388
- # Title and intro
389
- st.title(f"{Site_Name}")
390
- st.markdown(f"Welcome to {START_ROOM} - chat, vote, paste images, and enjoy IVA’s media magic! 🎉")
391
-
392
- # Load or set username
393
- saved_username = load_username()
394
- if saved_username and saved_username in FUN_USERNAMES:
395
- initial_username = saved_username
396
- if 'username' not in st.session_state:
397
- st.session_state.username = initial_username
398
- save_chat_entry("System 🌟", f"{initial_username} has joined {START_ROOM}!")
399
- save_username(st.session_state.username) # Persist before any refresh
400
-
401
- # Session state for refresh rate and quote index
402
- if 'refresh_rate' not in st.session_state:
403
- st.session_state.refresh_rate = 5
404
- if 'timer_start' not in st.session_state:
405
- st.session_state.timer_start = time.time() # Autostart timer
406
- if 'quote_index' not in st.session_state:
407
- quotes = load_quotes("famous")
408
- st.session_state.quote_index = random.randint(0, max(0, len(quotes) - 1)) if quotes else 0
409
- if 'quote_source' not in st.session_state:
410
- st.session_state.quote_source = "famous"
411
- if 'pasted_image' not in st.session_state:
412
- st.session_state.pasted_image = None
413
-
414
- # Chat section
415
- st.subheader(f"{START_ROOM} Chat 💬")
416
- chat_content = load_chat()
417
- chat_lines = chat_content.split('\n')
418
- for i, line in enumerate(chat_lines):
419
- if line.strip() and ': ' in line:
420
- col1, col2 = st.columns([5, 1])
421
- with col1:
422
- st.markdown(line)
423
- with col2:
424
- if st.button(f"👍", key=f"chat_vote_{i}"):
425
- user_hash = generate_user_hash()
426
- save_vote(QUOTE_VOTES_FILE, line, user_hash)
427
- st.session_state.timer_start = time.time()
428
- save_username(st.session_state.username)
429
- st.rerun()
430
-
431
- user_list = get_user_list(chat_content)
432
- new_username = st.selectbox("Switch Star", user_list + [st.session_state.username], index=len(user_list))
433
- if new_username != st.session_state.username:
434
- save_chat_entry("System 🌟", f"{st.session_state.username} switched to {new_username} in {START_ROOM}!")
435
- st.session_state.username = new_username
436
- st.session_state.timer_start = time.time()
437
- save_username(st.session_state.username)
438
-
439
- # Message input with search engine and paste handling
440
- agent_icon = st.session_state.username.split()[-1] # Extract emoji
441
- agent_label = FUN_USERNAMES.get(st.session_state.username, "Chatty Brat")
442
- message = st.text_input(f"Message Mate {agent_icon} - {agent_label} 🔍📸", key="message_input", placeholder="Type or paste here...")
443
- suggestions = get_message_suggestions(chat_content, message) if message else []
444
- if suggestions:
445
- suggestion = st.selectbox("Chat Snap", [""] + suggestions, index=0)
446
- if suggestion:
447
- message = suggestion
448
- st.session_state.message_input = suggestion
449
-
450
- col_send, _ = st.columns([1, 5])
451
- with col_send:
452
- send_button = st.button("Send Bend 🚀", key="send_button")
453
- if send_button and message.strip():
454
- if message.startswith("PastedImage:"):
455
- # Handle pasted image
456
- image_data = st.session_state.get('pasted_image')
457
- if image_data:
458
- filename = save_pasted_image(image_data)
459
- if filename:
460
- save_chat_entry(st.session_state.username, f"Pasted image: {filename}")
461
- st.session_state.pasted_image = None
462
- else:
463
- save_chat_entry(st.session_state.username, message)
464
- st.session_state.timer_start = time.time()
465
- save_username(st.session_state.username)
466
- st.rerun()
467
 
468
- # Quote section
469
- st.subheader("Quote Note 📝")
470
- quotes = load_quotes(st.session_state.quote_source)
471
- if quotes:
472
- st.session_state.quote_index = st.session_state.quote_index % len(quotes)
473
- quote = quotes[st.session_state.quote_index]
474
- col1, col2 = st.columns([5, 1])
475
- with col1:
476
- st.markdown(quote)
477
- with col2:
478
- if st.button("👍 Vote Float", key="quote_vote"):
479
- user_hash = generate_user_hash()
480
- save_vote(QUOTE_VOTES_FILE, quote, user_hash)
481
- st.session_state.timer_start = time.time()
482
- save_username(st.session_state.username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  st.rerun()
484
- if time.time() - st.session_state.timer_start > 10: # 10s quote refresh
485
- st.session_state.quote_index = (st.session_state.quote_index + 1) % len(quotes)
486
- st.session_state.quote_source = "custom" if st.session_state.quote_source == "famous" else "famous"
487
- quotes = load_quotes(st.session_state.quote_source)
488
- st.session_state.quote_index = st.session_state.quote_index % len(quotes) if quotes else 0
489
- st.session_state.timer_start = time.time()
490
- save_username(st.session_state.username)
 
491
  st.rerun()
492
- else:
493
- st.markdown("No quotes available - check back later! 📭")
494
-
495
- # IVA Media Gallery - Root Directory
496
- st.subheader("IVA Gallery 🎨🎶🎥")
497
- media_files = (
498
- glob.glob("./*.png") + glob.glob("./*.jpg") + glob.glob("./*.jpeg") +
499
- glob.glob("./*.mp3") + glob.glob("./*.wav") +
500
- glob.glob("./*.mp4")
501
- )
502
- if media_files:
503
- media_cols = st.slider("Gallery Rally", min_value=1, max_value=5, value=1)
504
- cols = st.columns(media_cols)
505
- for idx, media_file in enumerate(media_files):
506
- with cols[idx % media_cols]:
507
- if media_file.endswith(('.png', '.jpg', '.jpeg')):
508
- st.image(media_file, use_container_width=True)
509
- elif media_file.endswith(('.mp3', '.wav')):
510
- st.markdown(get_audio_html(media_file, width="100%"), unsafe_allow_html=True)
511
- elif media_file.endswith('.mp4'):
512
- st.markdown(get_video_html(media_file, width="100%"), unsafe_allow_html=True)
513
- if st.button(f"👍 Vote Tote {os.path.basename(media_file)}", key=f"media_vote_{idx}"):
514
- user_hash = generate_user_hash()
515
- save_vote(IMAGE_VOTES_FILE, media_file, user_hash)
516
- st.session_state.timer_start = time.time()
517
- save_username(st.session_state.username)
518
- st.rerun()
519
- else:
520
- st.error("No media files (images, audio, video) found in root directory!")
521
 
522
- # Refresh rate controls with pulsing timer
523
- st.subheader("Refresh Dash ⏳")
524
- refresh_rate = st.slider("Refresh Clash", min_value=1, max_value=300, value=st.session_state.refresh_rate, step=1)
525
- if refresh_rate != st.session_state.refresh_rate:
526
- st.session_state.refresh_rate = refresh_rate
527
- st.session_state.timer_start = time.time()
528
- save_username(st.session_state.username)
529
-
530
- col1, col2, col3 = st.columns(3)
531
- with col1:
532
- if st.button("🐇 Small Call (1s)"):
533
- st.session_state.refresh_rate = 1
534
- st.session_state.timer_start = time.time()
535
- save_username(st.session_state.username)
536
- with col2:
537
- if st.button("🐢 Medium Fling (5s)"):
538
- st.session_state.refresh_rate = 5
539
- st.session_state.timer_start = time.time()
540
- save_username(st.session_state.username)
541
- with col3:
542
- if st.button("🐘 Large Surge (5m)"):
543
- st.session_state.refresh_rate = 300
544
- st.session_state.timer_start = time.time()
545
- save_username(st.session_state.username)
546
-
547
- # Pulsing countdown timer with emoji digits and random Unicode font
548
- timer_placeholder = st.empty()
549
- start_time = st.session_state.timer_start
550
- for i in range(st.session_state.refresh_rate, -1, -1):
551
- font_name, font_func = random.choice(UNICODE_FONTS)
552
- countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
553
- timer_emoji = "⏳" if i % 2 == 0 else "💓" # Pulse effect
554
- timer_placeholder.markdown(f"<p class='timer'>{timer_emoji} {font_func('Next refresh in:')} {countdown_str} {font_func('seconds')}</p>", unsafe_allow_html=True)
555
- time.sleep(1) # Pulse every second
556
- st.session_state.timer_start = time.time()
557
- st.session_state.last_refresh = time.time()
558
- save_username(st.session_state.username)
559
- st.rerun()
560
-
561
- # Sidebar vote stats
562
- st.sidebar.subheader("Vote Loads")
563
- chat_votes = load_votes(QUOTE_VOTES_FILE)
564
- image_votes = load_votes(IMAGE_VOTES_FILE)
565
- for item, count in chat_votes.items():
566
- st.sidebar.write(f"{item}: {count} votes")
567
- for image, count in image_votes.items():
568
- st.sidebar.write(f"{image}: {count} votes")
569
-
570
- # Main event - the ringmaster kicking off the Sector circus! 🎪🤡
571
- async def main():
572
- """🎤 Drops the mic and starts the Sector party - it’s showtime, folks! 🎉"""
573
- global NODE_NAME, server_task
574
- NODE_NAME, port = get_node_name()
575
 
576
- # Start WebSocket server only once as a background task
577
- if server_task is None:
578
- server_task = asyncio.create_task(run_websocket_server())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
 
580
- query_params = st.query_params if hasattr(st, 'query_params') else {}
581
- initial_username = query_params.get("username", random.choice(list(FUN_USERNAMES.keys()))) if query_params else random.choice(list(FUN_USERNAMES.keys()))
582
- print(f"Welcoming {initial_username} to the Sector bash!")
 
 
 
 
 
 
 
583
 
584
- create_streamlit_interface(initial_username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
585
 
586
  if __name__ == "__main__":
587
- asyncio.run(main())
 
10
  import hashlib
11
  from PIL import Image
12
  import glob
 
 
13
  import base64
14
  import io
15
+ import streamlit.components.v1 as components
16
+ import edge_tts
17
+ from audio_recorder_streamlit import audio_recorder
18
+ import nest_asyncio
19
+ import re
20
+ from streamlit_paste_button import paste_image_button
21
+ import pytz
22
+ import shutil
23
+
24
+ # Patch for nested async - sneaky fix! 🐍✨
25
+ nest_asyncio.apply()
26
 
27
+ # Static config - constants rule! 📏👑
 
 
28
  icons = '🤖🧠🔬📝'
29
  START_ROOM = "Sector 🌌"
30
 
31
+ # Page setup - dressing up the window! 🖼️🎀
32
  st.set_page_config(
33
+ page_title="🤖🧠MMO Chat Brain📝🔬",
34
  page_icon=icons,
35
  layout="wide",
36
  initial_sidebar_state="auto"
37
  )
38
 
39
+ # Funky usernames with corresponding Edge TTS voices
40
  FUN_USERNAMES = {
41
+ "CosmicJester 🌌": "en-US-AriaNeural",
42
+ "PixelPanda 🐼": "en-US-JennyNeural",
43
+ "QuantumQuack 🦆": "en-GB-SoniaNeural",
44
+ "StellarSquirrel 🐿️": "en-AU-NatashaNeural",
45
+ "GizmoGuru ⚙️": "en-CA-ClaraNeural",
46
+ "NebulaNinja 🌠": "en-US-GuyNeural",
47
+ "ByteBuster 💾": "en-GB-RyanNeural",
48
+ "GalacticGopher 🌍": "en-AU-WilliamNeural",
49
+ "RocketRaccoon 🚀": "en-CA-LiamNeural",
50
+ "EchoElf 🧝": "en-US-AnaNeural",
51
+ "PhantomFox 🦊": "en-US-BrandonNeural",
52
+ "WittyWizard 🧙": "en-GB-ThomasNeural",
53
+ "LunarLlama 🌙": "en-AU-FreyaNeural",
54
+ "SolarSloth ☀️": "en-CA-LindaNeural",
55
+ "AstroAlpaca 🦙": "en-US-ChristopherNeural",
56
+ "CyberCoyote 🐺": "en-GB-ElliotNeural",
57
+ "MysticMoose 🦌": "en-AU-JamesNeural",
58
+ "GlitchGnome 🧚": "en-CA-EthanNeural",
59
+ "VortexViper 🐍": "en-US-AmberNeural",
60
+ "ChronoChimp 🐒": "en-GB-LibbyNeural"
61
  }
62
 
63
+ # Folders galore - organizing chaos! 📂🌀
64
  CHAT_DIR = "chat_logs"
65
  VOTE_DIR = "vote_logs"
66
  STATE_FILE = "user_state.txt"
67
+ AUDIO_DIR = "audio_logs"
68
+ HISTORY_DIR = "history_logs"
69
+ MEDIA_DIR = "media_files"
70
  os.makedirs(CHAT_DIR, exist_ok=True)
71
  os.makedirs(VOTE_DIR, exist_ok=True)
72
+ os.makedirs(AUDIO_DIR, exist_ok=True)
73
+ os.makedirs(HISTORY_DIR, exist_ok=True)
74
+ os.makedirs(MEDIA_DIR, exist_ok=True)
75
 
 
76
  CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
77
  QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
78
+ MEDIA_VOTES_FILE = os.path.join(VOTE_DIR, "media_votes.md")
79
+ HISTORY_FILE = os.path.join(HISTORY_DIR, "chat_history.md")
80
 
81
+ # Fancy digits - numbers got style! 🔢💃
82
+ UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
 
 
 
83
 
84
+ # Massive font collection - typography bonanza! 🖋️🎨
85
  UNICODE_FONTS = [
86
  ("Normal", lambda x: x),
87
  ("Bold", lambda x: "".join(chr(ord(c) + 0x1D400 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D41A - 0x61) if 'a' <= c <= 'z' else c for c in x)),
88
  ("Italic", lambda x: "".join(chr(ord(c) + 0x1D434 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D44E - 0x61) if 'a' <= c <= 'z' else c for c in x)),
89
+ ("Bold Italic", lambda x: "".join(chr(ord(c) + 0x1D468 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D482 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
90
  ("Script", lambda x: "".join(chr(ord(c) + 0x1D49C - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D4B6 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
91
+ ("Bold Script", lambda x: "".join(chr(ord(c) + 0x1D4D0 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D4EA - 0x61) if 'a' <= c <= 'z' else c for c in x)),
92
  ("Fraktur", lambda x: "".join(chr(ord(c) + 0x1D504 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D51E - 0x61) if 'a' <= c <= 'z' else c for c in x)),
93
+ ("Bold Fraktur", lambda x: "".join(chr(ord(c) + 0x1D56C - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D586 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
94
  ("Double Struck", lambda x: "".join(chr(ord(c) + 0x1D538 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D552 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
95
  ("Sans Serif", lambda x: "".join(chr(ord(c) + 0x1D5A0 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D5BA - 0x61) if 'a' <= c <= 'z' else c for c in x)),
96
+ ("Sans Serif Bold", lambda x: "".join(chr(ord(c) + 0x1D5D4 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D5EE - 0x61) if 'a' <= c <= 'z' else c for c in x)),
97
+ ("Sans Serif Italic", lambda x: "".join(chr(ord(c) + 0x1D608 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D622 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
98
+ ("Sans Serif Bold Italic", lambda x: "".join(chr(ord(c) + 0x1D63C - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D656 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
99
+ ("Monospace", lambda x: "".join(chr(ord(c) + 0x1D670 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D68A - 0x61) if 'a' <= c <= 'z' else c for c in x)),
100
+ ("Circled", lambda x: "".join(chr(ord(c) - 0x41 + 0x24B6) if 'A' <= c <= 'Z' else chr(ord(c) - 0x61 + 0x24D0) if 'a' <= c <= 'z' else c for c in x)),
101
+ ("Squared", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F130) if 'A' <= c <= 'Z' else c for c in x)),
102
+ ("Negative Circled", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F150) if 'A' <= c <= 'Z' else c for c in x)),
103
+ ("Negative Squared", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F170) if 'A' <= c <= 'Z' else c for c in x)),
104
+ ("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
105
  ]
106
 
107
+ # Global state - keeping tabs! 🌍📋
108
+ if 'server_running' not in st.session_state:
109
+ st.session_state.server_running = False
110
+ if 'server_task' not in st.session_state:
111
+ st.session_state.server_task = None
112
+ if 'active_connections' not in st.session_state:
113
+ st.session_state.active_connections = {}
114
+ if 'media_notifications' not in st.session_state:
115
+ st.session_state.media_notifications = []
116
+ if 'last_chat_update' not in st.session_state:
117
+ st.session_state.last_chat_update = 0
118
+ if 'displayed_chat_lines' not in st.session_state:
119
+ st.session_state.displayed_chat_lines = []
120
+ if 'old_val' not in st.session_state:
121
+ st.session_state.old_val = ""
122
+ if 'last_query' not in st.session_state:
123
+ st.session_state.last_query = ""
124
+ if 'message_text' not in st.session_state:
125
+ st.session_state.message_text = ""
126
+ if 'audio_cache' not in st.session_state:
127
+ st.session_state.audio_cache = {}
128
+ if 'pasted_image_data' not in st.session_state:
129
+ st.session_state.pasted_image_data = None
130
+ if 'quote_line' not in st.session_state:
131
+ st.session_state.quote_line = None
132
+ if 'refresh_rate' not in st.session_state:
133
+ st.session_state.refresh_rate = 5
134
+ if 'base64_cache' not in st.session_state:
135
+ st.session_state.base64_cache = {}
136
+ if 'transcript_history' not in st.session_state:
137
+ st.session_state.transcript_history = []
138
+ if 'last_transcript' not in st.session_state:
139
+ st.session_state.last_transcript = ""
140
+ if 'image_hashes' not in st.session_state:
141
+ st.session_state.image_hashes = set()
142
+ if 'gallery_columns' not in st.session_state:
143
+ st.session_state.gallery_columns = 1 # Default gallery tiles
144
+
145
+ # Timestamp wizardry - clock ticks with flair! ⏰🎩
146
+ def format_timestamp_prefix(username):
147
+ central = pytz.timezone('US/Central')
148
+ now = datetime.now(central)
149
+ return f"{now.strftime('%I-%M-%p-ct-%m-%d-%Y')}-by-{username}"
150
+
151
+ # Compute image hash from binary data
152
+ def compute_image_hash(image_data):
153
+ if isinstance(image_data, Image.Image):
154
+ img_byte_arr = io.BytesIO()
155
+ image_data.save(img_byte_arr, format='PNG')
156
+ img_bytes = img_byte_arr.getvalue()
157
+ else:
158
+ img_bytes = image_data
159
+ return hashlib.md5(img_bytes).hexdigest()[:8]
160
 
161
+ # Node naming - christening the beast! 🌐🍼
162
  def get_node_name():
 
163
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
164
+ parser.add_argument('--node-name', type=str, default=None)
165
+ parser.add_argument('--port', type=int, default=8501)
166
  args = parser.parse_args()
167
+ username = st.session_state.get('username', 'System 🌟')
168
+ log_action(username, "🌐🍼 - Node naming - christening the beast!")
169
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
170
 
171
+ # Action logger - spying on deeds! 🕵️📜
172
+ def log_action(username, action):
173
+ if 'action_log' not in st.session_state:
174
+ st.session_state.action_log = {}
175
+ user_log = st.session_state.action_log.setdefault(username, {})
176
+ current_time = time.time()
177
+ user_log = {k: v for k, v in user_log.items() if current_time - v < 10}
178
+ st.session_state.action_log[username] = user_log
179
+ if action not in user_log:
180
+ central = pytz.timezone('US/Central')
181
+ with open(HISTORY_FILE, 'a') as f:
182
+ f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
183
+ user_log[action] = current_time
184
+
185
+ # Clean text - strip the fancy stuff! 🧹📝
186
+ def clean_text_for_tts(text):
187
+ cleaned = re.sub(r'[#*!\[\]]+', '', text)
188
+ cleaned = ' '.join(cleaned.split())
189
+ return cleaned[:200] if cleaned else "No text to speak"
190
+
191
+ # Chat saver - words locked tight! 💬🔒
192
+ async def save_chat_entry(username, message, is_markdown=False):
193
+ await asyncio.to_thread(log_action, username, "💬🔒 - Chat saver - words locked tight!")
194
+ central = pytz.timezone('US/Central')
195
+ timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
196
+ if is_markdown:
197
+ entry = f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
198
+ else:
199
+ entry = f"[{timestamp}] {username}: {message}"
200
+ await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"{entry}\n"))
201
+ voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
202
+ cleaned_message = clean_text_for_tts(message)
203
+ audio_file = await async_edge_tts_generate(cleaned_message, voice)
204
+ if audio_file:
205
+ with open(HISTORY_FILE, 'a') as f:
206
+ f.write(f"[{timestamp}] {username} ({voice}): Audio generated - {audio_file}\n")
207
+ await broadcast_message(f"{username}|{message}", "chat")
208
+ st.session_state.last_chat_update = time.time()
209
+ return audio_file
210
+
211
+ # Save chat history with image
212
+ async def save_chat_history_with_image(username, image_path):
213
+ central = pytz.timezone('US/Central')
214
+ timestamp = datetime.now(central).strftime("%Y-%m-%d_%H-%M-%S")
215
+ history_filename = f"chat_history_{timestamp}-by-{username}.md"
216
+ history_filepath = os.path.join(HISTORY_DIR, history_filename)
217
+ chat_content = await load_chat()
218
+ voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
219
+ with open(history_filepath, 'w') as f:
220
+ f.write(f"# Chat History at {timestamp} by {username} (Voice: {voice})\n\n")
221
+ f.write(f"## Image Shared: {os.path.basename(image_path)}\n")
222
+ f.write(chat_content)
223
+ return history_filepath
224
+
225
+ # Chat loader - history unleashed! 📜🚀
226
+ async def load_chat():
227
+ username = st.session_state.get('username', 'System 🌟')
228
+ await asyncio.to_thread(log_action, username, "📜🚀 - Chat loader - history unleashed!")
229
  if not os.path.exists(CHAT_FILE):
230
+ await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"# {START_ROOM} Chat\n\nWelcome to the cosmic hub - start chatting! 🎤\n"))
231
+ with open(CHAT_FILE, 'r') as f:
232
+ content = await asyncio.to_thread(f.read)
233
+ return content
234
+
235
+ # User lister - who’s in the gang! 👥🎉
236
+ async def get_user_list(chat_content):
237
+ username = st.session_state.get('username', 'System 🌟')
238
+ await asyncio.to_thread(log_action, username, "👥🎉 - User lister - who’s in the gang!")
 
 
 
 
 
 
239
  users = set()
240
  for line in chat_content.split('\n'):
241
  if line.strip() and ': ' in line:
 
243
  users.add(user)
244
  return sorted(list(users))
245
 
246
+ # Join checker - been here before? 🚪🔍
247
+ async def has_joined_before(client_id, chat_content):
248
+ username = st.session_state.get('username', 'System 🌟')
249
+ await asyncio.to_thread(log_action, username, "🚪🔍 - Join checker - been here before?")
250
+ return any(f"Client-{client_id}" in line for line in chat_content.split('\n'))
251
+
252
+ # Suggestion maker - old quips resurface! 💡📝
253
+ async def get_message_suggestions(chat_content, prefix):
254
+ username = st.session_state.get('username', 'System 🌟')
255
+ await asyncio.to_thread(log_action, username, "💡📝 - Suggestion maker - old quips resurface!")
256
  lines = chat_content.split('\n')
257
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
258
+ return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
259
+
260
+ # Vote saver - cheers recorded! 👍📊
261
+ async def save_vote(file, item, user_hash, username, comment=""):
262
+ await asyncio.to_thread(log_action, username, "👍📊 - Vote saver - cheers recorded!")
263
+ central = pytz.timezone('US/Central')
264
+ timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  entry = f"[{timestamp}] {user_hash} voted for {item}"
266
+ await asyncio.to_thread(lambda: open(file, 'a').write(f"{entry}\n"))
267
+ await asyncio.to_thread(lambda: open(HISTORY_FILE, "a").write(f"- {timestamp} - User {user_hash} voted for {item}\n"))
268
+ chat_message = f"{username} upvoted: \"{item}\""
269
+ if comment:
270
+ chat_message += f" - {comment}"
271
+ await save_chat_entry(username, chat_message)
272
+
273
+ # Vote counter - tallying the love! 🏆📈
274
+ async def load_votes(file):
275
+ username = st.session_state.get('username', 'System 🌟')
276
+ await asyncio.to_thread(log_action, username, "🏆📈 - Vote counter - tallying the love!")
 
 
277
  if not os.path.exists(file):
278
+ await asyncio.to_thread(lambda: open(file, 'w').write("# Vote Tally\n\nNo votes yet - get clicking! 🖱️\n"))
279
+ with open(file, 'r') as f:
280
+ content = await asyncio.to_thread(f.read)
281
+ lines = content.strip().split('\n')[2:]
282
+ votes = {}
283
+ user_votes = set()
284
+ for line in lines:
285
+ if line.strip() and 'voted for' in line:
286
+ user_hash = line.split('] ')[1].split(' voted for ')[0]
287
+ item = line.split('voted for ')[1]
288
+ vote_key = f"{user_hash}-{item}"
289
+ if vote_key not in user_votes:
290
  votes[item] = votes.get(item, 0) + 1
291
+ user_votes.add(vote_key)
292
+ return votes
293
+
294
+ # Hash generator - secret codes ahoy! 🔑🕵️
295
+ async def generate_user_hash():
296
+ username = st.session_state.get('username', 'System 🌟')
297
+ await asyncio.to_thread(log_action, username, "🔑🕵️ - Hash generator - secret codes ahoy!")
 
298
  if 'user_hash' not in st.session_state:
299
+ st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
300
+ return st.session_state.user_hash
301
+
302
+ # Audio maker - voices come alive! 🎶🌟
303
+ async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
304
+ username = st.session_state.get('username', 'System 🌟')
305
+ await asyncio.to_thread(log_action, username, "🎶🌟 - Audio maker - voices come alive!")
306
+ timestamp = format_timestamp_prefix(username)
307
+ filename = f"{timestamp}.{file_format}"
308
+ filepath = os.path.join(AUDIO_DIR, filename)
309
+ communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  try:
311
+ await communicate.save(filepath)
312
+ return filepath if os.path.exists(filepath) else None
313
+ except edge_tts.exceptions.NoAudioReceived:
314
+ with open(HISTORY_FILE, 'a') as f:
315
+ central = pytz.timezone('US/Central')
316
+ f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: Audio failed - No audio received for '{text}'\n")
 
 
317
  return None
318
 
319
+ # Audio player - tunes blast off! 🔊🚀
320
+ def play_and_download_audio(file_path):
321
+ if file_path and os.path.exists(file_path):
322
+ st.audio(file_path)
323
+ if file_path not in st.session_state.base64_cache:
324
+ with open(file_path, "rb") as f:
325
+ b64 = base64.b64encode(f.read()).decode()
326
+ st.session_state.base64_cache[file_path] = b64
327
+ b64 = st.session_state.base64_cache[file_path]
328
+ dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}">🎵 Download {os.path.basename(file_path)}</a>'
329
+ st.markdown(dl_link, unsafe_allow_html=True)
330
+
331
+ # Image saver - pics preserved with naming! 📸💾
332
+ async def save_pasted_image(image, username):
333
+ await asyncio.to_thread(log_action, username, "📸💾 - Image saver - pics preserved!")
334
+ img_hash = compute_image_hash(image)
335
+ if img_hash in st.session_state.image_hashes:
336
+ return None
337
+ timestamp = format_timestamp_prefix(username)
338
+ filename = f"{timestamp}-{img_hash}.png"
339
+ filepath = os.path.join(MEDIA_DIR, filename)
340
+ await asyncio.to_thread(image.save, filepath, "PNG")
341
+ st.session_state.image_hashes.add(img_hash)
342
+ await save_chat_history_with_image(username, filepath)
343
+ return filepath
344
+
345
+ # Video renderer verbatim from your last version - movies roll with autoplay! 🎥🎬
346
  def get_video_html(video_path, width="100%"):
 
347
  video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
348
  return f'''
349
  <video width="{width}" controls autoplay muted loop>
 
352
  </video>
353
  '''
354
 
355
+ # Audio renderer - sounds soar! 🎶✈️
356
+ async def get_audio_html(audio_path, width="100%"):
357
+ username = st.session_state.get('username', 'System 🌟')
358
+ await asyncio.to_thread(log_action, username, "🎶✈️ - Audio renderer - sounds soar!")
359
+ audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, audio_path, 'rb').read()).decode()}"
360
+ return f'<audio controls style="width: {width};"><source src="{audio_url}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
 
 
 
 
 
361
 
362
+ # Websocket handler - chat links up! 🌐🔗
363
  async def websocket_handler(websocket, path):
364
+ username = st.session_state.get('username', 'System 🌟')
365
+ await asyncio.to_thread(log_action, username, "🌐🔗 - Websocket handler - chat links up!")
366
  try:
367
  client_id = str(uuid.uuid4())
368
+ room_id = "chat"
369
+ st.session_state.active_connections.setdefault(room_id, {})[client_id] = websocket
370
+ chat_content = await load_chat()
371
  username = st.session_state.get('username', random.choice(list(FUN_USERNAMES.keys())))
372
+ if not await has_joined_before(client_id, chat_content):
373
+ await save_chat_entry(f"Client-{client_id}", f"{username} has joined {START_ROOM}!")
374
  async for message in websocket:
375
+ parts = message.split('|', 1)
376
+ if len(parts) == 2:
377
+ username, content = parts
378
+ await save_chat_entry(username, content)
 
 
 
 
 
379
  except websockets.ConnectionClosed:
380
+ pass
 
381
  finally:
382
+ if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
383
+ del st.session_state.active_connections[room_id][client_id]
 
 
384
 
385
+ # Message broadcaster - words fly far! 📢✈️
386
  async def broadcast_message(message, room_id):
387
+ username = st.session_state.get('username', 'System 🌟')
388
+ await asyncio.to_thread(log_action, username, "📢✈️ - Message broadcaster - words fly far!")
389
+ if room_id in st.session_state.active_connections:
390
  disconnected = []
391
+ for client_id, ws in st.session_state.active_connections[room_id].items():
392
  try:
393
  await ws.send(message)
394
  except websockets.ConnectionClosed:
395
  disconnected.append(client_id)
396
  for client_id in disconnected:
397
+ del st.session_state.active_connections[room_id][client_id]
398
 
399
+ # Server starter - web spins up! 🖥️🌀
400
  async def run_websocket_server():
401
+ username = st.session_state.get('username', 'System 🌟')
402
+ await asyncio.to_thread(log_action, username, "🖥️🌀 - Server starter - web spins up!")
403
+ if not st.session_state.server_running:
404
  server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
405
+ st.session_state.server_running = True
 
406
  await server.wait_closed()
407
 
408
+ # Voice processor - speech to text! 🎤📝
409
+ async def process_voice_input(audio_bytes):
410
+ username = st.session_state.get('username', 'System 🌟')
411
+ await asyncio.to_thread(log_action, username, "🎤📝 - Voice processor - speech to text!")
412
+ if audio_bytes:
413
+ text = "Voice input simulation"
414
+ await save_chat_entry(username, text)
415
+
416
+ # Dummy AI lookup function (replace with actual implementation)
417
+ async def perform_ai_lookup(query, vocal_summary=True, extended_refs=False, titles_summary=True, full_audio=False, useArxiv=True, useArxivAudio=False):
418
+ username = st.session_state.get('username', 'System 🌟')
419
+ result = f"AI Lookup Result for '{query}' (Arxiv: {useArxiv}, Audio: {useArxivAudio})"
420
+ await save_chat_entry(username, result)
421
+ if useArxivAudio:
422
+ audio_file = await async_edge_tts_generate(result, st.session_state.voice)
423
+ if audio_file:
424
+ st.audio(audio_file)
425
+
426
+ # Delete all user files function
427
+ def delete_user_files():
428
+ protected_files = {'app.py', 'requirements.txt', 'README.md'}
429
+ deleted_files = []
430
+ directories = [MEDIA_DIR, AUDIO_DIR, CHAT_DIR, VOTE_DIR, HISTORY_DIR]
431
+ for directory in directories:
432
+ if os.path.exists(directory):
433
+ for root, _, files in os.walk(directory):
434
+ for file in files:
435
+ file_path = os.path.join(root, file)
436
+ if os.path.basename(file_path) not in protected_files:
437
+ try:
438
+ os.remove(file_path)
439
+ deleted_files.append(file_path)
440
+ except Exception as e:
441
+ st.error(f"Failed to delete {file_path}: {e}")
442
+ try:
443
+ shutil.rmtree(directory, ignore_errors=True)
444
+ os.makedirs(directory, exist_ok=True)
445
+ except Exception as e:
446
+ st.error(f"Failed to remove directory {directory}: {e}")
447
+ st.session_state.image_hashes.clear()
448
+ st.session_state.audio_cache.clear()
449
+ st.session_state.base64_cache.clear()
450
+ st.session_state.displayed_chat_lines.clear()
451
+ return deleted_files
452
+
453
+ # ASR Component HTML
454
+ ASR_HTML = """
455
+ <html>
456
+ <head>
457
+ <title>Continuous Speech Demo</title>
458
+ <style>
459
+ body {
460
+ font-family: sans-serif;
461
+ padding: 20px;
462
+ max-width: 800px;
463
+ margin: 0 auto;
464
+ }
465
+ button {
466
+ padding: 10px 20px;
467
+ margin: 10px 5px;
468
+ font-size: 16px;
469
+ }
470
+ #status {
471
+ margin: 10px 0;
472
  padding: 10px;
473
+ background: #e8f5e9;
474
+ border-radius: 4px;
 
475
  }
476
+ #output {
477
+ white-space: pre-wrap;
478
+ padding: 15px;
479
+ background: #f5f5f5;
480
+ border-radius: 4px;
481
+ margin: 10px 0;
482
+ min-height: 100px;
483
+ max-height: 400px;
484
+ overflow-y: auto;
485
  }
486
+ .controls {
487
+ margin: 10px 0;
 
 
488
  }
489
+ </style>
490
+ </head>
491
+ <body>
492
+ <div class="controls">
493
+ <button id="start">Start Listening</button>
494
+ <button id="stop" disabled>Stop Listening</button>
495
+ <button id="clear">Clear Text</button>
496
+ </div>
497
+ <div id="status">Ready</div>
498
+ <div id="output"></div>
499
+
500
+ <script>
501
+ if (!('webkitSpeechRecognition' in window)) {
502
+ alert('Speech recognition not supported');
503
+ } else {
504
+ const recognition = new webkitSpeechRecognition();
505
+ const startButton = document.getElementById('start');
506
+ const stopButton = document.getElementById('stop');
507
+ const clearButton = document.getElementById('clear');
508
+ const status = document.getElementById('status');
509
+ const output = document.getElementById('output');
510
+ let fullTranscript = '';
511
+ let lastUpdateTime = Date.now();
512
+
513
+ recognition.continuous = true;
514
+ recognition.interimResults = true;
515
+
516
+ const startRecognition = () => {
517
+ try {
518
+ recognition.start();
519
+ status.textContent = 'Listening...';
520
+ startButton.disabled = true;
521
+ stopButton.disabled = false;
522
+ } catch (e) {
523
+ console.error(e);
524
+ status.textContent = 'Error: ' + e.message;
525
  }
526
+ };
527
+
528
+ window.addEventListener('load', () => {
529
+ setTimeout(startRecognition, 1000);
530
+ });
531
+
532
+ startButton.onclick = startRecognition;
533
+
534
+ stopButton.onclick = () => {
535
+ recognition.stop();
536
+ status.textContent = 'Stopped';
537
+ startButton.disabled = false;
538
+ stopButton.disabled = true;
539
+ };
540
+
541
+ clearButton.onclick = () => {
542
+ fullTranscript = '';
543
+ output.textContent = '';
544
+ sendDataToPython({value: '', dataType: "json"});
545
+ };
546
+
547
+ recognition.onresult = (event) => {
548
+ let interimTranscript = '';
549
+ let finalTranscript = '';
550
+
551
+ for (let i = event.resultIndex; i < event.results.length; i++) {
552
+ const transcript = event.results[i][0].transcript;
553
+ if (event.results[i].isFinal) {
554
+ finalTranscript += transcript + '\\n';
555
+ } else {
556
+ interimTranscript += transcript;
557
+ }
558
+ }
559
+
560
+ if (finalTranscript || (Date.now() - lastUpdateTime > 5000)) {
561
+ if (finalTranscript) {
562
+ fullTranscript += finalTranscript;
563
+ }
564
+ lastUpdateTime = Date.now();
565
+ output.textContent = fullTranscript + (interimTranscript ? '... ' + interimTranscript : '');
566
+ output.scrollTop = output.scrollHeight;
567
+ sendDataToPython({value: fullTranscript, dataType: "json"});
568
+ }
569
+ };
570
+
571
+ recognition.onend = () => {
572
+ if (!stopButton.disabled) {
573
+ try {
574
+ recognition.start();
575
+ console.log('Restarted recognition');
576
+ } catch (e) {
577
+ console.error('Failed to restart recognition:', e);
578
+ status.textContent = 'Error restarting: ' + e.message;
579
+ startButton.disabled = false;
580
+ stopButton.disabled = true;
581
+ }
582
+ }
583
+ };
584
+
585
+ recognition.onerror = (event) => {
586
+ console.error('Recognition error:', event.error);
587
+ status.textContent = 'Error: ' + event.error;
588
+ if (event.error === 'not-allowed' || event.error === 'service-not-allowed') {
589
+ startButton.disabled = false;
590
+ stopButton.disabled = true;
591
+ }
592
+ };
593
+ }
594
+
595
+ function sendDataToPython(data) {
596
+ window.parent.postMessage({
597
+ isStreamlitMessage: true,
598
+ type: "streamlit:setComponentValue",
599
+ ...data
600
+ }, "*");
601
+ }
602
+
603
+ window.addEventListener('load', function() {
604
+ window.setTimeout(function() {
605
+ window.parent.postMessage({
606
+ isStreamlitMessage: true,
607
+ type: "streamlit:setFrameHeight",
608
+ height: document.documentElement.clientHeight
609
+ }, "*");
610
+ }, 0);
611
  });
612
+ </script>
613
+ </body>
614
+ </html>
615
+ """
616
+
617
+ # Main execution - let’s roll! 🎲🚀
618
+ def main():
619
+ NODE_NAME, port = get_node_name()
620
+
621
+ loop = asyncio.new_event_loop()
622
+ asyncio.set_event_loop(loop)
623
+
624
+ async def async_interface():
625
+ if 'username' not in st.session_state:
626
+ chat_content = await load_chat()
627
+ available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
628
+ st.session_state.username = random.choice(available_names) if available_names else random.choice(list(FUN_USERNAMES.keys()))
629
+ st.session_state.voice = FUN_USERNAMES[st.session_state.username]
630
+ st.markdown(f"**🎙️ Voice Selected**: {st.session_state.voice} 🗣️ for {st.session_state.username}")
631
+
632
+ st.title(f"🤖🧠MMO {st.session_state.username}📝🔬")
633
+ st.markdown(f"Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting! 🎉")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
 
635
+ if not st.session_state.server_task:
636
+ st.session_state.server_task = loop.create_task(run_websocket_server())
637
+
638
+ audio_bytes = audio_recorder()
639
+ if audio_bytes:
640
+ await process_voice_input(audio_bytes)
641
+ st.rerun()
642
+
643
+ # Continuous Speech Input (ASR)
644
+ st.subheader("🎤 Continuous Speech Input")
645
+ asr_component = components.html(ASR_HTML, height=400)
646
+ if asr_component and isinstance(asr_component, dict) and 'value' in asr_component:
647
+ transcript = asr_component['value'].strip()
648
+ if transcript and transcript != st.session_state.last_transcript:
649
+ st.session_state.transcript_history.append(transcript)
650
+ await save_chat_entry(st.session_state.username, transcript, is_markdown=True)
651
+ st.session_state.last_transcript = transcript
652
+ st.rerun()
653
+
654
+ # Unified Chat History at Top
655
+ st.subheader(f"{START_ROOM} Chat History 💬")
656
+ chat_content = await load_chat()
657
+ chat_lines = chat_content.split('\n')
658
+ chat_lines = [line for line in chat_lines if line.strip() and ': ' in line and not line.startswith('#')]
659
+ if chat_lines:
660
+ col1, col2 = st.columns([2, 1])
661
+ with col1:
662
+ st.write("### Text & Audio Chat")
663
+ for i, line in enumerate(reversed(chat_lines)): # Descending order
664
+ col_text, col_audio = st.columns([3, 1])
665
+ with col_text:
666
+ if "```markdown" in line:
667
+ markdown_content = re.search(r'```markdown\n(.*?)```', line, re.DOTALL)
668
+ if markdown_content:
669
+ st.markdown(markdown_content.group(1))
670
+ else:
671
+ st.markdown(line)
672
+ else:
673
+ st.markdown(line)
674
+ with col_audio:
675
+ username = line.split(': ')[1].split(' ')[0]
676
+ cache_key = f"{line}_{FUN_USERNAMES.get(username, 'en-US-AriaNeural')}"
677
+ if cache_key not in st.session_state.audio_cache:
678
+ cleaned_text = clean_text_for_tts(line.split(': ', 1)[1])
679
+ voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
680
+ audio_file = await async_edge_tts_generate(cleaned_text, voice)
681
+ st.session_state.audio_cache[cache_key] = audio_file
682
+ audio_file = st.session_state.audio_cache.get(cache_key)
683
+ if audio_file:
684
+ play_and_download_audio(audio_file)
685
+
686
+ if st.session_state.quote_line:
687
+ st.markdown(f"### Quoting: {st.session_state.quote_line}")
688
+ quote_response = st.text_area("Add your response", key="quote_response", value=st.session_state.message_text)
689
+ paste_result_quote = paste_image_button("📋 Paste Image or Text with Quote", key="paste_button_quote")
690
+ if paste_result_quote.image_data is not None:
691
+ if isinstance(paste_result_quote.image_data, str):
692
+ st.session_state.message_text = paste_result_quote.image_data
693
+ st.text_area("Add your response", key="quote_response", value=st.session_state.message_text)
694
+ else:
695
+ st.image(paste_result_quote.image_data, caption="Received Image for Quote")
696
+ filename = await save_pasted_image(paste_result_quote.image_data, st.session_state.username)
697
+ if filename:
698
+ st.session_state.pasted_image_data = filename
699
+ if st.button("Send Quote 🚀", key="send_quote"):
700
+ markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
701
+ if st.session_state.pasted_image_data:
702
+ markdown_response += f"\n- **Image**: ![Pasted Image]({st.session_state.pasted_image_data})"
703
+ await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}")
704
+ st.session_state.pasted_image_data = None
705
+ await save_chat_entry(st.session_state.username, markdown_response, is_markdown=True)
706
+ st.session_state.quote_line = None
707
+ st.session_state.message_text = ''
708
  st.rerun()
709
+
710
+ current_selection = st.session_state.username if st.session_state.username in FUN_USERNAMES else ""
711
+ new_username = st.selectbox("Change Name and Voice", [""] + list(FUN_USERNAMES.keys()), index=(list(FUN_USERNAMES.keys()).index(current_selection) + 1 if current_selection else 0), format_func=lambda x: f"{x} ({FUN_USERNAMES.get(x, 'No Voice')})" if x else "Select a name")
712
+ if new_username and new_username != st.session_state.username:
713
+ await save_chat_entry("System 🌟", f"{st.session_state.username} changed name to {new_username}")
714
+ st.session_state.username = new_username
715
+ st.session_state.voice = FUN_USERNAMES[new_username]
716
+ st.markdown(f"**🎙️ Voice Changed**: {st.session_state.voice} 🗣️ for {st.session_state.username}")
717
  st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718
 
719
+ # Message input with Send button on the right
720
+ col_input, col_send = st.columns([5, 1])
721
+ with col_input:
722
+ message = st.text_input(f"Message as {st.session_state.username} (Voice: {st.session_state.voice})", key="message_input", value=st.session_state.message_text)
723
+ with col_send:
724
+ if st.button("Send 🚀", key="send_button"):
725
+ if message.strip():
726
+ audio_file = await save_chat_entry(st.session_state.username, message, is_markdown=True)
727
+ if audio_file:
728
+ st.session_state.audio_cache[f"{message}_{FUN_USERNAMES[st.session_state.username]}"] = audio_file
729
+ st.audio(audio_file) # Immediate preview
730
+ if st.session_state.pasted_image_data:
731
+ await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}")
732
+ st.session_state.pasted_image_data = None
733
+ st.session_state.message_text = ''
734
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
 
736
+ paste_result_msg = paste_image_button("📋 Paste Image or Text with Message", key="paste_button_msg")
737
+ if paste_result_msg.image_data is not None:
738
+ if isinstance(paste_result_msg.image_data, str):
739
+ st.session_state.message_text = paste_result_msg.image_data
740
+ st.text_input(f"Message as {st.session_state.username} (Voice: {st.session_state.voice})", key="message_input_paste", value=st.session_state.message_text)
741
+ else:
742
+ st.image(paste_result_msg.image_data, caption="Received Image for Message")
743
+ filename = await save_pasted_image(paste_result_msg.image_data, st.session_state.username)
744
+ if filename:
745
+ st.session_state.pasted_image_data = filename
746
+
747
+ tab_main = st.radio("Action:", ["🎤 Voice", "📸 Media", "🔍 ArXiv", "📝 Editor"], horizontal=True)
748
+ useArxiv = st.checkbox("Search Arxiv for Research Paper Answers", value=True)
749
+ useArxivAudio = st.checkbox("Generate Audio File for Research Paper Answers", value=False)
750
+
751
+ st.subheader("Upload Media 🎨🎶🎥")
752
+ uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3'])
753
+ if uploaded_file:
754
+ timestamp = format_timestamp_prefix(st.session_state.username)
755
+ username = st.session_state.username
756
+ ext = uploaded_file.name.split('.')[-1]
757
+ file_hash = hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]
758
+ if file_hash not in st.session_state.image_hashes:
759
+ filename = f"{timestamp}-{file_hash}.{ext}"
760
+ file_path = os.path.join(MEDIA_DIR, filename)
761
+ await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
762
+ st.success(f"Uploaded {filename}")
763
+ await save_chat_entry(username, f"Uploaded media: {file_path}")
764
+ await save_chat_history_with_image(username, file_path)
765
+ st.session_state.image_hashes.add(file_hash)
766
+ if file_path.endswith('.mp4'):
767
+ st.session_state.media_notifications.append(file_path)
768
+
769
+ # Big Red Delete Button
770
+ st.subheader("🛑 Danger Zone")
771
+ if st.button("Try Not To Delete It All On Your First Day", key="delete_all", help="Deletes all user-added files!", type="primary", use_container_width=True):
772
+ deleted_files = delete_user_files()
773
+ if deleted_files:
774
+ st.markdown("### 🗑️ Deleted Files:\n" + "\n".join([f"- `{file}`" for file in deleted_files]))
775
+ else:
776
+ st.markdown("### 🗑️ Nothing to Delete!")
777
+ st.rerun()
778
 
779
+ st.subheader("Refresh ⏳")
780
+ refresh_rate = st.slider("Refresh Rate", 1, 300, st.session_state.refresh_rate)
781
+ st.session_state.refresh_rate = refresh_rate
782
+ timer_placeholder = st.empty()
783
+ for i in range(st.session_state.refresh_rate, -1, -1):
784
+ font_name, font_func = random.choice(UNICODE_FONTS)
785
+ countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
786
+ timer_placeholder.markdown(f"<p class='timer'>⏳ {font_func('Refresh in:')} {countdown_str}</p>", unsafe_allow_html=True)
787
+ time.sleep(1)
788
+ st.rerun()
789
 
790
+ # Gallery with Adjustable Tiles
791
+ st.subheader("Media Gallery 🎨🎶🎥")
792
+ gallery_columns = st.slider("Number of Gallery Tiles", 1, 20, st.session_state.gallery_columns)
793
+ st.session_state.gallery_columns = gallery_columns
794
+ media_files = glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg") + glob.glob(f"{MEDIA_DIR}/*.mp4")
795
+ if media_files:
796
+ media_votes = await load_votes(MEDIA_VOTES_FILE)
797
+ seen_files = set()
798
+ cols = st.columns(gallery_columns)
799
+ col_idx = 0
800
+ for media_file in sorted(media_files, key=os.path.getmtime, reverse=True):
801
+ if media_file not in seen_files:
802
+ seen_files.add(media_file)
803
+ with cols[col_idx]:
804
+ filename = os.path.basename(media_file)
805
+ vote_count = media_votes.get(media_file, 0)
806
+ st.markdown(f"**{filename}**")
807
+ if media_file.endswith(('.png', '.jpg')):
808
+ st.image(media_file, use_container_width=True)
809
+ elif media_file.endswith('.mp4'):
810
+ st.markdown(get_video_html(media_file), unsafe_allow_html=True) # Verbatim autoplay
811
+ if st.button(f"👍 {vote_count}", key=f"media_vote_{media_file}"):
812
+ await save_vote(MEDIA_VOTES_FILE, media_file, await generate_user_hash(), st.session_state.username)
813
+ st.rerun()
814
+ col_idx = (col_idx + 1) % gallery_columns
815
+
816
+ # Full Log at End
817
+ st.subheader("Full Chat Log 📜")
818
+ with open(HISTORY_FILE, 'r') as f:
819
+ history_content = f.read()
820
+ st.markdown(history_content)
821
+
822
+ loop.run_until_complete(async_interface())
823
 
824
  if __name__ == "__main__":
825
+ main()