awacke1 commited on
Commit
3a5391d
·
verified ·
1 Parent(s): 562ef4f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +352 -352
app.py CHANGED
@@ -23,14 +23,14 @@ from urllib.parse import urlencode
23
  from PyPDF2 import PdfReader
24
  import json
25
 
26
- # Patch for nested async - sneaky fix! 🐍✨
27
  nest_asyncio.apply()
28
 
29
- # Static config - constants rule! 📏👑
30
  icons = '🤖🧠🔬📝'
31
  START_ROOM = "Sector 🌌"
32
 
33
- # Page setup - dressing up the window! 🖼️🎀
34
  st.set_page_config(
35
  page_title="🤖🧠MMO Chat Brain📝🔬",
36
  page_icon=icons,
@@ -38,7 +38,7 @@ st.set_page_config(
38
  initial_sidebar_state="auto"
39
  )
40
 
41
- # Funky usernames with corresponding Edge TTS voices a cosmic cast! 🌌🎭
42
  FUN_USERNAMES = {
43
  "CosmicJester 🌌": "en-US-AriaNeural",
44
  "PixelPanda 🐼": "en-US-JennyNeural",
@@ -62,7 +62,7 @@ FUN_USERNAMES = {
62
  "ChronoChimp 🐒": "en-GB-LibbyNeural"
63
  }
64
 
65
- # Top-level files and directories the treasure trove of shared lore! 🗺️📜
66
  CHAT_FILE = "global_chat.md"
67
  QUOTE_VOTES_FILE = "quote_votes.md"
68
  MEDIA_VOTES_FILE = "media_votes.md"
@@ -73,10 +73,10 @@ MEDIA_DIR = "media_files"
73
  os.makedirs(AUDIO_DIR, exist_ok=True)
74
  os.makedirs(MEDIA_DIR, exist_ok=True)
75
 
76
- # Fancy digits - numbers got style, dancing in the cosmic light! 🔢💃
77
  UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
78
 
79
- # Massive font collection - typography bonanza, a scribe’s delight! 🖋️🎨
80
  UNICODE_FONTS = [
81
  ("Normal", lambda x: x),
82
  ("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)),
@@ -99,7 +99,7 @@ UNICODE_FONTS = [
99
  ("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
100
  ]
101
 
102
- # Global state - keeping tabs! 🌍📋 – a shared saga begins! 🎭
103
  if 'server_running' not in st.session_state:
104
  st.session_state.server_running = False
105
  if 'server_task' not in st.session_state:
@@ -133,13 +133,13 @@ if 'user_id' not in st.session_state:
133
  if 'user_hash' not in st.session_state:
134
  st.session_state.user_hash = None
135
 
136
- # Timestamp wizardry - clock ticks with flair, a temporal affair! ⏰🎩
137
  def format_timestamp_prefix(username):
138
  central = pytz.timezone('US/Central')
139
  now = datetime.now(central)
140
  return f"{username}-{now.strftime('%I-%M-%p-%m-%d-%Y')}-{st.session_state.user_id}"
141
 
142
- # Compute image hash - a cryptographic clash, securing our flash! 🛡️🔍
143
  def compute_image_hash(image_data):
144
  if isinstance(image_data, Image.Image):
145
  img_byte_arr = io.BytesIO()
@@ -149,7 +149,7 @@ def compute_image_hash(image_data):
149
  img_bytes = image_data
150
  return hashlib.md5(img_bytes).hexdigest()[:8]
151
 
152
- # Node naming - christening the beast, a heroic feast! 🌐🍼
153
  def get_node_name():
154
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
155
  parser.add_argument('--node-name', type=str, default=None)
@@ -159,7 +159,7 @@ def get_node_name():
159
  log_action(username, "🌐🍼 - Node naming - christening the beast!")
160
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
161
 
162
- # Action logger - spying on deeds, a stealthy steed! 🕵️📜
163
  def log_action(username, action):
164
  if 'action_log' not in st.session_state:
165
  st.session_state.action_log = {}
@@ -173,13 +173,13 @@ def log_action(username, action):
173
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
174
  user_log[action] = current_time
175
 
176
- # Clean text - strip the fancy fluff, a scribe’s tough bluff! 🧹📝
177
  def clean_text_for_tts(text):
178
  cleaned = re.sub(r'[#*!\[\]]+', '', text)
179
  cleaned = ' '.join(cleaned.split())
180
  return cleaned if cleaned else "No text to speak" # Default if empty
181
 
182
- # Shared Memory Cache - the epic vault, a shared memory assault! 🗳️💾
183
  @st.cache_resource
184
  def get_shared_memory():
185
  """
@@ -193,30 +193,30 @@ def get_shared_memory():
193
  self.cache_time = datetime.now() # Timestamp of glory, a hero’s blend!
194
 
195
  def update_chat(self, entry):
196
- """Add a chat entry, a rhyme in the stream, keeping our saga supreme! 💬🎶"""
197
  self.chat_history.append(entry)
198
  if len(self.chat_history) > 100: # Limit to keep it tight, a knight’s fight!
199
  self.chat_history.pop(0)
200
 
201
  def update_media(self, media_path):
202
- """Store media files, a visual dream, in our shared cache, a radiant beam! 🖼️🌟"""
203
  self.media_files.append(media_path)
204
  if len(self.media_files) > 50: # Cap the hoard, lest it grow too wide!
205
  self.media_files.pop(0)
206
 
207
  def get_condensed_dialogs(self):
208
- """Condense the chatter, a poetic pact, short and sweet, our story intact! 🗣️✨"""
209
  return "\n".join(f"- {entry.split(': ')[1][:50]}" for entry in self.chat_history[-10:])
210
 
211
  def clear(self):
212
- """Clear the cache, a dramatic purge, resetting our tale, a new surge! 🌀🔥"""
213
  self.chat_history.clear()
214
  self.media_files.clear()
215
  self.cache_time = datetime.now()
216
 
217
  return SharedMemory()
218
 
219
- # Audio Processor Class - voices echo, a sonic hero! 🎶🌟
220
  class AudioProcessor:
221
  def __init__(self):
222
  self.cache_dir = AUDIO_DIR
@@ -234,7 +234,7 @@ class AudioProcessor:
234
 
235
  async def create_audio(self, text, voice='en-US-AriaNeural', filename=None):
236
  """
237
- Create audio, a voice that roars, in our shared cache, through cosmic doors! 🎤🌌
238
  """
239
  cache_key = hashlib.md5(f"{text}:{voice}".encode()).hexdigest()
240
  timestamp = format_timestamp_prefix(st.session_state.username)
@@ -243,12 +243,12 @@ class AudioProcessor:
243
  if cache_key in self.metadata and os.path.exists(filename):
244
  return filename
245
 
246
- # Clean text for speech, a bard’s clean sweep!
247
  text = text.replace("\n", " ").replace("</s>", " ").strip()
248
  if not text:
249
  return None
250
 
251
- # Generate audio, a sonic leap, with edge_tts, our voices deep!
252
  try:
253
  communicate = edge_tts.Communicate(text, voice)
254
  await communicate.save(filename)
@@ -258,7 +258,7 @@ class AudioProcessor:
258
  log_action("System 🌟", f"TTS failed for text '{text}' with voice '{voice}': {str(e)}")
259
  return None
260
 
261
- # Update metadata, our epic creed, in shared memory, a heroic deed!
262
  self.metadata[cache_key] = {
263
  'timestamp': datetime.now().isoformat(),
264
  'text_length': len(text),
@@ -268,19 +268,18 @@ class AudioProcessor:
268
 
269
  return filename
270
 
271
- # Chat saver - words locked tight, in shared memory’s light! 💬🔒
272
  async def save_chat_entry(username, message, is_markdown=False, quote_line=None, media_file=None, skip_audio=False):
273
  """
274
- Save chats with flair, in shared cache we dare, a rhyming affair! ✨🎉
275
  """
276
- await asyncio.to_thread(log_action, username, "💬🔒 - Chat saver - words locked tight!")
277
  central = pytz.timezone('US/Central')
278
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
279
  user_history_file = f"{username}_history.md"
280
  voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
281
  indent = " " if quote_line else "" # Nesting for replies, a poetic spree!
282
 
283
- # Prepare entry, a verse so bright, in shared memory’s sight!
284
  if is_markdown:
285
  entry = f"{indent}[{timestamp}] {username}:\n{indent}```markdown\n{indent}{message}\n{indent}```"
286
  else:
@@ -288,19 +287,19 @@ async def save_chat_entry(username, message, is_markdown=False, quote_line=None,
288
  if quote_line:
289
  entry = f"{indent}> {quote_line}\n{entry}"
290
 
291
- # Save to global chat file, a shared epic tale, never frail!
292
  with open(CHAT_FILE, 'a') as f:
293
  f.write(f"{entry}\n")
294
 
295
- # Save to user-specific history, a personal rhyme, in shared time!
296
  if not os.path.exists(user_history_file):
297
  with open(user_history_file, 'w') as f:
298
  f.write(f"# Chat History for {username} (Voice: {voice})\n\n")
299
  with open(user_history_file, 'a') as f:
300
  f.write(f"{entry}\n")
301
 
302
- # Generate audio, unless we skip, in shared cache, a sonic grip!
303
- audio_filename = None # Initialize, lest our tale derail!
304
  if not skip_audio and message.strip():
305
  cleaned_message = clean_text_for_tts(message)
306
  audio_processor = AudioProcessor()
@@ -313,7 +312,7 @@ async def save_chat_entry(username, message, is_markdown=False, quote_line=None,
313
  with open(CHAT_FILE, 'a') as f:
314
  f.write(f"{indent}[{timestamp}] Audio: {audio_filename}\n")
315
 
316
- # Handle media files, a visual quest, in shared memory, our treasure chest!
317
  if media_file:
318
  if isinstance(media_file, Image.Image):
319
  timestamp_prefix = format_timestamp_prefix(username)
@@ -341,7 +340,7 @@ async def save_chat_entry(username, message, is_markdown=False, quote_line=None,
341
  with open(user_history_file, 'a') as f:
342
  f.write(f"{indent}[{timestamp}] Media: ![Media]({media_file})\n")
343
 
344
- # Update shared memory, our epic lore, in cache forevermore!
345
  shared_memory = get_shared_memory()
346
  shared_memory.update_chat(entry)
347
  if media_file:
@@ -349,12 +348,12 @@ async def save_chat_entry(username, message, is_markdown=False, quote_line=None,
349
 
350
  await broadcast_message(f"{username}|{message}", "chat")
351
  st.session_state.last_chat_update = time.time()
352
- return audio_filename # Return, even if silent, our tale’s delight!
353
 
354
- # Save chat history with image or PDF - a scribe’s historic flight! 📜🚀
355
  async def save_chat_history_with_image(username, image_path):
356
  """
357
- Save history, a scribe’s grand sight, in shared memory, pure and bright! ✨📖
358
  """
359
  central = pytz.timezone('US/Central')
360
  timestamp = datetime.now(central).strftime("%Y-%m-%d_%H-%M-%S")
@@ -368,11 +367,11 @@ async def save_chat_history_with_image(username, image_path):
368
  f.write(f"[{timestamp}] {username} (Voice: {voice}) Shared Media: {os.path.basename(image_path)}\n")
369
  f.write(f"```markdown\n{chat_content}\n```\n")
370
 
371
- # Chat loader - history unleashed, a shared epic feast! 📜🚀
372
  @st.cache_resource
373
  async def load_chat():
374
  """
375
- Load chats, a shared memory spree, from cache with glee, our history free! 🌟💬
376
  """
377
  username = st.session_state.get('username', 'System 🌟')
378
  await asyncio.to_thread(log_action, username, "📜🚀 - Chat loader - history unleashed!")
@@ -382,10 +381,10 @@ async def load_chat():
382
  content = await asyncio.to_thread(f.read)
383
  return content
384
 
385
- # User lister - who’s in the gang, a shared memory bang! 👥🎉
386
  async def get_user_list(chat_content):
387
  """
388
- List users, a shared roster’s rhyme, in cache divine, through space and time! 🌍🎭
389
  """
390
  username = st.session_state.get('username', 'System 🌟')
391
  await asyncio.to_thread(log_action, username, "👥🎉 - User lister - who’s in the gang!")
@@ -396,19 +395,19 @@ async def get_user_list(chat_content):
396
  users.add(user)
397
  return sorted(list(users))
398
 
399
- # Join checker - been here before, in shared cache, a lore galore! 🚪🔍
400
  async def has_joined_before(client_id, chat_content):
401
  """
402
- Check joins, a shared memory chore, in cache secure, forevermore! 🌀🔐
403
  """
404
  username = st.session_state.get('username', 'System 🌟')
405
  await asyncio.to_thread(log_action, username, "🚪🔍 - Join checker - been here before?")
406
  return any(f"Client-{client_id}" in line for line in chat_content.split('\n'))
407
 
408
- # Suggestion maker - old quips resurface, in shared cache, a verse so fierce! 💡📝
409
  async def get_message_suggestions(chat_content, prefix):
410
  """
411
- Suggest quips, a shared memory jest, in cache we nest, our humor blessed! 😂🌟
412
  """
413
  username = st.session_state.get('username', 'System 🌟')
414
  await asyncio.to_thread(log_action, username, "💡📝 - Suggestion maker - old quips resurface!")
@@ -416,11 +415,11 @@ async def get_message_suggestions(chat_content, prefix):
416
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
417
  return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
418
 
419
- # Vote saver - cheers recorded, in shared cache, our cheers restored! 👍📊
420
  @st.cache_resource
421
  async def save_vote(file, item, user_hash, username, comment=""):
422
  """
423
- Save votes, a shared tally’s cheer, in cache so clear, our triumph near! 🏆🎉
424
  """
425
  await asyncio.to_thread(log_action, username, "👍📊 - Vote saver - cheers recorded!")
426
  central = pytz.timezone('US/Central')
@@ -432,13 +431,13 @@ async def save_vote(file, item, user_hash, username, comment=""):
432
  if comment:
433
  chat_message += f" - {comment}"
434
  await save_chat_entry(username, chat_message)
435
- return entry # Return for caching, our epic fling!
436
 
437
- # Vote counter - tallying the love, in shared cache, a tale above! 🏆📈
438
  @st.cache_resource
439
  async def load_votes(file):
440
  """
441
- Count votes, a shared tally’s might, in cache so bright, our victory’s light! 🌟📊
442
  """
443
  username = st.session_state.get('username', 'System 🌟')
444
  await asyncio.to_thread(log_action, username, "🏆📈 - Vote counter - tallying the love!")
@@ -459,10 +458,10 @@ async def load_votes(file):
459
  user_votes.add(vote_key)
460
  return votes
461
 
462
- # Hash generator - secret codes ahoy, in shared cache, a cryptic joy! 🔑🕵️
463
  async def generate_user_hash():
464
  """
465
- Generate hashes, a shared code’s chime, in cache sublime, through space and time! 🌌🔐
466
  """
467
  username = st.session_state.get('username', 'System 🌟')
468
  await asyncio.to_thread(log_action, username, "🔑🕵️ - Hash generator - secret codes ahoy!")
@@ -470,10 +469,10 @@ async def generate_user_hash():
470
  st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
471
  return st.session_state.user_hash
472
 
473
- # Audio maker - voices come alive, in shared cache, a sonic dive! 🎶🌟
474
  async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
475
  """
476
- Make audio, a shared voice’s thrill, in cache we fill, with sonic will! 🎤🔊
477
  """
478
  username = st.session_state.get('username', 'System 🌟')
479
  await asyncio.to_thread(log_action, username, "🎶🌟 - Audio maker - voices come alive!")
@@ -489,10 +488,10 @@ async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3
489
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: Audio failed - No audio received for '{text}'\n")
490
  return None
491
 
492
- # Audio player - tunes blast off, in shared cache, a musical scoff! 🔊🚀
493
  def play_and_download_audio(file_path):
494
  """
495
- Play tunes, a shared melody’s jest, in cache expressed, our audio quest! 🎵🌌
496
  """
497
  if file_path and os.path.exists(file_path):
498
  st.audio(file_path)
@@ -501,10 +500,10 @@ def play_and_download_audio(file_path):
501
  dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}">🎵 Download {os.path.basename(file_path)}</a>'
502
  st.markdown(dl_link, unsafe_allow_html=True)
503
 
504
- # Image saver - pics preserved, in shared cache, a visual burst! 📸💾
505
  async def save_pasted_image(image, username):
506
  """
507
- Save images, a shared sight’s cheer, in cache so clear, our vision near! 🖼️🌟
508
  """
509
  await asyncio.to_thread(log_action, username, "📸💾 - Image saver - pics preserved!")
510
  timestamp = format_timestamp_prefix(username)
@@ -515,10 +514,10 @@ async def save_pasted_image(image, username):
515
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(img_byte_arr.getvalue()))
516
  return media_filename
517
 
518
- # Video and Audio savers - media magic, in shared cache, a heroic tragic! 🎥🎶
519
  async def save_media(file, username, ext):
520
  """
521
- Save media, a shared epic’s might, in cache so bright, our treasures ignite! 🧙🔥
522
  """
523
  await asyncio.to_thread(log_action, username, f"📸💾 - Media saver - {ext} preserved!")
524
  timestamp = format_timestamp_prefix(username)
@@ -527,10 +526,10 @@ async def save_media(file, username, ext):
527
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(file.getbuffer()))
528
  return media_filename
529
 
530
- # PDF saver and audio generator - documents dance, in shared cache, a chance! 📜🎶
531
  async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
532
  """
533
- Save PDFs, a shared document’s glee, in cache we see, our history’s key! 📚🌟
534
  """
535
  await asyncio.to_thread(log_action, username, "📜🎶 - PDF saver and audio generator!")
536
  timestamp = format_timestamp_prefix(username)
@@ -559,10 +558,10 @@ async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
559
 
560
  return pdf_filename, texts, audio_files
561
 
562
- # Video renderer - movies roll, in shared cache, a visual toll! 🎥🎬
563
  def get_video_html(video_path, width="100px"):
564
  """
565
- Render videos, a shared screen’s thrill, in cache we fill, with cinematic will! 📺🌌
566
  """
567
  video_url = f"data:video/mp4;base64,{base64.b64encode(open(os.path.join(MEDIA_DIR, video_path), 'rb').read()).decode()}"
568
  return f'''
@@ -572,10 +571,10 @@ def get_video_html(video_path, width="100px"):
572
  </video>
573
  '''
574
 
575
- # Audio renderer - sounds soar, in shared cache, a sonic roar! 🎶✈️
576
  async def get_audio_html(audio_path, width="100px"):
577
  """
578
- Render audio, a shared sound’s cheer, in cache so clear, our music near! 🎵🌟
579
  """
580
  username = st.session_state.get('username', 'System 🌟')
581
  await asyncio.to_thread(log_action, username, "🎶✈️ - Audio renderer - sounds soar!")
@@ -587,10 +586,10 @@ async def get_audio_html(audio_path, width="100px"):
587
  </audio>
588
  '''
589
 
590
- # Websocket handler - chat links up, in shared cache, a connection’s cup! 🌐🔗
591
  async def websocket_handler(websocket, path):
592
  """
593
- Handle chats, a shared link’s might, in cache so bright, our network’s light! 🌍🔌
594
  """
595
  username = st.session_state.get('username', 'System 🌟')
596
  await asyncio.to_thread(log_action, username, "🌐🔗 - Websocket handler - chat links up!")
@@ -613,10 +612,10 @@ async def websocket_handler(websocket, path):
613
  if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
614
  del st.session_state.active_connections[room_id][client_id]
615
 
616
- # Message broadcaster - words fly far, in shared cache, a starry czar! 📢✈️
617
  async def broadcast_message(message, room_id):
618
  """
619
- Broadcast words, a shared echo’s cheer, in cache so clear, our message near! 🌠📡
620
  """
621
  username = st.session_state.get('username', 'System 🌟')
622
  await asyncio.to_thread(log_action, username, "📢✈️ - Message broadcaster - words fly far!")
@@ -630,10 +629,10 @@ async def broadcast_message(message, room_id):
630
  for client_id in disconnected:
631
  del st.session_state.active_connections[room_id][client_id]
632
 
633
- # Server starter - web spins up, in shared cache, a digital pup! 🖥️🌀
634
  async def run_websocket_server():
635
  """
636
- Start server, a shared spin’s delight, in cache so right, our web takes flight! 🚀🌐
637
  """
638
  username = st.session_state.get('username', 'System 🌟')
639
  await asyncio.to_thread(log_action, username, "🖥️🌀 - Server starter - web spins up!")
@@ -642,10 +641,10 @@ async def run_websocket_server():
642
  st.session_state.server_running = True
643
  await server.wait_closed()
644
 
645
- # Delete all user files - a purge so grand, in shared cache, a clearing band! 🗑️🔥
646
  def delete_user_files():
647
  """
648
- Delete files, a shared purge’s might, in cache so light, our slate wiped tight! 🧹🌌
649
  """
650
  protected_files = {'app.py', 'requirements.txt', 'README.md', CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE, AUDIO_DIR, MEDIA_DIR}
651
  deleted_files = []
@@ -676,12 +675,14 @@ def delete_user_files():
676
  st.session_state.audio_cache.clear()
677
  st.session_state.base64_cache.clear()
678
  st.session_state.displayed_chat_lines.clear()
 
 
679
  return deleted_files
680
 
681
- # Query parameter checker - parse q with flair, in shared cache, a naming affair! 🚪🔍
682
  def check_query_params():
683
  """
684
- Check queries, a shared name’s quest, in cache so blessed, our path expressed! 🌟🔍
685
  """
686
  query_params = st.query_params if hasattr(st, 'query_params') else st.experimental_get_query_params()
687
  q_value = query_params.get("q", [None])[0]
@@ -693,10 +694,10 @@ def check_query_params():
693
  st.session_state.user_id = q_value # Use as user_id if not a valid username
694
  return None
695
 
696
- # Mermaid graph generator - visualize our tale, in shared cache, a graphic gale! 🌳📈
697
  def generate_mermaid_graph(chat_lines):
698
  """
699
- Generate graphs, a shared vision’s rhyme, in cache sublime, our story’s chime! 🧩🌌
700
  """
701
  mermaid_code = "graph TD\n"
702
  nodes = {}
@@ -727,298 +728,297 @@ def generate_mermaid_graph(chat_lines):
727
  mermaid_code += "\n".join(f" {edge}" for edge in edges)
728
  return mermaid_code
729
 
730
- # Main execution - let’s roll, in shared cache, a heroic toll! 🎲🚀
731
  def main():
732
  """
733
- Launch our tale, a shared epic’s start, in cache so smart, our hearts impart! 🌠🎉
734
  """
735
  NODE_NAME, port = get_node_name()
736
 
737
- loop = asyncio.new_event_loop()
738
- asyncio.set_event_loop(loop)
739
-
740
- async def async_interface():
741
- # Generate user ID and hash, a shared identity’s clash! 🆔🔮
742
- if not st.session_state.user_id:
743
- st.session_state.user_id = str(uuid.uuid4())
744
- st.session_state.user_hash = await generate_user_hash()
745
-
746
- # Check query params, a shared name’s quest, in cache expressed, our path impressed! 🚪🔍
747
- q_value = check_query_params()
748
- if not q_value and 'username' not in st.session_state:
749
- chat_content = await load_chat()
750
- available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
751
- st.session_state.username = random.choice(available_names) if available_names else random.choice(list(FUN_USERNAMES.keys()))
752
- st.session_state.voice = FUN_USERNAMES[st.session_state.username]
753
- st.markdown(f"**🎙️ Voice Selected**: {st.session_state.voice} 🗣️ for {st.session_state.username}")
754
-
755
- # Check existing history, a shared memory’s cheer, in cache so clear, our lore near! 📜🌟
756
- user_history_file = f"{st.session_state.username}_history.md"
757
- if os.path.exists(user_history_file):
758
- with open(user_history_file, 'r') as f:
759
- st.session_state.displayed_chat_lines = f.read().split('\n')
760
-
761
- user_url = f"/q={st.session_state.username}"
762
- with st.container():
763
- st.markdown(f"<small>Your unique URL path: [{user_url}]({user_url})</small>", unsafe_allow_html=True)
764
-
765
- with st.container():
766
- st.markdown(f"#### 🤖🧠MMO {st.session_state.username}📝🔬")
767
- st.markdown(f"<small>Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting! 🎉 User ID: {st.session_state.user_id}</small>", unsafe_allow_html=True)
768
-
769
- if not st.session_state.server_task:
770
- st.session_state.server_task = loop.create_task(run_websocket_server())
771
-
772
- # Unified Chat History at Top - a shared epic’s roar, condensed and stored! 💬🌌
773
- with st.container():
774
- st.markdown(f"##### {START_ROOM} Chat History 💬")
775
- shared_memory = get_shared_memory()
776
- chat_content = await load_chat()
777
- chat_lines = [line for line in chat_content.split('\n') if line.strip() and not line.startswith('#')]
778
- if chat_lines:
779
- chat_by_minute = {}
780
- for line in reversed(chat_lines):
781
- timestamp = line.split('] ')[0][1:] if '] ' in line else "Unknown"
782
- minute_key = timestamp[:16] # Up to minute
783
- if minute_key not in chat_by_minute:
784
- chat_by_minute[minute_key] = []
785
- chat_by_minute[minute_key].append(line)
786
-
787
- markdown_output = ""
788
- for minute, lines in chat_by_minute.items():
789
- minute_output = f"###### {minute[-5:]}\n" # Show only HH:MM
790
- for line in lines:
791
- if ': ' in line and not line.startswith(' '):
792
- user_message = line.split(': ', 1)[1]
793
- user = user_message.split(' ')[0]
794
- msg = user_message.split(' ', 1)[1] if ' ' in user_message else ''
795
- audio_html = ""
796
- media_content = ""
797
- next_lines = chat_lines[chat_lines.index(line)+1:chat_lines.index(line)+3]
798
- for nl in next_lines:
799
- if "Audio:" in nl:
800
- audio_file = nl.split("Audio: ")[-1].strip()
801
- audio_html = play_and_download_audio(audio_file)
802
- elif "Media:" in nl:
803
- media_file = nl.split("Media: ")[-1].strip('![]()')
804
- media_path = os.path.join(MEDIA_DIR, media_file)
805
- if os.path.exists(media_path):
806
- if media_file.endswith(('.png', '.jpg')):
807
- media_content = f"<img src='file://{media_path}' width='100'>"
808
- elif media_file.endswith('.mp4'):
809
- media_content = get_video_html(media_file)
810
- elif media_file.endswith('.mp3'):
811
- media_content = await get_audio_html(media_file)
812
- elif media_file.endswith('.pdf'):
813
- media_content = f"📜 {os.path.basename(media_file)}"
814
- minute_output += f"- 💬 **{user}**: {msg[:50]}... {audio_html} {media_content}\n" # Condensed dialog
815
- markdown_output += minute_output
816
- st.markdown(markdown_output, unsafe_allow_html=True)
817
- shared_memory.update_chat(markdown_output) # Cache condensed dialogs
818
-
819
- # Condensed Dialogs Display - a shared tale, concise and hale! 🗣️✨
820
- st.markdown("###### Condensed Dialogs 🗣️")
821
- condensed_dialogs = shared_memory.get_condensed_dialogs()
822
- st.markdown(condensed_dialogs)
823
-
824
- # Mermaid Graph Visualization - a shared map, our chat’s trap! 🌳📈
825
- st.markdown("###### Chat Relationship Tree 🌳")
826
- mermaid_code = generate_mermaid_graph(chat_lines)
827
- mermaid_html = f"""
828
- <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
829
- <div class="mermaid" style="height: 200px; overflow: auto;">{mermaid_code}</div>
830
- <script>mermaid.initialize({{startOnLoad:true}});</script>
831
- """
832
- components.html(mermaid_html, height=250)
833
-
834
- with st.container():
835
- if st.session_state.quote_line:
836
- st.markdown(f"###### Quoting: {st.session_state.quote_line}")
837
- quote_response = st.text_area("Add your response", key="quote_response", value=st.session_state.message_text)
838
- paste_result_quote = paste_image_button("📋 Paste Image or Text with Quote", key="paste_button_quote")
839
- if paste_result_quote.image_data is not None:
840
- if isinstance(paste_result_quote.image_data, str):
841
- st.session_state.message_text = paste_result_quote.image_data
842
- st.text_area("Add your response", key="quote_response", value=st.session_state.message_text)
843
- else:
844
- st.image(paste_result_quote.image_data, caption="Received Image for Quote")
845
- filename = await save_pasted_image(paste_result_quote.image_data, st.session_state.username)
846
- if filename:
847
- st.session_state.pasted_image_data = filename
848
- await save_chat_entry(st.session_state.username, f"Pasted image: {filename}", quote_line=st.session_state.quote_line, media_file=paste_result_quote.image_data)
849
- if st.button("Send Quote 🚀", key="send_quote"):
850
- markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
851
- if st.session_state.pasted_image_data:
852
- markdown_response += f"\n- **Image**: ![Pasted Image]({st.session_state.pasted_image_data})"
853
- await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}", quote_line=st.session_state.quote_line, media_file=st.session_state.pasted_image_data)
854
- st.session_state.pasted_image_data = None
855
- await save_chat_entry(st.session_state.username, markdown_response, is_markdown=True, quote_line=st.session_state.quote_line)
856
- st.session_state.quote_line = None
857
- st.session_state.message_text = ''
858
- st.rerun()
859
-
860
- current_selection = st.session_state.username if st.session_state.username in FUN_USERNAMES else ""
861
- 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")
862
- if new_username and new_username != st.session_state.username:
863
- await save_chat_entry("System 🌟", f"{st.session_state.username} changed name to {new_username}")
864
- st.session_state.username = new_username
865
- st.session_state.voice = FUN_USERNAMES[new_username]
866
- st.markdown(f"**🎙️ Voice Changed**: {st.session_state.voice} 🗣️ for {st.session_state.username}")
867
- st.rerun()
868
 
869
- # Message input with Send button on the right - a shared chat’s might! 💬🚀
870
- col_input, col_send = st.columns([5, 1])
871
- with col_input:
872
- 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)
873
- with col_send:
874
- if st.button("Send 🚀", key="send_button"):
875
- if message.strip() or st.session_state.pasted_image_data:
876
- await save_chat_entry(st.session_state.username, message if message.strip() else "Image shared", is_markdown=True, media_file=st.session_state.pasted_image_data if st.session_state.pasted_image_data else None, skip_audio=not message.strip())
877
- if st.session_state.pasted_image_data:
878
- st.session_state.pasted_image_data = None
879
- st.session_state.message_text = ''
880
- st.rerun()
881
 
882
- paste_result_msg = paste_image_button("📋 Paste Image or Text with Message", key="paste_button_msg")
883
- if paste_result_msg.image_data is not None:
884
- if isinstance(paste_result_msg.image_data, str):
885
- st.session_state.message_text = paste_result_msg.image_data
886
- 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)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
  else:
888
- st.image(paste_result_msg.image_data, caption="Received Image for Quote")
889
- filename = await save_pasted_image(paste_result_msg.image_data, st.session_state.username)
890
  if filename:
891
- await save_chat_entry(st.session_state.username, "Image shared", is_markdown=True, media_file=paste_result_msg.image_data, skip_audio=True)
892
- st.session_state.pasted_image_data = None
893
- st.rerun()
894
-
895
- with st.container():
896
- st.markdown("###### Upload Media 🎨🎶📜🎥")
897
- uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3', 'wav', 'pdf', 'txt', 'md', 'py'])
898
- if uploaded_file:
899
- timestamp = format_timestamp_prefix(st.session_state.username)
900
- username = st.session_state.username
901
- ext = uploaded_file.name.split('.')[-1].lower()
902
- if ext in ['png', 'jpg']:
903
- filename = await save_pasted_image(uploaded_file, username)
904
- elif ext in ['mp4', 'mp3', 'wav']:
905
- filename = await save_media(uploaded_file, username, ext)
906
- elif ext == 'pdf':
907
- pdf_filename, _, _ = await save_pdf_and_generate_audio(uploaded_file, username)
908
- filename = pdf_filename
909
- else:
910
- filename = f"{username}-{timestamp.split('-')[-1]}.{ext}"
911
- media_path = os.path.join(MEDIA_DIR, filename)
912
- await asyncio.to_thread(lambda: open(media_path, 'wb').write(uploaded_file.getbuffer()))
913
- await save_chat_entry(username, f"Uploaded {ext.upper()}: {os.path.basename(filename)}", media_file=uploaded_file, skip_audio=True)
914
- shared_memory = get_shared_memory()
915
- shared_memory.update_media(os.path.join(MEDIA_DIR, filename)) # Cache media
916
- st.success(f"Uploaded {filename}")
917
-
918
- # Big Red Delete Button - a purge so grand, clearing our shared land! 🗑️🔥
919
- st.markdown("###### 🛑 Danger Zone")
920
- 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):
921
- deleted_files = delete_user_files()
922
- if deleted_files:
923
- st.markdown("### 🗑️ Deleted Files:\n" + "\n".join([f"- `{file}`" for file in deleted_files]))
924
- shared_memory = get_shared_memory()
925
- shared_memory.clear() # Clear shared cache on purge
926
- else:
927
- st.markdown("### 🗑️ Nothing to Delete!")
928
  st.rerun()
929
 
930
- st.markdown("###### Refresh ")
931
- refresh_rate = st.slider("Refresh Rate", 1, 300, st.session_state.refresh_rate)
932
- st.session_state.refresh_rate = refresh_rate
933
- timer_placeholder = st.empty()
934
- for i in range(st.session_state.refresh_rate, -1, -1):
935
- font_name, font_func = random.choice(UNICODE_FONTS)
936
- countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
937
- timer_placeholder.markdown(f"<small>⏳ {font_func('Refresh in:')} {countdown_str}</small>", unsafe_allow_html=True)
938
- time.sleep(1)
939
  st.rerun()
940
 
941
- # Separate Galleries for Own and Shared Files - a shared gallery’s glare! 🖼️🎥
942
- with st.container():
943
- all_files = glob.glob(os.path.join(MEDIA_DIR, "*.md")) + glob.glob(os.path.join(MEDIA_DIR, "*.pdf")) + glob.glob(os.path.join(MEDIA_DIR, "*.txt")) + glob.glob(os.path.join(MEDIA_DIR, "*.py")) + glob.glob(os.path.join(MEDIA_DIR, "*.png")) + glob.glob(os.path.join(MEDIA_DIR, "*.jpg")) + glob.glob(os.path.join(MEDIA_DIR, "*.mp3")) + glob.glob(os.path.join(MEDIA_DIR, "*.mp4")) + glob.glob(os.path.join(AUDIO_DIR, "*.mp3"))
944
- shared_memory = get_shared_memory()
945
- own_files = [f for f in all_files if st.session_state.user_id in os.path.basename(f) or st.session_state.username in os.path.basename(f)]
946
- shared_files = [f for f in all_files if f not in own_files and not f in [CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE, os.path.join(AUDIO_DIR, "*"), os.path.join(MEDIA_DIR, "*")]]
947
-
948
- st.markdown("###### Your Files 📂")
949
- st.markdown("###### Image Gallery 🖼")
950
- own_image_files = [f for f in own_files if f.endswith(('.png', '.jpg'))]
951
- image_cols = st.slider("Image Gallery Columns 🖼 (Own)", min_value=1, max_value=15, value=5)
952
- cols = st.columns(image_cols)
953
- for idx, image_file in enumerate(own_image_files):
954
- with cols[idx % image_cols]:
955
- st.image(image_file, use_container_width=True)
956
- shared_memory.update_media(image_file) # Cache media
957
-
958
- st.markdown("###### Video Gallery 🎥")
959
- own_video_files = [f for f in own_files if f.endswith('.mp4')]
960
- video_cols = st.slider("Video Gallery Columns 🎬 (Own)", min_value=1, max_value=5, value=3)
961
- cols = st.columns(video_cols)
962
- for idx, video_file in enumerate(own_video_files):
963
- with cols[idx % video_cols]:
964
- st.markdown(get_video_html(video_file), unsafe_allow_html=True)
965
- shared_memory.update_media(video_file) # Cache media
966
-
967
- st.markdown("###### Audio Gallery 🎧")
968
- own_audio_files = [f for f in own_files if f.endswith(('.mp3', '.wav')) or f.startswith(os.path.join(AUDIO_DIR, "audio_"))]
969
- audio_cols = st.slider("Audio Gallery Columns 🎶 (Own)", min_value=1, max_value=15, value=5)
970
- cols = st.columns(audio_cols)
971
- for idx, audio_file in enumerate(own_audio_files):
972
- with cols[idx % audio_cols]:
973
- st.markdown(await get_audio_html(audio_file), unsafe_allow_html=True)
974
- shared_memory.update_media(audio_file) # Cache media
975
-
976
- st.markdown("###### Shared Files 📤")
977
- st.markdown("###### Image Gallery 🖼")
978
- shared_image_files = [f for f in shared_files if f.endswith(('.png', '.jpg'))]
979
- image_cols = st.slider("Image Gallery Columns 🖼 (Shared)", min_value=1, max_value=15, value=5)
980
- cols = st.columns(image_cols)
981
- for idx, image_file in enumerate(shared_image_files):
982
- with cols[idx % image_cols]:
983
- st.image(image_file, use_container_width=True)
984
- shared_memory.update_media(image_file) # Cache media
985
-
986
- st.markdown("###### Video Gallery 🎥")
987
- shared_video_files = [f for f in shared_files if f.endswith('.mp4')]
988
- video_cols = st.slider("Video Gallery Columns 🎬 (Shared)", min_value=1, max_value=5, value=3)
989
- cols = st.columns(video_cols)
990
- for idx, video_file in enumerate(shared_video_files):
991
- with cols[idx % video_cols]:
992
- st.markdown(get_video_html(video_file), unsafe_allow_html=True)
993
- shared_memory.update_media(video_file) # Cache media
994
-
995
- st.markdown("###### Audio Gallery 🎧")
996
- shared_audio_files = [f for f in shared_files if f.endswith(('.mp3', '.wav')) or f.startswith(os.path.join(AUDIO_DIR, "audio_"))]
997
- audio_cols = st.slider("Audio Gallery Columns 🎶 (Shared)", min_value=1, max_value=15, value=5)
998
- cols = st.columns(audio_cols)
999
- for idx, audio_file in enumerate(shared_audio_files):
1000
- with cols[idx % audio_cols]:
1001
- st.markdown(await get_audio_html(audio_file), unsafe_allow_html=True)
1002
- shared_memory.update_media(audio_file) # Cache media
1003
-
1004
- # Full Log at End with Download - a shared epic’s end, our tale to mend! 📜📥
1005
- with st.container():
1006
- st.markdown("###### Full Chat Log 📜")
1007
- with open(CHAT_FILE, 'r') as f:
1008
- history_content = f.read()
1009
- st.markdown(history_content)
1010
- st.download_button("Download Chat Log as .md", history_content, file_name=f"chat_{st.session_state.user_id}.md", mime="text/markdown")
1011
-
1012
- # Clear Cache Button - purge the cache, a shared epic’s crash! 🗑️🔄
1013
- if st.button("Clear Shared Memory Cache", key="clear_cache"):
1014
  shared_memory = get_shared_memory()
1015
- shared_memory.clear()
1016
- st.success("Shared memory cache cleared, a fresh start with a bard’s heart! 🌟🎶")
 
 
 
 
 
 
 
 
 
 
 
 
1017
 
1018
- loop.run_until_complete(async_interface())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1019
 
1020
  if __name__ == "__main__":
1021
  """
1022
- Launch our saga, a shared memory’s cheer, in Streamlit’s cache, our epic year! 🚀✨
1023
  """
1024
  main()
 
23
  from PyPDF2 import PdfReader
24
  import json
25
 
26
+ # Patch for nested async - sneaky fix, a loop’s heroic mix! 🐍✨
27
  nest_asyncio.apply()
28
 
29
+ # Static config - constants rule, a cosmic tool, our saga’s jewel! 📏👑
30
  icons = '🤖🧠🔬📝'
31
  START_ROOM = "Sector 🌌"
32
 
33
+ # Page setup - dressing up with flair, a UI so rare, in Streamlit’s glare! 🖼️🎀
34
  st.set_page_config(
35
  page_title="🤖🧠MMO Chat Brain📝🔬",
36
  page_icon=icons,
 
38
  initial_sidebar_state="auto"
39
  )
40
 
41
+ # Funky usernames with voices - a cosmic cast, shared memory’s blast! 🌌🎭
42
  FUN_USERNAMES = {
43
  "CosmicJester 🌌": "en-US-AriaNeural",
44
  "PixelPanda 🐼": "en-US-JennyNeural",
 
62
  "ChronoChimp 🐒": "en-GB-LibbyNeural"
63
  }
64
 
65
+ # Top-level files and directories - the treasure trove, shared memory’s grove! 🗺️📜
66
  CHAT_FILE = "global_chat.md"
67
  QUOTE_VOTES_FILE = "quote_votes.md"
68
  MEDIA_VOTES_FILE = "media_votes.md"
 
73
  os.makedirs(AUDIO_DIR, exist_ok=True)
74
  os.makedirs(MEDIA_DIR, exist_ok=True)
75
 
76
+ # Fancy digits - numbers dance, in shared cache, a numeric trance! 🔢💃
77
  UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
78
 
79
+ # Massive font collection - typography’s spree, in shared cache, a scribe’s decree! 🖋️🎨
80
  UNICODE_FONTS = [
81
  ("Normal", lambda x: x),
82
  ("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)),
 
99
  ("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
100
  ]
101
 
102
+ # Global state - keeping tabs, a shared saga’s grab! 🌍📋 – epic memory’s lab!
103
  if 'server_running' not in st.session_state:
104
  st.session_state.server_running = False
105
  if 'server_task' not in st.session_state:
 
133
  if 'user_hash' not in st.session_state:
134
  st.session_state.user_hash = None
135
 
136
+ # Timestamp wizardry - clock ticks with flair, a temporal affair, in shared air! ⏰🎩
137
  def format_timestamp_prefix(username):
138
  central = pytz.timezone('US/Central')
139
  now = datetime.now(central)
140
  return f"{username}-{now.strftime('%I-%M-%p-%m-%d-%Y')}-{st.session_state.user_id}"
141
 
142
+ # Compute image hash - a cryptographic clash, securing our flash, in shared dash! 🛡️🔍
143
  def compute_image_hash(image_data):
144
  if isinstance(image_data, Image.Image):
145
  img_byte_arr = io.BytesIO()
 
149
  img_bytes = image_data
150
  return hashlib.md5(img_bytes).hexdigest()[:8]
151
 
152
+ # Node naming - christening the beast, a heroic feast, in shared yeast! 🌐🍼
153
  def get_node_name():
154
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
155
  parser.add_argument('--node-name', type=str, default=None)
 
159
  log_action(username, "🌐🍼 - Node naming - christening the beast!")
160
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
161
 
162
+ # Action logger - spying on deeds, a stealthy steed, in shared need! 🕵️📜
163
  def log_action(username, action):
164
  if 'action_log' not in st.session_state:
165
  st.session_state.action_log = {}
 
173
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
174
  user_log[action] = current_time
175
 
176
+ # Clean text - strip the fancy fluff, a scribe’s tough bluff, in shared puff! 🧹📝
177
  def clean_text_for_tts(text):
178
  cleaned = re.sub(r'[#*!\[\]]+', '', text)
179
  cleaned = ' '.join(cleaned.split())
180
  return cleaned if cleaned else "No text to speak" # Default if empty
181
 
182
+ # Shared Memory Cache - the epic vault, a shared memory assault, in Streamlit’s fault! 🗳️💾
183
  @st.cache_resource
184
  def get_shared_memory():
185
  """
 
193
  self.cache_time = datetime.now() # Timestamp of glory, a hero’s blend!
194
 
195
  def update_chat(self, entry):
196
+ """Add a chat entry, a rhyme in the stream, keeping our saga supreme, in shared dream! 💬🎶"""
197
  self.chat_history.append(entry)
198
  if len(self.chat_history) > 100: # Limit to keep it tight, a knight’s fight!
199
  self.chat_history.pop(0)
200
 
201
  def update_media(self, media_path):
202
+ """Store media files, a visual dream, in our shared cache, a radiant beam, in shared gleam! 🖼️🌟"""
203
  self.media_files.append(media_path)
204
  if len(self.media_files) > 50: # Cap the hoard, lest it grow too wide!
205
  self.media_files.pop(0)
206
 
207
  def get_condensed_dialogs(self):
208
+ """Condense the chatter, a poetic pact, short and sweet, our story intact, in shared act! 🗣️✨"""
209
  return "\n".join(f"- {entry.split(': ')[1][:50]}" for entry in self.chat_history[-10:])
210
 
211
  def clear(self):
212
+ """Clear the cache, a dramatic purge, resetting our tale, a new surge, in shared urge! 🌀🔥"""
213
  self.chat_history.clear()
214
  self.media_files.clear()
215
  self.cache_time = datetime.now()
216
 
217
  return SharedMemory()
218
 
219
+ # Audio Processor Class - voices echo, a sonic hero, in shared zero! 🎶🌟
220
  class AudioProcessor:
221
  def __init__(self):
222
  self.cache_dir = AUDIO_DIR
 
234
 
235
  async def create_audio(self, text, voice='en-US-AriaNeural', filename=None):
236
  """
237
+ Create audio, a voice that roars, in our shared cache, through cosmic doors, in shared shores! 🎤🌌
238
  """
239
  cache_key = hashlib.md5(f"{text}:{voice}".encode()).hexdigest()
240
  timestamp = format_timestamp_prefix(st.session_state.username)
 
243
  if cache_key in self.metadata and os.path.exists(filename):
244
  return filename
245
 
246
+ # Clean text for speech, a bard’s clean sweep, in shared deep!
247
  text = text.replace("\n", " ").replace("</s>", " ").strip()
248
  if not text:
249
  return None
250
 
251
+ # Generate audio, a sonic leap, with edge_tts, our voices deep, in shared keep!
252
  try:
253
  communicate = edge_tts.Communicate(text, voice)
254
  await communicate.save(filename)
 
258
  log_action("System 🌟", f"TTS failed for text '{text}' with voice '{voice}': {str(e)}")
259
  return None
260
 
261
+ # Update metadata, our epic creed, in shared memory, a heroic deed, in shared need!
262
  self.metadata[cache_key] = {
263
  'timestamp': datetime.now().isoformat(),
264
  'text_length': len(text),
 
268
 
269
  return filename
270
 
271
+ # Chat saver - words locked tight, in shared memory’s light, a shared fight! 💬🔒
272
  async def save_chat_entry(username, message, is_markdown=False, quote_line=None, media_file=None, skip_audio=False):
273
  """
274
+ Save chats with flair, in shared cache we dare, a rhyming affair, in shared air! ✨🎉
275
  """
 
276
  central = pytz.timezone('US/Central')
277
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
278
  user_history_file = f"{username}_history.md"
279
  voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
280
  indent = " " if quote_line else "" # Nesting for replies, a poetic spree!
281
 
282
+ # Prepare entry, a verse so bright, in shared memory’s sight, a shared delight!
283
  if is_markdown:
284
  entry = f"{indent}[{timestamp}] {username}:\n{indent}```markdown\n{indent}{message}\n{indent}```"
285
  else:
 
287
  if quote_line:
288
  entry = f"{indent}> {quote_line}\n{entry}"
289
 
290
+ # Save to global chat file, a shared epic tale, never frail, in shared gale!
291
  with open(CHAT_FILE, 'a') as f:
292
  f.write(f"{entry}\n")
293
 
294
+ # Save to user-specific history, a personal rhyme, in shared time, a shared chime!
295
  if not os.path.exists(user_history_file):
296
  with open(user_history_file, 'w') as f:
297
  f.write(f"# Chat History for {username} (Voice: {voice})\n\n")
298
  with open(user_history_file, 'a') as f:
299
  f.write(f"{entry}\n")
300
 
301
+ # Generate audio, unless we skip, in shared cache, a sonic grip, in shared rip!
302
+ audio_filename = None # Initialize, lest our tale derail, in shared veil!
303
  if not skip_audio and message.strip():
304
  cleaned_message = clean_text_for_tts(message)
305
  audio_processor = AudioProcessor()
 
312
  with open(CHAT_FILE, 'a') as f:
313
  f.write(f"{indent}[{timestamp}] Audio: {audio_filename}\n")
314
 
315
+ # Handle media files, a visual quest, in shared memory, our treasure chest, in shared zest!
316
  if media_file:
317
  if isinstance(media_file, Image.Image):
318
  timestamp_prefix = format_timestamp_prefix(username)
 
340
  with open(user_history_file, 'a') as f:
341
  f.write(f"{indent}[{timestamp}] Media: ![Media]({media_file})\n")
342
 
343
+ # Update shared memory, our epic lore, in cache forevermore, in shared core!
344
  shared_memory = get_shared_memory()
345
  shared_memory.update_chat(entry)
346
  if media_file:
 
348
 
349
  await broadcast_message(f"{username}|{message}", "chat")
350
  st.session_state.last_chat_update = time.time()
351
+ return audio_filename # Return, even if silent, our tale’s delight, in shared flight!
352
 
353
+ # Save chat history with image or PDF - a scribe’s historic flight, in shared night! 📜🚀
354
  async def save_chat_history_with_image(username, image_path):
355
  """
356
+ Save history, a scribe’s grand sight, in shared memory, pure and bright, in shared light! ✨📖
357
  """
358
  central = pytz.timezone('US/Central')
359
  timestamp = datetime.now(central).strftime("%Y-%m-%d_%H-%M-%S")
 
367
  f.write(f"[{timestamp}] {username} (Voice: {voice}) Shared Media: {os.path.basename(image_path)}\n")
368
  f.write(f"```markdown\n{chat_content}\n```\n")
369
 
370
+ # Chat loader - history unleashed, a shared epic feast, in shared beast! 📜🚀
371
  @st.cache_resource
372
  async def load_chat():
373
  """
374
+ Load chats, a shared memory spree, from cache with glee, our history free, in shared sea! 🌟💬
375
  """
376
  username = st.session_state.get('username', 'System 🌟')
377
  await asyncio.to_thread(log_action, username, "📜🚀 - Chat loader - history unleashed!")
 
381
  content = await asyncio.to_thread(f.read)
382
  return content
383
 
384
+ # User lister - who’s in the gang, a shared memory bang, in shared rang! 👥🎉
385
  async def get_user_list(chat_content):
386
  """
387
+ List users, a shared roster’s rhyme, in cache divine, through space and time, in shared chime! 🌍🎭
388
  """
389
  username = st.session_state.get('username', 'System 🌟')
390
  await asyncio.to_thread(log_action, username, "👥🎉 - User lister - who’s in the gang!")
 
395
  users.add(user)
396
  return sorted(list(users))
397
 
398
+ # Join checker - been here before, in shared cache, a lore galore, in shared shore! 🚪🔍
399
  async def has_joined_before(client_id, chat_content):
400
  """
401
+ Check joins, a shared memory chore, in cache secure, forevermore, in shared lore! 🌀🔐
402
  """
403
  username = st.session_state.get('username', 'System 🌟')
404
  await asyncio.to_thread(log_action, username, "🚪🔍 - Join checker - been here before?")
405
  return any(f"Client-{client_id}" in line for line in chat_content.split('\n'))
406
 
407
+ # Suggestion maker - old quips resurface, in shared cache, a verse so fierce, in shared pierce! 💡📝
408
  async def get_message_suggestions(chat_content, prefix):
409
  """
410
+ Suggest quips, a shared memory jest, in cache we nest, our humor blessed, in shared zest! 😂🌟
411
  """
412
  username = st.session_state.get('username', 'System 🌟')
413
  await asyncio.to_thread(log_action, username, "💡📝 - Suggestion maker - old quips resurface!")
 
415
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
416
  return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
417
 
418
+ # Vote saver - cheers recorded, in shared cache, our cheers restored, in shared chord! 👍📊
419
  @st.cache_resource
420
  async def save_vote(file, item, user_hash, username, comment=""):
421
  """
422
+ Save votes, a shared tally’s cheer, in cache so clear, our triumph near, in shared spear! 🏆🎉
423
  """
424
  await asyncio.to_thread(log_action, username, "👍📊 - Vote saver - cheers recorded!")
425
  central = pytz.timezone('US/Central')
 
431
  if comment:
432
  chat_message += f" - {comment}"
433
  await save_chat_entry(username, chat_message)
434
+ return entry # Return for caching, our epic fling, in shared ring!
435
 
436
+ # Vote counter - tallying the love, in shared cache, a tale above, in shared glove! 🏆📈
437
  @st.cache_resource
438
  async def load_votes(file):
439
  """
440
+ Count votes, a shared tally’s might, in cache so bright, our victory’s light, in shared fight! 🌟📊
441
  """
442
  username = st.session_state.get('username', 'System 🌟')
443
  await asyncio.to_thread(log_action, username, "🏆📈 - Vote counter - tallying the love!")
 
458
  user_votes.add(vote_key)
459
  return votes
460
 
461
+ # Hash generator - secret codes ahoy, in shared cache, a cryptic joy, in shared toy! 🔑🕵️
462
  async def generate_user_hash():
463
  """
464
+ Generate hashes, a shared code’s chime, in cache sublime, through space and time, in shared rhyme! 🌌🔐
465
  """
466
  username = st.session_state.get('username', 'System 🌟')
467
  await asyncio.to_thread(log_action, username, "🔑🕵️ - Hash generator - secret codes ahoy!")
 
469
  st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
470
  return st.session_state.user_hash
471
 
472
+ # Audio maker - voices come alive, in shared cache, a sonic dive, in shared hive! 🎶🌟
473
  async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
474
  """
475
+ Make audio, a shared voice’s thrill, in cache we fill, with sonic will, in shared hill! 🎤🔊
476
  """
477
  username = st.session_state.get('username', 'System 🌟')
478
  await asyncio.to_thread(log_action, username, "🎶🌟 - Audio maker - voices come alive!")
 
488
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: Audio failed - No audio received for '{text}'\n")
489
  return None
490
 
491
+ # Audio player - tunes blast off, in shared cache, a musical scoff, in shared toff! 🔊🚀
492
  def play_and_download_audio(file_path):
493
  """
494
+ Play tunes, a shared melody’s jest, in cache expressed, our audio quest, in shared zest! 🎵🌌
495
  """
496
  if file_path and os.path.exists(file_path):
497
  st.audio(file_path)
 
500
  dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}">🎵 Download {os.path.basename(file_path)}</a>'
501
  st.markdown(dl_link, unsafe_allow_html=True)
502
 
503
+ # Image saver - pics preserved, in shared cache, a visual burst, in shared thirst! 📸💾
504
  async def save_pasted_image(image, username):
505
  """
506
+ Save images, a shared sight’s cheer, in cache so clear, our vision near, in shared spear! 🖼️🌟
507
  """
508
  await asyncio.to_thread(log_action, username, "📸💾 - Image saver - pics preserved!")
509
  timestamp = format_timestamp_prefix(username)
 
514
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(img_byte_arr.getvalue()))
515
  return media_filename
516
 
517
+ # Video and Audio savers - media magic, in shared cache, a heroic tragic, in shared magic! 🎥🎶
518
  async def save_media(file, username, ext):
519
  """
520
+ Save media, a shared epic’s might, in cache so bright, our treasures ignite, in shared kite! 🧙🔥
521
  """
522
  await asyncio.to_thread(log_action, username, f"📸💾 - Media saver - {ext} preserved!")
523
  timestamp = format_timestamp_prefix(username)
 
526
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(file.getbuffer()))
527
  return media_filename
528
 
529
+ # PDF saver and audio generator - documents dance, in shared cache, a chance, in shared trance! 📜🎶
530
  async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
531
  """
532
+ Save PDFs, a shared document’s glee, in cache we see, our history’s key, in shared sea! 📚🌟
533
  """
534
  await asyncio.to_thread(log_action, username, "📜🎶 - PDF saver and audio generator!")
535
  timestamp = format_timestamp_prefix(username)
 
558
 
559
  return pdf_filename, texts, audio_files
560
 
561
+ # Video renderer - movies roll, in shared cache, a visual toll, in shared roll! 🎥🎬
562
  def get_video_html(video_path, width="100px"):
563
  """
564
+ Render videos, a shared screen’s thrill, in cache we fill, with cinematic will, in shared hill! 📺🌌
565
  """
566
  video_url = f"data:video/mp4;base64,{base64.b64encode(open(os.path.join(MEDIA_DIR, video_path), 'rb').read()).decode()}"
567
  return f'''
 
571
  </video>
572
  '''
573
 
574
+ # Audio renderer - sounds soar, in shared cache, a sonic roar, in shared roar! 🎶✈️
575
  async def get_audio_html(audio_path, width="100px"):
576
  """
577
+ Render audio, a shared sound’s cheer, in cache so clear, our music near, in shared spear! 🎵🌟
578
  """
579
  username = st.session_state.get('username', 'System 🌟')
580
  await asyncio.to_thread(log_action, username, "🎶✈️ - Audio renderer - sounds soar!")
 
586
  </audio>
587
  '''
588
 
589
+ # Websocket handler - chat links up, in shared cache, a connection’s cup, in shared cup! 🌐🔗
590
  async def websocket_handler(websocket, path):
591
  """
592
+ Handle chats, a shared link’s might, in cache so bright, our network’s light, in shared night! 🌍🔌
593
  """
594
  username = st.session_state.get('username', 'System 🌟')
595
  await asyncio.to_thread(log_action, username, "🌐🔗 - Websocket handler - chat links up!")
 
612
  if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
613
  del st.session_state.active_connections[room_id][client_id]
614
 
615
+ # Message broadcaster - words fly far, in shared cache, a starry czar, in shared star! 📢✈️
616
  async def broadcast_message(message, room_id):
617
  """
618
+ Broadcast words, a shared echo’s cheer, in cache so clear, our message near, in shared spear! 🌠📡
619
  """
620
  username = st.session_state.get('username', 'System 🌟')
621
  await asyncio.to_thread(log_action, username, "📢✈️ - Message broadcaster - words fly far!")
 
629
  for client_id in disconnected:
630
  del st.session_state.active_connections[room_id][client_id]
631
 
632
+ # Server starter - web spins up, in shared cache, a digital pup, in shared pup! 🖥️🌀
633
  async def run_websocket_server():
634
  """
635
+ Start server, a shared spin’s delight, in cache so right, our web takes flight, in shared night! 🚀🌐
636
  """
637
  username = st.session_state.get('username', 'System 🌟')
638
  await asyncio.to_thread(log_action, username, "🖥️🌀 - Server starter - web spins up!")
 
641
  st.session_state.server_running = True
642
  await server.wait_closed()
643
 
644
+ # Delete all user files - a purge so grand, in shared cache, a clearing band, in shared land! 🗑️🔥
645
  def delete_user_files():
646
  """
647
+ Delete files, a shared purge’s might, in cache so light, our slate wiped tight, in shared night! 🧹🌌
648
  """
649
  protected_files = {'app.py', 'requirements.txt', 'README.md', CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE, AUDIO_DIR, MEDIA_DIR}
650
  deleted_files = []
 
675
  st.session_state.audio_cache.clear()
676
  st.session_state.base64_cache.clear()
677
  st.session_state.displayed_chat_lines.clear()
678
+ shared_memory = get_shared_memory()
679
+ shared_memory.clear() # Clear shared cache on purge, in shared surge!
680
  return deleted_files
681
 
682
+ # Query parameter checker - parse q with flair, in shared cache, a naming affair, in shared air! 🚪🔍
683
  def check_query_params():
684
  """
685
+ Check queries, a shared name’s quest, in cache so blessed, our path expressed, in shared zest! 🌟🔍
686
  """
687
  query_params = st.query_params if hasattr(st, 'query_params') else st.experimental_get_query_params()
688
  q_value = query_params.get("q", [None])[0]
 
694
  st.session_state.user_id = q_value # Use as user_id if not a valid username
695
  return None
696
 
697
+ # Mermaid graph generator - visualize our tale, in shared cache, a graphic gale, in shared vale! 🌳📈
698
  def generate_mermaid_graph(chat_lines):
699
  """
700
+ Generate graphs, a shared vision’s rhyme, in cache sublime, our story’s chime, in shared time! 🧩🌌
701
  """
702
  mermaid_code = "graph TD\n"
703
  nodes = {}
 
728
  mermaid_code += "\n".join(f" {edge}" for edge in edges)
729
  return mermaid_code
730
 
731
+ # Main execution - let’s roll, in shared cache, a heroic toll, in shared soul! 🎲🚀
732
  def main():
733
  """
734
+ Launch our saga, a shared memory’s cheer, in Streamlit’s cache, our epic year, in shared spear! 🚀✨
735
  """
736
  NODE_NAME, port = get_node_name()
737
 
738
+ # Use asyncio.run to manage the event loop cleanly, avoiding nested awaits! 🌀🔄
739
+ asyncio.run(async_interface())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
 
741
+ # Async interface - our epic quest, in shared cache, a tale expressed, in shared zest! 🎭🌟
742
+ async def async_interface():
743
+ # Generate user ID and hash, a shared identity’s clash, in shared flash! 🆔🔮
744
+ if not st.session_state.user_id:
745
+ st.session_state.user_id = str(uuid.uuid4())
746
+ st.session_state.user_hash = await generate_user_hash()
 
 
 
 
 
 
747
 
748
+ # Check query params, a shared name’s quest, in cache expressed, our path impressed, in shared zest! 🚪🔍
749
+ q_value = check_query_params()
750
+ if not q_value and 'username' not in st.session_state:
751
+ chat_content = await load_chat()
752
+ available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
753
+ st.session_state.username = random.choice(available_names) if available_names else random.choice(list(FUN_USERNAMES.keys()))
754
+ st.session_state.voice = FUN_USERNAMES[st.session_state.username]
755
+ st.markdown(f"**🎙️ Voice Selected**: {st.session_state.voice} 🗣️ for {st.session_state.username}")
756
+
757
+ # Check existing history, a shared memory’s cheer, in cache so clear, our lore near, in shared spear! 📜🌟
758
+ user_history_file = f"{st.session_state.username}_history.md"
759
+ if os.path.exists(user_history_file):
760
+ with open(user_history_file, 'r') as f:
761
+ st.session_state.displayed_chat_lines = f.read().split('\n')
762
+
763
+ user_url = f"/q={st.session_state.username}"
764
+ with st.container():
765
+ st.markdown(f"<small>Your unique URL path: [{user_url}]({user_url})</small>", unsafe_allow_html=True)
766
+
767
+ with st.container():
768
+ st.markdown(f"#### 🤖🧠MMO {st.session_state.username}📝🔬")
769
+ st.markdown(f"<small>Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting! 🎉 User ID: {st.session_state.user_id}</small>", unsafe_allow_html=True)
770
+
771
+ if not st.session_state.server_task:
772
+ st.session_state.server_task = asyncio.create_task(run_websocket_server())
773
+
774
+ # Unified Chat History at Top - a shared epic’s roar, condensed and stored, in shared cord! 💬🌌
775
+ with st.container():
776
+ st.markdown(f"##### {START_ROOM} Chat History 💬")
777
+ shared_memory = get_shared_memory()
778
+ chat_content = await load_chat()
779
+ chat_lines = [line for line in chat_content.split('\n') if line.strip() and not line.startswith('#')]
780
+ if chat_lines:
781
+ chat_by_minute = {}
782
+ for line in reversed(chat_lines):
783
+ timestamp = line.split('] ')[0][1:] if '] ' in line else "Unknown"
784
+ minute_key = timestamp[:16] # Up to minute
785
+ if minute_key not in chat_by_minute:
786
+ chat_by_minute[minute_key] = []
787
+ chat_by_minute[minute_key].append(line)
788
+
789
+ markdown_output = ""
790
+ for minute, lines in chat_by_minute.items():
791
+ minute_output = f"###### {minute[-5:]}\n" # Show only HH:MM
792
+ for line in lines:
793
+ if ': ' in line and not line.startswith(' '):
794
+ user_message = line.split(': ', 1)[1]
795
+ user = user_message.split(' ')[0]
796
+ msg = user_message.split(' ', 1)[1] if ' ' in user_message else ''
797
+ audio_html = ""
798
+ media_content = ""
799
+ next_lines = chat_lines[chat_lines.index(line)+1:chat_lines.index(line)+3]
800
+ for nl in next_lines:
801
+ if "Audio:" in nl:
802
+ audio_file = nl.split("Audio: ")[-1].strip()
803
+ audio_html = play_and_download_audio(audio_file)
804
+ elif "Media:" in nl:
805
+ media_file = nl.split("Media: ")[-1].strip('![]()')
806
+ media_path = os.path.join(MEDIA_DIR, media_file)
807
+ if os.path.exists(media_path):
808
+ if media_file.endswith(('.png', '.jpg')):
809
+ media_content = f"<img src='file://{media_path}' width='100'>"
810
+ elif media_file.endswith('.mp4'):
811
+ media_content = get_video_html(media_file)
812
+ elif media_file.endswith('.mp3'):
813
+ media_content = await get_audio_html(media_file)
814
+ elif media_file.endswith('.pdf'):
815
+ media_content = f"📜 {os.path.basename(media_file)}"
816
+ minute_output += f"- 💬 **{user}**: {msg[:50]}... {audio_html} {media_content}\n" # Condensed dialog
817
+ markdown_output += minute_output
818
+ st.markdown(markdown_output, unsafe_allow_html=True)
819
+ shared_memory.update_chat(markdown_output) # Cache condensed dialogs
820
+
821
+ # Condensed Dialogs Display - a shared tale, concise and hale, in shared vale! 🗣️✨
822
+ st.markdown("###### Condensed Dialogs 🗣️")
823
+ condensed_dialogs = shared_memory.get_condensed_dialogs()
824
+ st.markdown(condensed_dialogs)
825
+
826
+ # Mermaid Graph Visualization - a shared map, our chat’s trap, in shared cap! 🌳📈
827
+ st.markdown("###### Chat Relationship Tree 🌳")
828
+ mermaid_code = generate_mermaid_graph(chat_lines)
829
+ mermaid_html = f"""
830
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
831
+ <div class="mermaid" style="height: 200px; overflow: auto;">{mermaid_code}</div>
832
+ <script>mermaid.initialize({{startOnLoad:true}});</script>
833
+ """
834
+ components.html(mermaid_html, height=250)
835
+
836
+ with st.container():
837
+ if st.session_state.quote_line:
838
+ st.markdown(f"###### Quoting: {st.session_state.quote_line}")
839
+ quote_response = st.text_area("Add your response", key="quote_response", value=st.session_state.message_text)
840
+ paste_result_quote = paste_image_button("📋 Paste Image or Text with Quote", key="paste_button_quote")
841
+ if paste_result_quote.image_data is not None:
842
+ if isinstance(paste_result_quote.image_data, str):
843
+ st.session_state.message_text = paste_result_quote.image_data
844
+ st.text_area("Add your response", key="quote_response", value=st.session_state.message_text)
845
  else:
846
+ st.image(paste_result_quote.image_data, caption="Received Image for Quote")
847
+ filename = await save_pasted_image(paste_result_quote.image_data, st.session_state.username)
848
  if filename:
849
+ st.session_state.pasted_image_data = filename
850
+ await save_chat_entry(st.session_state.username, f"Pasted image: {filename}", quote_line=st.session_state.quote_line, media_file=paste_result_quote.image_data)
851
+ if st.button("Send Quote 🚀", key="send_quote"):
852
+ markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
853
+ if st.session_state.pasted_image_data:
854
+ markdown_response += f"\n- **Image**: ![Pasted Image]({st.session_state.pasted_image_data})"
855
+ await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}", quote_line=st.session_state.quote_line, media_file=st.session_state.pasted_image_data)
856
+ st.session_state.pasted_image_data = None
857
+ await save_chat_entry(st.session_state.username, markdown_response, is_markdown=True, quote_line=st.session_state.quote_line)
858
+ st.session_state.quote_line = None
859
+ st.session_state.message_text = ''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
  st.rerun()
861
 
862
+ current_selection = st.session_state.username if st.session_state.username in FUN_USERNAMES else ""
863
+ 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")
864
+ if new_username and new_username != st.session_state.username:
865
+ await save_chat_entry("System 🌟", f"{st.session_state.username} changed name to {new_username}")
866
+ st.session_state.username = new_username
867
+ st.session_state.voice = FUN_USERNAMES[new_username]
868
+ st.markdown(f"**🎙️ Voice Changed**: {st.session_state.voice} 🗣️ for {st.session_state.username}")
 
 
869
  st.rerun()
870
 
871
+ # Message input with Send button - a shared chat’s might, in shared night! 💬🚀
872
+ col_input, col_send = st.columns([5, 1])
873
+ with col_input:
874
+ 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)
875
+ with col_send:
876
+ if st.button("Send 🚀", key="send_button"):
877
+ if message.strip() or st.session_state.pasted_image_data:
878
+ await save_chat_entry(st.session_state.username, message if message.strip() else "Image shared", is_markdown=True, media_file=st.session_state.pasted_image_data if st.session_state.pasted_image_data else None, skip_audio=not message.strip())
879
+ if st.session_state.pasted_image_data:
880
+ st.session_state.pasted_image_data = None
881
+ st.session_state.message_text = ''
882
+ st.rerun()
883
+
884
+ paste_result_msg = paste_image_button("📋 Paste Image or Text with Message", key="paste_button_msg")
885
+ if paste_result_msg.image_data is not None:
886
+ if isinstance(paste_result_msg.image_data, str):
887
+ st.session_state.message_text = paste_result_msg.image_data
888
+ 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)
889
+ else:
890
+ st.image(paste_result_msg.image_data, caption="Received Image for Quote")
891
+ filename = await save_pasted_image(paste_result_msg.image_data, st.session_state.username)
892
+ if filename:
893
+ await save_chat_entry(st.session_state.username, "Image shared", is_markdown=True, media_file=paste_result_msg.image_data, skip_audio=True)
894
+ st.session_state.pasted_image_data = None
895
+ st.rerun()
896
+
897
+ with st.container():
898
+ st.markdown("###### Upload Media 🎨🎶📜🎥")
899
+ uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3', 'wav', 'pdf', 'txt', 'md', 'py'])
900
+ if uploaded_file:
901
+ timestamp = format_timestamp_prefix(st.session_state.username)
902
+ username = st.session_state.username
903
+ ext = uploaded_file.name.split('.')[-1].lower()
904
+ if ext in ['png', 'jpg']:
905
+ filename = await save_pasted_image(uploaded_file, username)
906
+ elif ext in ['mp4', 'mp3', 'wav']:
907
+ filename = await save_media(uploaded_file, username, ext)
908
+ elif ext == 'pdf':
909
+ pdf_filename, _, _ = await save_pdf_and_generate_audio(uploaded_file, username)
910
+ filename = pdf_filename
911
+ else:
912
+ filename = f"{username}-{timestamp.split('-')[-1]}.{ext}"
913
+ media_path = os.path.join(MEDIA_DIR, filename)
914
+ await asyncio.to_thread(lambda: open(media_path, 'wb').write(uploaded_file.getbuffer()))
915
+ await save_chat_entry(username, f"Uploaded {ext.upper()}: {os.path.basename(filename)}", media_file=uploaded_file, skip_audio=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
916
  shared_memory = get_shared_memory()
917
+ shared_memory.update_media(os.path.join(MEDIA_DIR, filename)) # Cache media
918
+ st.success(f"Uploaded {filename}")
919
+
920
+ # Big Red Delete Button - a purge so grand, clearing our shared land, in shared sand! 🗑️🔥
921
+ st.markdown("###### 🛑 Danger Zone")
922
+ 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):
923
+ deleted_files = delete_user_files()
924
+ if deleted_files:
925
+ st.markdown("### 🗑️ Deleted Files:\n" + "\n".join([f"- `{file}`" for file in deleted_files]))
926
+ shared_memory = get_shared_memory()
927
+ shared_memory.clear() # Clear shared cache on purge, in shared surge!
928
+ else:
929
+ st.markdown("### 🗑️ Nothing to Delete!")
930
+ st.rerun()
931
 
932
+ st.markdown("###### Refresh ⏳")
933
+ refresh_rate = st.slider("Refresh Rate", 1, 300, st.session_state.refresh_rate)
934
+ st.session_state.refresh_rate = refresh_rate
935
+ timer_placeholder = st.empty()
936
+ for i in range(st.session_state.refresh_rate, -1, -1):
937
+ font_name, font_func = random.choice(UNICODE_FONTS)
938
+ countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
939
+ timer_placeholder.markdown(f"<small>⏳ {font_func('Refresh in:')} {countdown_str}</small>", unsafe_allow_html=True)
940
+ await asyncio.sleep(1) # Use asyncio.sleep for async context, in shared nest!
941
+ st.rerun()
942
+
943
+ # Separate Galleries for Own and Shared Files - a shared gallery’s glare, in shared air! 🖼️🎥
944
+ with st.container():
945
+ all_files = glob.glob(os.path.join(MEDIA_DIR, "*.md")) + glob.glob(os.path.join(MEDIA_DIR, "*.pdf")) + glob.glob(os.path.join(MEDIA_DIR, "*.txt")) + glob.glob(os.path.join(MEDIA_DIR, "*.py")) + glob.glob(os.path.join(MEDIA_DIR, "*.png")) + glob.glob(os.path.join(MEDIA_DIR, "*.jpg")) + glob.glob(os.path.join(MEDIA_DIR, "*.mp3")) + glob.glob(os.path.join(MEDIA_DIR, "*.mp4")) + glob.glob(os.path.join(AUDIO_DIR, "*.mp3"))
946
+ shared_memory = get_shared_memory()
947
+ own_files = [f for f in all_files if st.session_state.user_id in os.path.basename(f) or st.session_state.username in os.path.basename(f)]
948
+ shared_files = [f for f in all_files if f not in own_files and not f in [CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE, os.path.join(AUDIO_DIR, "*"), os.path.join(MEDIA_DIR, "*")]]
949
+
950
+ st.markdown("###### Your Files 📂")
951
+ st.markdown("###### Image Gallery 🖼")
952
+ own_image_files = [f for f in own_files if f.endswith(('.png', '.jpg'))]
953
+ image_cols = st.slider("Image Gallery Columns 🖼 (Own)", min_value=1, max_value=15, value=5)
954
+ cols = st.columns(image_cols)
955
+ for idx, image_file in enumerate(own_image_files):
956
+ with cols[idx % image_cols]:
957
+ st.image(image_file, use_container_width=True)
958
+ shared_memory.update_media(image_file) # Cache media, in shared sea!
959
+
960
+ st.markdown("###### Video Gallery 🎥")
961
+ own_video_files = [f for f in own_files if f.endswith('.mp4')]
962
+ video_cols = st.slider("Video Gallery Columns 🎬 (Own)", min_value=1, max_value=5, value=3)
963
+ cols = st.columns(video_cols)
964
+ for idx, video_file in enumerate(own_video_files):
965
+ with cols[idx % video_cols]:
966
+ st.markdown(get_video_html(video_file), unsafe_allow_html=True)
967
+ shared_memory.update_media(video_file) # Cache media, in shared lea!
968
+
969
+ st.markdown("###### Audio Gallery 🎧")
970
+ own_audio_files = [f for f in own_files if f.endswith(('.mp3', '.wav')) or f.startswith(os.path.join(AUDIO_DIR, "audio_"))]
971
+ audio_cols = st.slider("Audio Gallery Columns 🎶 (Own)", min_value=1, max_value=15, value=5)
972
+ cols = st.columns(audio_cols)
973
+ for idx, audio_file in enumerate(own_audio_files):
974
+ with cols[idx % audio_cols]:
975
+ st.markdown(await get_audio_html(audio_file), unsafe_allow_html=True)
976
+ shared_memory.update_media(audio_file) # Cache media, in shared tea!
977
+
978
+ st.markdown("###### Shared Files 📤")
979
+ st.markdown("###### Image Gallery 🖼")
980
+ shared_image_files = [f for f in shared_files if f.endswith(('.png', '.jpg'))]
981
+ image_cols = st.slider("Image Gallery Columns 🖼 (Shared)", min_value=1, max_value=15, value=5)
982
+ cols = st.columns(image_cols)
983
+ for idx, image_file in enumerate(shared_image_files):
984
+ with cols[idx % image_cols]:
985
+ st.image(image_file, use_container_width=True)
986
+ shared_memory.update_media(image_file) # Cache media, in shared bee!
987
+
988
+ st.markdown("###### Video Gallery 🎥")
989
+ shared_video_files = [f for f in shared_files if f.endswith('.mp4')]
990
+ video_cols = st.slider("Video Gallery Columns 🎬 (Shared)", min_value=1, max_value=5, value=3)
991
+ cols = st.columns(video_cols)
992
+ for idx, video_file in enumerate(shared_video_files):
993
+ with cols[idx % video_cols]:
994
+ st.markdown(get_video_html(video_file), unsafe_allow_html=True)
995
+ shared_memory.update_media(video_file) # Cache media, in shared tree!
996
+
997
+ st.markdown("###### Audio Gallery 🎧")
998
+ shared_audio_files = [f for f in shared_files if f.endswith(('.mp3', '.wav')) or f.startswith(os.path.join(AUDIO_DIR, "audio_"))]
999
+ audio_cols = st.slider("Audio Gallery Columns 🎶 (Shared)", min_value=1, max_value=15, value=5)
1000
+ cols = st.columns(audio_cols)
1001
+ for idx, audio_file in enumerate(shared_audio_files):
1002
+ with cols[idx % audio_cols]:
1003
+ st.markdown(await get_audio_html(audio_file), unsafe_allow_html=True)
1004
+ shared_memory.update_media(audio_file) # Cache media, in shared glee!
1005
+
1006
+ # Full Log at End with Download - a shared epic’s end, our tale to mend, in shared bend! 📜📥
1007
+ with st.container():
1008
+ st.markdown("###### Full Chat Log 📜")
1009
+ with open(CHAT_FILE, 'r') as f:
1010
+ history_content = f.read()
1011
+ st.markdown(history_content)
1012
+ st.download_button("Download Chat Log as .md", history_content, file_name=f"chat_{st.session_state.user_id}.md", mime="text/markdown")
1013
+
1014
+ # Clear Cache Button - purge the cache, a shared epic’s crash, in shared flash! 🗑️🔄
1015
+ if st.button("Clear Shared Memory Cache", key="clear_cache"):
1016
+ shared_memory = get_shared_memory()
1017
+ shared_memory.clear()
1018
+ st.success("Shared memory cache cleared, a fresh start with a bard’s heart, in shared art! 🌟🎶")
1019
 
1020
  if __name__ == "__main__":
1021
  """
1022
+ Launch our saga, a shared memory’s cheer, in Streamlit’s cache, our epic year, in shared spear! 🚀✨
1023
  """
1024
  main()