awacke1 commited on
Commit
562ef4f
ยท
verified ยท
1 Parent(s): ae88433

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +272 -111
app.py CHANGED
@@ -38,7 +38,7 @@ st.set_page_config(
38
  initial_sidebar_state="auto"
39
  )
40
 
41
- # Funky usernames with corresponding Edge TTS voices
42
  FUN_USERNAMES = {
43
  "CosmicJester ๐ŸŒŒ": "en-US-AriaNeural",
44
  "PixelPanda ๐Ÿผ": "en-US-JennyNeural",
@@ -62,18 +62,21 @@ FUN_USERNAMES = {
62
  "ChronoChimp ๐Ÿ’": "en-GB-LibbyNeural"
63
  }
64
 
65
- # Top-level files (no subdirectories)
66
  CHAT_FILE = "global_chat.md"
67
  QUOTE_VOTES_FILE = "quote_votes.md"
68
  MEDIA_VOTES_FILE = "media_votes.md"
69
  HISTORY_FILE = "chat_history.md"
70
  STATE_FILE = "user_state.txt"
71
- MEDIA_DIR = "media_base64"
 
 
 
72
 
73
- # Fancy digits - numbers got style! ๐Ÿ”ข๐Ÿ’ƒ
74
  UNICODE_DIGITS = {i: f"{i}\uFE0Fโƒฃ" for i in range(10)}
75
 
76
- # Massive font collection - typography bonanza! ๐Ÿ–‹๏ธ๐ŸŽจ
77
  UNICODE_FONTS = [
78
  ("Normal", lambda x: x),
79
  ("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)),
@@ -96,7 +99,7 @@ UNICODE_FONTS = [
96
  ("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
97
  ]
98
 
99
- # Global state - keeping tabs! ๐ŸŒ๐Ÿ“‹
100
  if 'server_running' not in st.session_state:
101
  st.session_state.server_running = False
102
  if 'server_task' not in st.session_state:
@@ -130,13 +133,13 @@ if 'user_id' not in st.session_state:
130
  if 'user_hash' not in st.session_state:
131
  st.session_state.user_hash = None
132
 
133
- # Timestamp wizardry - clock ticks with flair! โฐ๐ŸŽฉ
134
  def format_timestamp_prefix(username):
135
  central = pytz.timezone('US/Central')
136
  now = datetime.now(central)
137
- return f"{username}-{now.strftime('%I-%M-%p-%m-%d-%Y')}"
138
 
139
- # Compute image hash from binary data
140
  def compute_image_hash(image_data):
141
  if isinstance(image_data, Image.Image):
142
  img_byte_arr = io.BytesIO()
@@ -146,7 +149,7 @@ def compute_image_hash(image_data):
146
  img_bytes = image_data
147
  return hashlib.md5(img_bytes).hexdigest()[:8]
148
 
149
- # Node naming - christening the beast! ๐ŸŒ๐Ÿผ
150
  def get_node_name():
151
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
152
  parser.add_argument('--node-name', type=str, default=None)
@@ -156,7 +159,7 @@ def get_node_name():
156
  log_action(username, "๐ŸŒ๐Ÿผ - Node naming - christening the beast!")
157
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
158
 
159
- # Action logger - spying on deeds! ๐Ÿ•ต๏ธ๐Ÿ“œ
160
  def log_action(username, action):
161
  if 'action_log' not in st.session_state:
162
  st.session_state.action_log = {}
@@ -170,16 +173,53 @@ def log_action(username, action):
170
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
171
  user_log[action] = current_time
172
 
173
- # Clean text - strip the fancy stuff! ๐Ÿงน๐Ÿ“
174
  def clean_text_for_tts(text):
175
  cleaned = re.sub(r'[#*!\[\]]+', '', text)
176
  cleaned = ' '.join(cleaned.split())
177
  return cleaned if cleaned else "No text to speak" # Default if empty
178
 
179
- # Audio Processor Class from your code, adapted
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  class AudioProcessor:
181
  def __init__(self):
182
- self.cache_dir = "audio_cache"
183
  os.makedirs(self.cache_dir, exist_ok=True)
184
  self.metadata = self._load_metadata()
185
 
@@ -193,28 +233,32 @@ class AudioProcessor:
193
  json.dump(self.metadata, f)
194
 
195
  async def create_audio(self, text, voice='en-US-AriaNeural', filename=None):
 
 
 
196
  cache_key = hashlib.md5(f"{text}:{voice}".encode()).hexdigest()
197
- cache_path = filename or os.path.join(self.cache_dir, f"{cache_key}.mp3")
 
198
 
199
- if cache_key in self.metadata and os.path.exists(cache_path):
200
- return cache_path
201
 
202
- # Clean text for speech
203
  text = text.replace("\n", " ").replace("</s>", " ").strip()
204
  if not text:
205
  return None
206
 
207
- # Generate audio with edge_tts
208
  try:
209
  communicate = edge_tts.Communicate(text, voice)
210
- await communicate.save(cache_path)
211
- if not os.path.exists(cache_path):
212
  raise edge_tts.exceptions.NoAudioReceived("No audio file created")
213
  except edge_tts.exceptions.NoAudioReceived as e:
214
  log_action("System ๐ŸŒŸ", f"TTS failed for text '{text}' with voice '{voice}': {str(e)}")
215
  return None
216
 
217
- # Update metadata
218
  self.metadata[cache_key] = {
219
  'timestamp': datetime.now().isoformat(),
220
  'text_length': len(text),
@@ -222,18 +266,21 @@ class AudioProcessor:
222
  }
223
  self._save_metadata()
224
 
225
- return cache_path
226
 
227
- # Chat saver - words locked tight! ๐Ÿ’ฌ๐Ÿ”’
228
  async def save_chat_entry(username, message, is_markdown=False, quote_line=None, media_file=None, skip_audio=False):
 
 
 
229
  await asyncio.to_thread(log_action, username, "๐Ÿ’ฌ๐Ÿ”’ - Chat saver - words locked tight!")
230
  central = pytz.timezone('US/Central')
231
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
232
  user_history_file = f"{username}_history.md"
233
  voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
234
- indent = " " if quote_line else "" # Nesting for replies
235
 
236
- # Prepare entry
237
  if is_markdown:
238
  entry = f"{indent}[{timestamp}] {username}:\n{indent}```markdown\n{indent}{message}\n{indent}```"
239
  else:
@@ -241,46 +288,52 @@ async def save_chat_entry(username, message, is_markdown=False, quote_line=None,
241
  if quote_line:
242
  entry = f"{indent}> {quote_line}\n{entry}"
243
 
244
- # Save to global chat file
245
  with open(CHAT_FILE, 'a') as f:
246
  f.write(f"{entry}\n")
247
 
248
- # Save to user-specific history file
249
  if not os.path.exists(user_history_file):
250
  with open(user_history_file, 'w') as f:
251
  f.write(f"# Chat History for {username} (Voice: {voice})\n\n")
252
  with open(user_history_file, 'a') as f:
253
  f.write(f"{entry}\n")
254
 
255
- # Generate audio (unless skipped)
 
256
  if not skip_audio and message.strip():
257
  cleaned_message = clean_text_for_tts(message)
258
  audio_processor = AudioProcessor()
259
- audio_filename = f"{format_timestamp_prefix(username)}-{hashlib.md5(cleaned_message.encode()).hexdigest()[:8]}.mp3"
260
- log_action(username, f"Attempting TTS with text: '{cleaned_message}' and voice: '{voice}'")
261
- audio_file = await audio_processor.create_audio(cleaned_message, voice, audio_filename)
262
-
263
- # Log audio
264
- if audio_file:
265
  with open(HISTORY_FILE, 'a') as f:
266
  f.write(f"[{timestamp}] {username} ({voice}): Audio generated - {audio_filename}\n")
267
  with open(user_history_file, 'a') as f:
268
  f.write(f"{indent}[{timestamp}] Audio: {audio_filename}\n")
269
  with open(CHAT_FILE, 'a') as f:
270
  f.write(f"{indent}[{timestamp}] Audio: {audio_filename}\n")
 
 
271
  if media_file:
272
  if isinstance(media_file, Image.Image):
273
  timestamp_prefix = format_timestamp_prefix(username)
274
- media_filename = f"{username}-{timestamp_prefix.split('-')[-1]}.png"
275
- media_path = media_filename # Save at root
276
  img_byte_arr = io.BytesIO()
277
- media_file.save(img_byte_arr, format='PNG')
278
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(img_byte_arr.getvalue()))
279
  media_file = media_filename
280
- elif media_file.name in ['png', 'jpg', 'mp4', 'mp3']:
281
  timestamp_prefix = format_timestamp_prefix(username)
282
  media_filename = f"{username}-{timestamp_prefix.split('-')[-1]}.{media_file.name.split('.')[-1]}"
283
- media_path = media_filename # Save at root
 
 
 
 
 
 
 
284
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(media_file.getbuffer()))
285
  media_file = media_filename
286
  with open(CHAT_FILE, 'a') as f:
@@ -288,12 +341,21 @@ async def save_chat_entry(username, message, is_markdown=False, quote_line=None,
288
  with open(user_history_file, 'a') as f:
289
  f.write(f"{indent}[{timestamp}] Media: ![Media]({media_file})\n")
290
 
 
 
 
 
 
 
291
  await broadcast_message(f"{username}|{message}", "chat")
292
  st.session_state.last_chat_update = time.time()
293
- return audio_filename
294
 
295
- # Save chat history with image or PDF
296
  async def save_chat_history_with_image(username, image_path):
 
 
 
297
  central = pytz.timezone('US/Central')
298
  timestamp = datetime.now(central).strftime("%Y-%m-%d_%H-%M-%S")
299
  user_history_file = f"{username}_history.md"
@@ -306,8 +368,12 @@ async def save_chat_history_with_image(username, image_path):
306
  f.write(f"[{timestamp}] {username} (Voice: {voice}) Shared Media: {os.path.basename(image_path)}\n")
307
  f.write(f"```markdown\n{chat_content}\n```\n")
308
 
309
- # Chat loader - history unleashed! ๐Ÿ“œ๐Ÿš€
 
310
  async def load_chat():
 
 
 
311
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
312
  await asyncio.to_thread(log_action, username, "๐Ÿ“œ๐Ÿš€ - Chat loader - history unleashed!")
313
  if not os.path.exists(CHAT_FILE):
@@ -316,8 +382,11 @@ async def load_chat():
316
  content = await asyncio.to_thread(f.read)
317
  return content
318
 
319
- # User lister - whoโ€™s in the gang! ๐Ÿ‘ฅ๐ŸŽ‰
320
  async def get_user_list(chat_content):
 
 
 
321
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
322
  await asyncio.to_thread(log_action, username, "๐Ÿ‘ฅ๐ŸŽ‰ - User lister - whoโ€™s in the gang!")
323
  users = set()
@@ -327,22 +396,32 @@ async def get_user_list(chat_content):
327
  users.add(user)
328
  return sorted(list(users))
329
 
330
- # Join checker - been here before? ๐Ÿšช๐Ÿ”
331
  async def has_joined_before(client_id, chat_content):
 
 
 
332
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
333
  await asyncio.to_thread(log_action, username, "๐Ÿšช๐Ÿ” - Join checker - been here before?")
334
  return any(f"Client-{client_id}" in line for line in chat_content.split('\n'))
335
 
336
- # Suggestion maker - old quips resurface! ๐Ÿ’ก๐Ÿ“
337
  async def get_message_suggestions(chat_content, prefix):
 
 
 
338
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
339
  await asyncio.to_thread(log_action, username, "๐Ÿ’ก๐Ÿ“ - Suggestion maker - old quips resurface!")
340
  lines = chat_content.split('\n')
341
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
342
  return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
343
 
344
- # Vote saver - cheers recorded! ๐Ÿ‘๐Ÿ“Š
 
345
  async def save_vote(file, item, user_hash, username, comment=""):
 
 
 
346
  await asyncio.to_thread(log_action, username, "๐Ÿ‘๐Ÿ“Š - Vote saver - cheers recorded!")
347
  central = pytz.timezone('US/Central')
348
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
@@ -353,9 +432,14 @@ async def save_vote(file, item, user_hash, username, comment=""):
353
  if comment:
354
  chat_message += f" - {comment}"
355
  await save_chat_entry(username, chat_message)
 
356
 
357
- # Vote counter - tallying the love! ๐Ÿ†๐Ÿ“ˆ
 
358
  async def load_votes(file):
 
 
 
359
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
360
  await asyncio.to_thread(log_action, username, "๐Ÿ†๐Ÿ“ˆ - Vote counter - tallying the love!")
361
  if not os.path.exists(file):
@@ -375,76 +459,88 @@ async def load_votes(file):
375
  user_votes.add(vote_key)
376
  return votes
377
 
378
- # Hash generator - secret codes ahoy! ๐Ÿ”‘๐Ÿ•ต๏ธ
379
  async def generate_user_hash():
 
 
 
380
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
381
  await asyncio.to_thread(log_action, username, "๐Ÿ”‘๐Ÿ•ต๏ธ - Hash generator - secret codes ahoy!")
382
  if 'user_hash' not in st.session_state:
383
  st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
384
  return st.session_state.user_hash
385
 
386
- # Audio maker - voices come alive! ๐ŸŽถ๐ŸŒŸ
387
  async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
 
 
 
388
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
389
  await asyncio.to_thread(log_action, username, "๐ŸŽถ๐ŸŒŸ - Audio maker - voices come alive!")
390
  timestamp = format_timestamp_prefix(username)
391
- filename = f"{timestamp}-{hashlib.md5(text.encode()).hexdigest()[:8]}.{file_format}"
392
- filepath = filename # Top-level file
393
  communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
394
  try:
395
- await communicate.save(filepath)
396
- return filepath if os.path.exists(filepath) else None
397
  except edge_tts.exceptions.NoAudioReceived:
398
  with open(HISTORY_FILE, 'a') as f:
399
  central = pytz.timezone('US/Central')
400
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: Audio failed - No audio received for '{text}'\n")
401
  return None
402
 
403
- # Audio player - tunes blast off! ๐Ÿ”Š๐Ÿš€
404
  def play_and_download_audio(file_path):
 
 
 
405
  if file_path and os.path.exists(file_path):
 
406
  with open(file_path, "rb") as f:
407
- audio_data = f.read()
408
- b64 = base64.b64encode(audio_data).decode()
409
- audio_html = f'''
410
- <audio controls style="display:inline; vertical-align:middle; width:100px;">
411
- <source src="data:audio/mpeg;base64,{b64}" type="audio/mpeg">
412
- </audio>
413
- <a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}" style="vertical-align:middle;">๐ŸŽต</a>
414
- '''
415
- return audio_html
416
- return ""
417
-
418
- # Image saver - pics preserved with naming! ๐Ÿ“ธ๐Ÿ’พ
419
  async def save_pasted_image(image, username):
 
 
 
420
  await asyncio.to_thread(log_action, username, "๐Ÿ“ธ๐Ÿ’พ - Image saver - pics preserved!")
421
  timestamp = format_timestamp_prefix(username)
422
  media_filename = f"{username}-{timestamp.split('-')[-1]}.png"
423
- media_path = media_filename # Save at root
424
  img_byte_arr = io.BytesIO()
425
  image.save(img_byte_arr, format='PNG')
426
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(img_byte_arr.getvalue()))
427
  return media_filename
428
 
429
- # Video and Audio savers
430
  async def save_media(file, username, ext):
 
 
 
431
  await asyncio.to_thread(log_action, username, f"๐Ÿ“ธ๐Ÿ’พ - Media saver - {ext} preserved!")
432
  timestamp = format_timestamp_prefix(username)
433
  media_filename = f"{username}-{timestamp.split('-')[-1]}.{ext}"
434
- media_path = media_filename # Save at root
435
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(file.getbuffer()))
436
  return media_filename
437
 
438
- # PDF saver and audio generator
439
  async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
 
 
 
440
  await asyncio.to_thread(log_action, username, "๐Ÿ“œ๐ŸŽถ - PDF saver and audio generator!")
441
  timestamp = format_timestamp_prefix(username)
442
  file_hash = hashlib.md5(pdf_file.getbuffer()).hexdigest()[:8]
443
  pdf_filename = f"{username}-{timestamp.split('-')[-1]}-{file_hash}.pdf"
444
- with open(pdf_filename, 'wb') as f:
 
445
  f.write(pdf_file.getbuffer())
446
 
447
- reader = PdfReader(pdf_filename)
448
  total_pages = min(len(reader.pages), max_pages)
449
  texts = []
450
  audio_files = []
@@ -456,15 +552,19 @@ async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
456
  text = reader.pages[i].extract_text()
457
  texts.append(text)
458
  audio_filename = f"{username}-{timestamp.split('-')[-1]}-page{i+1}-{file_hash}-voice-{voice}.mp3"
459
- audio_data = await audio_processor.create_audio(text, voice, audio_filename)
 
460
  if audio_data:
461
  audio_files.append(audio_filename)
462
 
463
  return pdf_filename, texts, audio_files
464
 
465
- # Video renderer - movies roll with autoplay! ๐ŸŽฅ๐ŸŽฌ
466
  def get_video_html(video_path, width="100px"):
467
- video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
 
 
 
468
  return f'''
469
  <video width="{width}" controls autoplay muted loop>
470
  <source src="{video_url}" type="video/mp4">
@@ -472,11 +572,14 @@ def get_video_html(video_path, width="100px"):
472
  </video>
473
  '''
474
 
475
- # Audio renderer - sounds soar! ๐ŸŽถโœˆ๏ธ
476
  async def get_audio_html(audio_path, width="100px"):
 
 
 
477
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
478
  await asyncio.to_thread(log_action, username, "๏ฟฝ๏ฟฝ๏ฟฝโœˆ๏ธ - Audio renderer - sounds soar!")
479
- audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, audio_path, 'rb').read()).decode()}"
480
  return f'''
481
  <audio controls style="width: {width};">
482
  <source src="{audio_url}" type="audio/mpeg">
@@ -484,8 +587,11 @@ async def get_audio_html(audio_path, width="100px"):
484
  </audio>
485
  '''
486
 
487
- # Websocket handler - chat links up! ๐ŸŒ๐Ÿ”—
488
  async def websocket_handler(websocket, path):
 
 
 
489
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
490
  await asyncio.to_thread(log_action, username, "๐ŸŒ๐Ÿ”— - Websocket handler - chat links up!")
491
  try:
@@ -507,8 +613,11 @@ async def websocket_handler(websocket, path):
507
  if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
508
  del st.session_state.active_connections[room_id][client_id]
509
 
510
- # Message broadcaster - words fly far! ๐Ÿ“ขโœˆ๏ธ
511
  async def broadcast_message(message, room_id):
 
 
 
512
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
513
  await asyncio.to_thread(log_action, username, "๐Ÿ“ขโœˆ๏ธ - Message broadcaster - words fly far!")
514
  if room_id in st.session_state.active_connections:
@@ -521,8 +630,11 @@ async def broadcast_message(message, room_id):
521
  for client_id in disconnected:
522
  del st.session_state.active_connections[room_id][client_id]
523
 
524
- # Server starter - web spins up! ๐Ÿ–ฅ๏ธ๐ŸŒ€
525
  async def run_websocket_server():
 
 
 
526
  username = st.session_state.get('username', 'System ๐ŸŒŸ')
527
  await asyncio.to_thread(log_action, username, "๐Ÿ–ฅ๏ธ๐ŸŒ€ - Server starter - web spins up!")
528
  if not st.session_state.server_running:
@@ -530,9 +642,12 @@ async def run_websocket_server():
530
  st.session_state.server_running = True
531
  await server.wait_closed()
532
 
533
- # Delete all user files function
534
  def delete_user_files():
535
- protected_files = {'app.py', 'requirements.txt', 'README.md', CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE, MEDIA_DIR}
 
 
 
536
  deleted_files = []
537
  for file in os.listdir('.'):
538
  if file not in protected_files and not file.endswith('_history.md'):
@@ -541,6 +656,14 @@ def delete_user_files():
541
  deleted_files.append(file)
542
  except Exception as e:
543
  st.error(f"Failed to delete {file}: {e}")
 
 
 
 
 
 
 
 
544
  for root, dirs, files in os.walk(MEDIA_DIR):
545
  for file in files:
546
  file_path = os.path.join(root, file)
@@ -555,8 +678,11 @@ def delete_user_files():
555
  st.session_state.displayed_chat_lines.clear()
556
  return deleted_files
557
 
558
- # Query parameter checker - parse q for username
559
  def check_query_params():
 
 
 
560
  query_params = st.query_params if hasattr(st, 'query_params') else st.experimental_get_query_params()
561
  q_value = query_params.get("q", [None])[0]
562
  if q_value and q_value in FUN_USERNAMES:
@@ -567,8 +693,11 @@ def check_query_params():
567
  st.session_state.user_id = q_value # Use as user_id if not a valid username
568
  return None
569
 
570
- # Mermaid graph generator
571
  def generate_mermaid_graph(chat_lines):
 
 
 
572
  mermaid_code = "graph TD\n"
573
  nodes = {}
574
  edges = []
@@ -579,7 +708,7 @@ def generate_mermaid_graph(chat_lines):
579
  user = content.split(' ')[0]
580
  message = content.split(' ', 1)[1] if ' ' in content else ''
581
  node_id = f"{user}_{i}"
582
- nodes[node_id] = f"{user}: {message}"
583
  if i + 1 < len(chat_lines) and "Audio:" in chat_lines[i + 1]:
584
  audio_node = f"audio_{i}"
585
  nodes[audio_node] = "๐ŸŽต"
@@ -598,20 +727,23 @@ def generate_mermaid_graph(chat_lines):
598
  mermaid_code += "\n".join(f" {edge}" for edge in edges)
599
  return mermaid_code
600
 
601
- # Main execution - letโ€™s roll! ๐ŸŽฒ๐Ÿš€
602
  def main():
 
 
 
603
  NODE_NAME, port = get_node_name()
604
 
605
  loop = asyncio.new_event_loop()
606
  asyncio.set_event_loop(loop)
607
 
608
  async def async_interface():
609
- # Generate user ID and hash if not set
610
  if not st.session_state.user_id:
611
  st.session_state.user_id = str(uuid.uuid4())
612
  st.session_state.user_hash = await generate_user_hash()
613
 
614
- # Check query params first to override username
615
  q_value = check_query_params()
616
  if not q_value and 'username' not in st.session_state:
617
  chat_content = await load_chat()
@@ -620,7 +752,7 @@ def main():
620
  st.session_state.voice = FUN_USERNAMES[st.session_state.username]
621
  st.markdown(f"**๐ŸŽ™๏ธ Voice Selected**: {st.session_state.voice} ๐Ÿ—ฃ๏ธ for {st.session_state.username}")
622
 
623
- # Check existing history file for content
624
  user_history_file = f"{st.session_state.username}_history.md"
625
  if os.path.exists(user_history_file):
626
  with open(user_history_file, 'r') as f:
@@ -637,9 +769,10 @@ def main():
637
  if not st.session_state.server_task:
638
  st.session_state.server_task = loop.create_task(run_websocket_server())
639
 
640
- # Unified Chat History at Top with Markdown Emoji Output
641
  with st.container():
642
  st.markdown(f"##### {START_ROOM} Chat History ๐Ÿ’ฌ")
 
643
  chat_content = await load_chat()
644
  chat_lines = [line for line in chat_content.split('\n') if line.strip() and not line.startswith('#')]
645
  if chat_lines:
@@ -668,19 +801,27 @@ def main():
668
  audio_html = play_and_download_audio(audio_file)
669
  elif "Media:" in nl:
670
  media_file = nl.split("Media: ")[-1].strip('![]()')
671
- if media_file.endswith(('.png', '.jpg')):
672
- media_content = f"<img src='file://{media_file}' width='100'>"
673
- elif media_file.endswith('.mp4'):
674
- media_content = get_video_html(media_file)
675
- elif media_file.endswith('.mp3'):
676
- media_content = await get_audio_html(media_file)
677
- elif media_file.endswith('.pdf'):
678
- media_content = f"๐Ÿ“œ {os.path.basename(media_file)}"
679
- minute_output += f"- ๐Ÿ’ฌ **{user}**: {msg} {audio_html} {media_content}\n"
 
 
680
  markdown_output += minute_output
681
  st.markdown(markdown_output, unsafe_allow_html=True)
 
682
 
683
- # Mermaid Graph Visualization
 
 
 
 
 
684
  st.markdown("###### Chat Relationship Tree ๐ŸŒณ")
685
  mermaid_code = generate_mermaid_graph(chat_lines)
686
  mermaid_html = f"""
@@ -725,7 +866,7 @@ def main():
725
  st.markdown(f"**๐ŸŽ™๏ธ Voice Changed**: {st.session_state.voice} ๐Ÿ—ฃ๏ธ for {st.session_state.username}")
726
  st.rerun()
727
 
728
- # Message input with Send button on the right
729
  col_input, col_send = st.columns([5, 1])
730
  with col_input:
731
  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)
@@ -747,7 +888,6 @@ def main():
747
  st.image(paste_result_msg.image_data, caption="Received Image for Quote")
748
  filename = await save_pasted_image(paste_result_msg.image_data, st.session_state.username)
749
  if filename:
750
- st.session_state.pasted_image_data = filename
751
  await save_chat_entry(st.session_state.username, "Image shared", is_markdown=True, media_file=paste_result_msg.image_data, skip_audio=True)
752
  st.session_state.pasted_image_data = None
753
  st.rerun()
@@ -768,16 +908,21 @@ def main():
768
  filename = pdf_filename
769
  else:
770
  filename = f"{username}-{timestamp.split('-')[-1]}.{ext}"
771
- await asyncio.to_thread(lambda: open(filename, 'wb').write(uploaded_file.getbuffer()))
 
772
  await save_chat_entry(username, f"Uploaded {ext.upper()}: {os.path.basename(filename)}", media_file=uploaded_file, skip_audio=True)
 
 
773
  st.success(f"Uploaded {filename}")
774
 
775
- # Big Red Delete Button
776
  st.markdown("###### ๐Ÿ›‘ Danger Zone")
777
  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):
778
  deleted_files = delete_user_files()
779
  if deleted_files:
780
  st.markdown("### ๐Ÿ—‘๏ธ Deleted Files:\n" + "\n".join([f"- `{file}`" for file in deleted_files]))
 
 
781
  else:
782
  st.markdown("### ๐Ÿ—‘๏ธ Nothing to Delete!")
783
  st.rerun()
@@ -793,11 +938,12 @@ def main():
793
  time.sleep(1)
794
  st.rerun()
795
 
796
- # Separate Galleries for Own and Shared Files
797
  with st.container():
798
- all_files = glob.glob("*.md") + glob.glob("*.pdf") + glob.glob("*.txt") + glob.glob("*.py") + glob.glob("*.png") + glob.glob("*.jpg") + glob.glob("*.mp3") + glob.glob("*.mp4") + glob.glob(os.path.join(MEDIA_DIR, "*.b64"))
 
799
  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)]
800
- 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(MEDIA_DIR, "*")]]
801
 
802
  st.markdown("###### Your Files ๐Ÿ“‚")
803
  st.markdown("###### Image Gallery ๐Ÿ–ผ")
@@ -807,6 +953,7 @@ def main():
807
  for idx, image_file in enumerate(own_image_files):
808
  with cols[idx % image_cols]:
809
  st.image(image_file, use_container_width=True)
 
810
 
811
  st.markdown("###### Video Gallery ๐ŸŽฅ")
812
  own_video_files = [f for f in own_files if f.endswith('.mp4')]
@@ -815,14 +962,16 @@ def main():
815
  for idx, video_file in enumerate(own_video_files):
816
  with cols[idx % video_cols]:
817
  st.markdown(get_video_html(video_file), unsafe_allow_html=True)
 
818
 
819
  st.markdown("###### Audio Gallery ๐ŸŽง")
820
- own_audio_files = [f for f in own_files if f.endswith(('.mp3', '.wav'))]
821
  audio_cols = st.slider("Audio Gallery Columns ๐ŸŽถ (Own)", min_value=1, max_value=15, value=5)
822
  cols = st.columns(audio_cols)
823
  for idx, audio_file in enumerate(own_audio_files):
824
  with cols[idx % audio_cols]:
825
  st.markdown(await get_audio_html(audio_file), unsafe_allow_html=True)
 
826
 
827
  st.markdown("###### Shared Files ๐Ÿ“ค")
828
  st.markdown("###### Image Gallery ๐Ÿ–ผ")
@@ -832,6 +981,7 @@ def main():
832
  for idx, image_file in enumerate(shared_image_files):
833
  with cols[idx % image_cols]:
834
  st.image(image_file, use_container_width=True)
 
835
 
836
  st.markdown("###### Video Gallery ๐ŸŽฅ")
837
  shared_video_files = [f for f in shared_files if f.endswith('.mp4')]
@@ -840,16 +990,18 @@ def main():
840
  for idx, video_file in enumerate(shared_video_files):
841
  with cols[idx % video_cols]:
842
  st.markdown(get_video_html(video_file), unsafe_allow_html=True)
 
843
 
844
  st.markdown("###### Audio Gallery ๐ŸŽง")
845
- shared_audio_files = [f for f in shared_files if f.endswith(('.mp3', '.wav'))]
846
  audio_cols = st.slider("Audio Gallery Columns ๐ŸŽถ (Shared)", min_value=1, max_value=15, value=5)
847
  cols = st.columns(audio_cols)
848
  for idx, audio_file in enumerate(shared_audio_files):
849
  with cols[idx % audio_cols]:
850
  st.markdown(await get_audio_html(audio_file), unsafe_allow_html=True)
 
851
 
852
- # Full Log at End with Download
853
  with st.container():
854
  st.markdown("###### Full Chat Log ๐Ÿ“œ")
855
  with open(CHAT_FILE, 'r') as f:
@@ -857,7 +1009,16 @@ def main():
857
  st.markdown(history_content)
858
  st.download_button("Download Chat Log as .md", history_content, file_name=f"chat_{st.session_state.user_id}.md", mime="text/markdown")
859
 
 
 
 
 
 
 
860
  loop.run_until_complete(async_interface())
861
 
862
  if __name__ == "__main__":
 
 
 
863
  main()
 
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
  "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"
69
  HISTORY_FILE = "chat_history.md"
70
  STATE_FILE = "user_state.txt"
71
+ AUDIO_DIR = "audio_logs"
72
+ 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
  ("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
  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
  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
  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
  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
+ """
186
+ Oh, the cache, a grand, shared hall, where data dances, never to fall!
187
+ Thread-safe and global, it holds our lore, across users and sessions, forevermore! ๐ŸŒ๐Ÿง™
188
+ """
189
+ class SharedMemory:
190
+ def __init__(self):
191
+ self.chat_history = [] # Condensed dialogs, epic tales condensed!
192
+ self.media_files = [] # Media treasures, shared with flair and bends!
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
223
  os.makedirs(self.cache_dir, exist_ok=True)
224
  self.metadata = self._load_metadata()
225
 
 
233
  json.dump(self.metadata, f)
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)
241
+ filename = filename or os.path.join(self.cache_dir, f"audio_{timestamp}_{random.randint(1000, 9999)}.mp3")
242
 
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)
255
+ if not os.path.exists(filename):
256
  raise edge_tts.exceptions.NoAudioReceived("No audio file created")
257
  except edge_tts.exceptions.NoAudioReceived as e:
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),
 
266
  }
267
  self._save_metadata()
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
  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()
307
+ audio_filename = await audio_processor.create_audio(cleaned_message, voice)
308
+ if audio_filename:
 
 
 
 
309
  with open(HISTORY_FILE, 'a') as f:
310
  f.write(f"[{timestamp}] {username} ({voice}): Audio generated - {audio_filename}\n")
311
  with open(user_history_file, 'a') as f:
312
  f.write(f"{indent}[{timestamp}] Audio: {audio_filename}\n")
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)
320
+ media_filename = f"{username}-{timestamp_prefix.split('-')[-1]}.{media_file.format.lower()}"
321
+ media_path = os.path.join(MEDIA_DIR, media_filename)
322
  img_byte_arr = io.BytesIO()
323
+ media_file.save(img_byte_arr, format=media_file.format)
324
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(img_byte_arr.getvalue()))
325
  media_file = media_filename
326
+ elif media_file.name in ['png', 'jpg', 'mp4', 'mp3', 'wav']:
327
  timestamp_prefix = format_timestamp_prefix(username)
328
  media_filename = f"{username}-{timestamp_prefix.split('-')[-1]}.{media_file.name.split('.')[-1]}"
329
+ media_path = os.path.join(MEDIA_DIR, media_filename)
330
+ await asyncio.to_thread(lambda: open(media_path, 'wb').write(media_file.getbuffer()))
331
+ media_file = media_filename
332
+ elif media_file.name == 'pdf':
333
+ timestamp_prefix = format_timestamp_prefix(username)
334
+ file_hash = hashlib.md5(media_file.getbuffer()).hexdigest()[:8]
335
+ media_filename = f"{username}-{timestamp_prefix.split('-')[-1]}-{file_hash}.pdf"
336
+ media_path = os.path.join(MEDIA_DIR, media_filename)
337
  await asyncio.to_thread(lambda: open(media_path, 'wb').write(media_file.getbuffer()))
338
  media_file = media_filename
339
  with open(CHAT_FILE, 'a') as f:
 
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:
348
+ shared_memory.update_media(os.path.join(MEDIA_DIR, media_file))
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")
361
  user_history_file = f"{username}_history.md"
 
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!")
379
  if not os.path.exists(CHAT_FILE):
 
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!")
392
  users = set()
 
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!")
415
  lines = chat_content.split('\n')
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')
427
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
 
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!")
445
  if not os.path.exists(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!")
469
  if 'user_hash' not in st.session_state:
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!")
480
  timestamp = format_timestamp_prefix(username)
481
+ filename = os.path.join(AUDIO_DIR, f"audio_{timestamp}_{random.randint(1000, 9999)}.mp3")
 
482
  communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
483
  try:
484
+ await communicate.save(filename)
485
+ return filename if os.path.exists(filename) else None
486
  except edge_tts.exceptions.NoAudioReceived:
487
  with open(HISTORY_FILE, 'a') as f:
488
  central = pytz.timezone('US/Central')
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)
499
  with open(file_path, "rb") as f:
500
+ b64 = base64.b64encode(f.read()).decode()
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)
511
  media_filename = f"{username}-{timestamp.split('-')[-1]}.png"
512
+ media_path = os.path.join(MEDIA_DIR, media_filename)
513
  img_byte_arr = io.BytesIO()
514
  image.save(img_byte_arr, format='PNG')
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)
525
  media_filename = f"{username}-{timestamp.split('-')[-1]}.{ext}"
526
+ media_path = os.path.join(MEDIA_DIR, media_filename)
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)
537
  file_hash = hashlib.md5(pdf_file.getbuffer()).hexdigest()[:8]
538
  pdf_filename = f"{username}-{timestamp.split('-')[-1]}-{file_hash}.pdf"
539
+ media_path = os.path.join(MEDIA_DIR, pdf_filename)
540
+ with open(media_path, 'wb') as f:
541
  f.write(pdf_file.getbuffer())
542
 
543
+ reader = PdfReader(media_path)
544
  total_pages = min(len(reader.pages), max_pages)
545
  texts = []
546
  audio_files = []
 
552
  text = reader.pages[i].extract_text()
553
  texts.append(text)
554
  audio_filename = f"{username}-{timestamp.split('-')[-1]}-page{i+1}-{file_hash}-voice-{voice}.mp3"
555
+ audio_path = os.path.join(AUDIO_DIR, audio_filename)
556
+ audio_data = await audio_processor.create_audio(text, voice, audio_path)
557
  if audio_data:
558
  audio_files.append(audio_filename)
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'''
569
  <video width="{width}" controls autoplay muted loop>
570
  <source src="{video_url}" type="video/mp4">
 
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!")
582
+ audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, os.path.join(AUDIO_DIR, audio_path), 'rb').read()).decode()}"
583
  return f'''
584
  <audio controls style="width: {width};">
585
  <source src="{audio_url}" type="audio/mpeg">
 
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!")
597
  try:
 
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!")
623
  if room_id in st.session_state.active_connections:
 
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!")
640
  if not st.session_state.server_running:
 
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 = []
652
  for file in os.listdir('.'):
653
  if file not in protected_files and not file.endswith('_history.md'):
 
656
  deleted_files.append(file)
657
  except Exception as e:
658
  st.error(f"Failed to delete {file}: {e}")
659
+ for root, dirs, files in os.walk(AUDIO_DIR):
660
+ for file in files:
661
+ file_path = os.path.join(root, file)
662
+ try:
663
+ os.remove(file_path)
664
+ deleted_files.append(file_path)
665
+ except Exception as e:
666
+ st.error(f"Failed to delete {file_path}: {e}")
667
  for root, dirs, files in os.walk(MEDIA_DIR):
668
  for file in files:
669
  file_path = os.path.join(root, file)
 
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]
688
  if q_value and q_value in FUN_USERNAMES:
 
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 = {}
703
  edges = []
 
708
  user = content.split(' ')[0]
709
  message = content.split(' ', 1)[1] if ' ' in content else ''
710
  node_id = f"{user}_{i}"
711
+ nodes[node_id] = f"{user}: {message[:20]}..." if len(message) > 20 else f"{user}: {message}"
712
  if i + 1 < len(chat_lines) and "Audio:" in chat_lines[i + 1]:
713
  audio_node = f"audio_{i}"
714
  nodes[audio_node] = "๐ŸŽต"
 
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()
 
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:
 
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:
 
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"""
 
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)
 
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()
 
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()
 
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 ๐Ÿ–ผ")
 
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')]
 
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 ๐Ÿ–ผ")
 
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')]
 
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:
 
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()