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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -253
app.py CHANGED
@@ -14,12 +14,12 @@ import base64
14
  import io
15
  import streamlit.components.v1 as components
16
  import edge_tts
17
- from audio_recorder_streamlit import audio_recorder
18
  import nest_asyncio
19
  import re
20
  from streamlit_paste_button import paste_image_button
21
  import pytz
22
  import shutil
 
23
 
24
  # Patch for nested async - sneaky fix! 🐍✨
25
  nest_asyncio.apply()
@@ -60,23 +60,12 @@ FUN_USERNAMES = {
60
  "ChronoChimp πŸ’": "en-GB-LibbyNeural"
61
  }
62
 
63
- # Folders galore - organizing chaos! πŸ“‚πŸŒ€
64
- CHAT_DIR = "chat_logs"
65
- VOTE_DIR = "vote_logs"
 
 
66
  STATE_FILE = "user_state.txt"
67
- AUDIO_DIR = "audio_logs"
68
- HISTORY_DIR = "history_logs"
69
- MEDIA_DIR = "media_files"
70
- os.makedirs(CHAT_DIR, exist_ok=True)
71
- os.makedirs(VOTE_DIR, exist_ok=True)
72
- os.makedirs(AUDIO_DIR, exist_ok=True)
73
- os.makedirs(HISTORY_DIR, exist_ok=True)
74
- os.makedirs(MEDIA_DIR, exist_ok=True)
75
-
76
- CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
77
- QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
78
- MEDIA_VOTES_FILE = os.path.join(VOTE_DIR, "media_votes.md")
79
- HISTORY_FILE = os.path.join(HISTORY_DIR, "chat_history.md")
80
 
81
  # Fancy digits - numbers got style! πŸ”’πŸ’ƒ
82
  UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
@@ -133,14 +122,14 @@ if 'refresh_rate' not in st.session_state:
133
  st.session_state.refresh_rate = 5
134
  if 'base64_cache' not in st.session_state:
135
  st.session_state.base64_cache = {}
136
- if 'transcript_history' not in st.session_state:
137
- st.session_state.transcript_history = []
138
- if 'last_transcript' not in st.session_state:
139
- st.session_state.last_transcript = ""
140
  if 'image_hashes' not in st.session_state:
141
  st.session_state.image_hashes = set()
142
  if 'gallery_columns' not in st.session_state:
143
  st.session_state.gallery_columns = 1 # Default gallery tiles
 
 
 
 
144
 
145
  # Timestamp wizardry - clock ticks with flair! ⏰🎩
146
  def format_timestamp_prefix(username):
@@ -189,21 +178,43 @@ def clean_text_for_tts(text):
189
  return cleaned[:200] if cleaned else "No text to speak"
190
 
191
  # Chat saver - words locked tight! πŸ’¬πŸ”’
192
- async def save_chat_entry(username, message, is_markdown=False):
193
  await asyncio.to_thread(log_action, username, "πŸ’¬πŸ”’ - Chat saver - words locked tight!")
194
  central = pytz.timezone('US/Central')
195
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
 
 
 
196
  if is_markdown:
197
- entry = f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
198
  else:
199
- entry = f"[{timestamp}] {username}: {message}"
 
 
 
 
200
  await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"{entry}\n"))
201
- voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
 
 
 
 
 
 
 
202
  cleaned_message = clean_text_for_tts(message)
203
  audio_file = await async_edge_tts_generate(cleaned_message, voice)
204
  if audio_file:
205
  with open(HISTORY_FILE, 'a') as f:
206
  f.write(f"[{timestamp}] {username} ({voice}): Audio generated - {audio_file}\n")
 
 
 
 
 
 
 
 
207
  await broadcast_message(f"{username}|{message}", "chat")
208
  st.session_state.last_chat_update = time.time()
209
  return audio_file
@@ -212,15 +223,15 @@ async def save_chat_entry(username, message, is_markdown=False):
212
  async def save_chat_history_with_image(username, image_path):
213
  central = pytz.timezone('US/Central')
214
  timestamp = datetime.now(central).strftime("%Y-%m-%d_%H-%M-%S")
215
- history_filename = f"chat_history_{timestamp}-by-{username}.md"
216
- history_filepath = os.path.join(HISTORY_DIR, history_filename)
217
  chat_content = await load_chat()
218
  voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
219
- with open(history_filepath, 'w') as f:
220
- f.write(f"# Chat History at {timestamp} by {username} (Voice: {voice})\n\n")
221
- f.write(f"## Image Shared: {os.path.basename(image_path)}\n")
222
- f.write(chat_content)
223
- return history_filepath
 
224
 
225
  # Chat loader - history unleashed! πŸ“œπŸš€
226
  async def load_chat():
@@ -305,7 +316,7 @@ async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3
305
  await asyncio.to_thread(log_action, username, "🎢🌟 - Audio maker - voices come alive!")
306
  timestamp = format_timestamp_prefix(username)
307
  filename = f"{timestamp}.{file_format}"
308
- filepath = os.path.join(AUDIO_DIR, filename)
309
  communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
310
  try:
311
  await communicate.save(filepath)
@@ -336,7 +347,7 @@ async def save_pasted_image(image, username):
336
  return None
337
  timestamp = format_timestamp_prefix(username)
338
  filename = f"{timestamp}-{img_hash}.png"
339
- filepath = os.path.join(MEDIA_DIR, filename)
340
  await asyncio.to_thread(image.save, filepath, "PNG")
341
  st.session_state.image_hashes.add(img_hash)
342
  await save_chat_history_with_image(username, filepath)
@@ -405,14 +416,6 @@ async def run_websocket_server():
405
  st.session_state.server_running = True
406
  await server.wait_closed()
407
 
408
- # Voice processor - speech to text! πŸŽ€πŸ“
409
- async def process_voice_input(audio_bytes):
410
- username = st.session_state.get('username', 'System 🌟')
411
- await asyncio.to_thread(log_action, username, "πŸŽ€πŸ“ - Voice processor - speech to text!")
412
- if audio_bytes:
413
- text = "Voice input simulation"
414
- await save_chat_entry(username, text)
415
-
416
  # Dummy AI lookup function (replace with actual implementation)
417
  async def perform_ai_lookup(query, vocal_summary=True, extended_refs=False, titles_summary=True, full_audio=False, useArxiv=True, useArxivAudio=False):
418
  username = st.session_state.get('username', 'System 🌟')
@@ -425,194 +428,28 @@ async def perform_ai_lookup(query, vocal_summary=True, extended_refs=False, titl
425
 
426
  # Delete all user files function
427
  def delete_user_files():
428
- protected_files = {'app.py', 'requirements.txt', 'README.md'}
429
  deleted_files = []
430
- directories = [MEDIA_DIR, AUDIO_DIR, CHAT_DIR, VOTE_DIR, HISTORY_DIR]
431
- for directory in directories:
432
- if os.path.exists(directory):
433
- for root, _, files in os.walk(directory):
434
- for file in files:
435
- file_path = os.path.join(root, file)
436
- if os.path.basename(file_path) not in protected_files:
437
- try:
438
- os.remove(file_path)
439
- deleted_files.append(file_path)
440
- except Exception as e:
441
- st.error(f"Failed to delete {file_path}: {e}")
442
  try:
443
- shutil.rmtree(directory, ignore_errors=True)
444
- os.makedirs(directory, exist_ok=True)
445
  except Exception as e:
446
- st.error(f"Failed to remove directory {directory}: {e}")
447
  st.session_state.image_hashes.clear()
448
  st.session_state.audio_cache.clear()
449
  st.session_state.base64_cache.clear()
450
  st.session_state.displayed_chat_lines.clear()
451
  return deleted_files
452
 
453
- # ASR Component HTML
454
- ASR_HTML = """
455
- <html>
456
- <head>
457
- <title>Continuous Speech Demo</title>
458
- <style>
459
- body {
460
- font-family: sans-serif;
461
- padding: 20px;
462
- max-width: 800px;
463
- margin: 0 auto;
464
- }
465
- button {
466
- padding: 10px 20px;
467
- margin: 10px 5px;
468
- font-size: 16px;
469
- }
470
- #status {
471
- margin: 10px 0;
472
- padding: 10px;
473
- background: #e8f5e9;
474
- border-radius: 4px;
475
- }
476
- #output {
477
- white-space: pre-wrap;
478
- padding: 15px;
479
- background: #f5f5f5;
480
- border-radius: 4px;
481
- margin: 10px 0;
482
- min-height: 100px;
483
- max-height: 400px;
484
- overflow-y: auto;
485
- }
486
- .controls {
487
- margin: 10px 0;
488
- }
489
- </style>
490
- </head>
491
- <body>
492
- <div class="controls">
493
- <button id="start">Start Listening</button>
494
- <button id="stop" disabled>Stop Listening</button>
495
- <button id="clear">Clear Text</button>
496
- </div>
497
- <div id="status">Ready</div>
498
- <div id="output"></div>
499
-
500
- <script>
501
- if (!('webkitSpeechRecognition' in window)) {
502
- alert('Speech recognition not supported');
503
- } else {
504
- const recognition = new webkitSpeechRecognition();
505
- const startButton = document.getElementById('start');
506
- const stopButton = document.getElementById('stop');
507
- const clearButton = document.getElementById('clear');
508
- const status = document.getElementById('status');
509
- const output = document.getElementById('output');
510
- let fullTranscript = '';
511
- let lastUpdateTime = Date.now();
512
-
513
- recognition.continuous = true;
514
- recognition.interimResults = true;
515
-
516
- const startRecognition = () => {
517
- try {
518
- recognition.start();
519
- status.textContent = 'Listening...';
520
- startButton.disabled = true;
521
- stopButton.disabled = false;
522
- } catch (e) {
523
- console.error(e);
524
- status.textContent = 'Error: ' + e.message;
525
- }
526
- };
527
-
528
- window.addEventListener('load', () => {
529
- setTimeout(startRecognition, 1000);
530
- });
531
-
532
- startButton.onclick = startRecognition;
533
-
534
- stopButton.onclick = () => {
535
- recognition.stop();
536
- status.textContent = 'Stopped';
537
- startButton.disabled = false;
538
- stopButton.disabled = true;
539
- };
540
-
541
- clearButton.onclick = () => {
542
- fullTranscript = '';
543
- output.textContent = '';
544
- sendDataToPython({value: '', dataType: "json"});
545
- };
546
-
547
- recognition.onresult = (event) => {
548
- let interimTranscript = '';
549
- let finalTranscript = '';
550
-
551
- for (let i = event.resultIndex; i < event.results.length; i++) {
552
- const transcript = event.results[i][0].transcript;
553
- if (event.results[i].isFinal) {
554
- finalTranscript += transcript + '\\n';
555
- } else {
556
- interimTranscript += transcript;
557
- }
558
- }
559
-
560
- if (finalTranscript || (Date.now() - lastUpdateTime > 5000)) {
561
- if (finalTranscript) {
562
- fullTranscript += finalTranscript;
563
- }
564
- lastUpdateTime = Date.now();
565
- output.textContent = fullTranscript + (interimTranscript ? '... ' + interimTranscript : '');
566
- output.scrollTop = output.scrollHeight;
567
- sendDataToPython({value: fullTranscript, dataType: "json"});
568
- }
569
- };
570
-
571
- recognition.onend = () => {
572
- if (!stopButton.disabled) {
573
- try {
574
- recognition.start();
575
- console.log('Restarted recognition');
576
- } catch (e) {
577
- console.error('Failed to restart recognition:', e);
578
- status.textContent = 'Error restarting: ' + e.message;
579
- startButton.disabled = false;
580
- stopButton.disabled = true;
581
- }
582
- }
583
- };
584
-
585
- recognition.onerror = (event) => {
586
- console.error('Recognition error:', event.error);
587
- status.textContent = 'Error: ' + event.error;
588
- if (event.error === 'not-allowed' || event.error === 'service-not-allowed') {
589
- startButton.disabled = false;
590
- stopButton.disabled = true;
591
- }
592
- };
593
- }
594
-
595
- function sendDataToPython(data) {
596
- window.parent.postMessage({
597
- isStreamlitMessage: true,
598
- type: "streamlit:setComponentValue",
599
- ...data
600
- }, "*");
601
- }
602
-
603
- window.addEventListener('load', function() {
604
- window.setTimeout(function() {
605
- window.parent.postMessage({
606
- isStreamlitMessage: true,
607
- type: "streamlit:setFrameHeight",
608
- height: document.documentElement.clientHeight
609
- }, "*");
610
- }, 0);
611
- });
612
- </script>
613
- </body>
614
- </html>
615
- """
616
 
617
  # Main execution - let’s roll! πŸŽ²πŸš€
618
  def main():
@@ -622,6 +459,10 @@ def main():
622
  asyncio.set_event_loop(loop)
623
 
624
  async def async_interface():
 
 
 
 
625
  if 'username' not in st.session_state:
626
  chat_content = await load_chat()
627
  available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
@@ -629,33 +470,22 @@ def main():
629
  st.session_state.voice = FUN_USERNAMES[st.session_state.username]
630
  st.markdown(f"**πŸŽ™οΈ Voice Selected**: {st.session_state.voice} πŸ—£οΈ for {st.session_state.username}")
631
 
 
 
 
 
 
632
  st.title(f"πŸ€–πŸ§ MMO {st.session_state.username}πŸ“πŸ”¬")
633
- st.markdown(f"Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting! πŸŽ‰")
634
 
635
  if not st.session_state.server_task:
636
  st.session_state.server_task = loop.create_task(run_websocket_server())
637
 
638
- audio_bytes = audio_recorder()
639
- if audio_bytes:
640
- await process_voice_input(audio_bytes)
641
- st.rerun()
642
-
643
- # Continuous Speech Input (ASR)
644
- st.subheader("🎀 Continuous Speech Input")
645
- asr_component = components.html(ASR_HTML, height=400)
646
- if asr_component and isinstance(asr_component, dict) and 'value' in asr_component:
647
- transcript = asr_component['value'].strip()
648
- if transcript and transcript != st.session_state.last_transcript:
649
- st.session_state.transcript_history.append(transcript)
650
- await save_chat_entry(st.session_state.username, transcript, is_markdown=True)
651
- st.session_state.last_transcript = transcript
652
- st.rerun()
653
-
654
  # Unified Chat History at Top
655
  st.subheader(f"{START_ROOM} Chat History πŸ’¬")
656
  chat_content = await load_chat()
657
  chat_lines = chat_content.split('\n')
658
- chat_lines = [line for line in chat_lines if line.strip() and ': ' in line and not line.startswith('#')]
659
  if chat_lines:
660
  col1, col2 = st.columns([2, 1])
661
  with col1:
@@ -664,7 +494,7 @@ def main():
664
  col_text, col_audio = st.columns([3, 1])
665
  with col_text:
666
  if "```markdown" in line:
667
- markdown_content = re.search(r'```markdown\n(.*?)```', line, re.DOTALL)
668
  if markdown_content:
669
  st.markdown(markdown_content.group(1))
670
  else:
@@ -672,13 +502,17 @@ def main():
672
  else:
673
  st.markdown(line)
674
  with col_audio:
675
- username = line.split(': ')[1].split(' ')[0]
676
  cache_key = f"{line}_{FUN_USERNAMES.get(username, 'en-US-AriaNeural')}"
677
- if cache_key not in st.session_state.audio_cache:
678
- cleaned_text = clean_text_for_tts(line.split(': ', 1)[1])
679
- voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
680
- audio_file = await async_edge_tts_generate(cleaned_text, voice)
681
- st.session_state.audio_cache[cache_key] = audio_file
 
 
 
 
682
  audio_file = st.session_state.audio_cache.get(cache_key)
683
  if audio_file:
684
  play_and_download_audio(audio_file)
@@ -696,13 +530,13 @@ def main():
696
  filename = await save_pasted_image(paste_result_quote.image_data, st.session_state.username)
697
  if filename:
698
  st.session_state.pasted_image_data = filename
699
- if st.button("Send Quote πŸš€", key="send_quote"):
700
  markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
701
  if st.session_state.pasted_image_data:
702
  markdown_response += f"\n- **Image**: ![Pasted Image]({st.session_state.pasted_image_data})"
703
- await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}")
704
  st.session_state.pasted_image_data = None
705
- await save_chat_entry(st.session_state.username, markdown_response, is_markdown=True)
706
  st.session_state.quote_line = None
707
  st.session_state.message_text = ''
708
  st.rerun()
@@ -744,7 +578,7 @@ def main():
744
  if filename:
745
  st.session_state.pasted_image_data = filename
746
 
747
- tab_main = st.radio("Action:", ["🎀 Voice", "πŸ“Έ Media", "πŸ” ArXiv", "πŸ“ Editor"], horizontal=True)
748
  useArxiv = st.checkbox("Search Arxiv for Research Paper Answers", value=True)
749
  useArxivAudio = st.checkbox("Generate Audio File for Research Paper Answers", value=False)
750
 
@@ -757,7 +591,7 @@ def main():
757
  file_hash = hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]
758
  if file_hash not in st.session_state.image_hashes:
759
  filename = f"{timestamp}-{file_hash}.{ext}"
760
- file_path = os.path.join(MEDIA_DIR, filename)
761
  await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
762
  st.success(f"Uploaded {filename}")
763
  await save_chat_entry(username, f"Uploaded media: {file_path}")
@@ -791,7 +625,7 @@ def main():
791
  st.subheader("Media Gallery 🎨🎢πŸŽ₯")
792
  gallery_columns = st.slider("Number of Gallery Tiles", 1, 20, st.session_state.gallery_columns)
793
  st.session_state.gallery_columns = gallery_columns
794
- media_files = glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg") + glob.glob(f"{MEDIA_DIR}/*.mp4")
795
  if media_files:
796
  media_votes = await load_votes(MEDIA_VOTES_FILE)
797
  seen_files = set()
@@ -807,7 +641,7 @@ def main():
807
  if media_file.endswith(('.png', '.jpg')):
808
  st.image(media_file, use_container_width=True)
809
  elif media_file.endswith('.mp4'):
810
- st.markdown(get_video_html(media_file), unsafe_allow_html=True) # Verbatim autoplay
811
  if st.button(f"πŸ‘ {vote_count}", key=f"media_vote_{media_file}"):
812
  await save_vote(MEDIA_VOTES_FILE, media_file, await generate_user_hash(), st.session_state.username)
813
  st.rerun()
 
14
  import io
15
  import streamlit.components.v1 as components
16
  import edge_tts
 
17
  import nest_asyncio
18
  import re
19
  from streamlit_paste_button import paste_image_button
20
  import pytz
21
  import shutil
22
+ from urllib.parse import urlencode
23
 
24
  # Patch for nested async - sneaky fix! 🐍✨
25
  nest_asyncio.apply()
 
60
  "ChronoChimp πŸ’": "en-GB-LibbyNeural"
61
  }
62
 
63
+ # Top-level files (no subdirectories)
64
+ CHAT_FILE = "global_chat.md"
65
+ QUOTE_VOTES_FILE = "quote_votes.md"
66
+ MEDIA_VOTES_FILE = "media_votes.md"
67
+ HISTORY_FILE = "chat_history.md"
68
  STATE_FILE = "user_state.txt"
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  # Fancy digits - numbers got style! πŸ”’πŸ’ƒ
71
  UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
 
122
  st.session_state.refresh_rate = 5
123
  if 'base64_cache' not in st.session_state:
124
  st.session_state.base64_cache = {}
 
 
 
 
125
  if 'image_hashes' not in st.session_state:
126
  st.session_state.image_hashes = set()
127
  if 'gallery_columns' not in st.session_state:
128
  st.session_state.gallery_columns = 1 # Default gallery tiles
129
+ if 'user_id' not in st.session_state:
130
+ st.session_state.user_id = None
131
+ if 'user_hash' not in st.session_state:
132
+ st.session_state.user_hash = None
133
 
134
  # Timestamp wizardry - clock ticks with flair! ⏰🎩
135
  def format_timestamp_prefix(username):
 
178
  return cleaned[:200] if cleaned else "No text to speak"
179
 
180
  # Chat saver - words locked tight! πŸ’¬πŸ”’
181
+ async def save_chat_entry(username, message, is_markdown=False, quote_line=None):
182
  await asyncio.to_thread(log_action, username, "πŸ’¬πŸ”’ - Chat saver - words locked tight!")
183
  central = pytz.timezone('US/Central')
184
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
185
+ user_history_file = f"{username}_history.md"
186
+ voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
187
+ indent = " " if quote_line else "" # Nesting for replies
188
  if is_markdown:
189
+ entry = f"{indent}[{timestamp}] {username}:\n{indent}```markdown\n{indent}{message}\n{indent}```"
190
  else:
191
+ entry = f"{indent}[{timestamp}] {username}: {message}"
192
+ if quote_line:
193
+ entry = f"{indent}> {quote_line}\n{entry}"
194
+
195
+ # Save to global chat file
196
  await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"{entry}\n"))
197
+
198
+ # Save to user-specific history file
199
+ if not os.path.exists(user_history_file):
200
+ with open(user_history_file, 'w') as f:
201
+ f.write(f"# Chat History for {username} (Voice: {voice})\n\n")
202
+ with open(user_history_file, 'a') as f:
203
+ f.write(f"{entry}\n")
204
+
205
  cleaned_message = clean_text_for_tts(message)
206
  audio_file = await async_edge_tts_generate(cleaned_message, voice)
207
  if audio_file:
208
  with open(HISTORY_FILE, 'a') as f:
209
  f.write(f"[{timestamp}] {username} ({voice}): Audio generated - {audio_file}\n")
210
+ with open(user_history_file, 'a') as f:
211
+ f.write(f"{indent}[{timestamp}] Audio: {audio_file}\n")
212
+
213
+ # Embed audio in global chat if it's an image or media reference
214
+ if message.startswith("Pasted image:") or message.startswith("Uploaded media:"):
215
+ with open(CHAT_FILE, 'a') as f:
216
+ f.write(f"{indent}[{timestamp}] Audio: {audio_file}\n")
217
+
218
  await broadcast_message(f"{username}|{message}", "chat")
219
  st.session_state.last_chat_update = time.time()
220
  return audio_file
 
223
  async def save_chat_history_with_image(username, image_path):
224
  central = pytz.timezone('US/Central')
225
  timestamp = datetime.now(central).strftime("%Y-%m-%d_%H-%M-%S")
226
+ user_history_file = f"{username}_history.md"
 
227
  chat_content = await load_chat()
228
  voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
229
+ if not os.path.exists(user_history_file):
230
+ with open(user_history_file, 'w') as f:
231
+ f.write(f"# Chat History for {username} (Voice: {voice})\n\n")
232
+ with open(user_history_file, 'a') as f:
233
+ f.write(f"[{timestamp}] {username} (Voice: {voice}) Shared Image: {os.path.basename(image_path)}\n")
234
+ f.write(f"```markdown\n{chat_content}\n```\n")
235
 
236
  # Chat loader - history unleashed! πŸ“œπŸš€
237
  async def load_chat():
 
316
  await asyncio.to_thread(log_action, username, "🎢🌟 - Audio maker - voices come alive!")
317
  timestamp = format_timestamp_prefix(username)
318
  filename = f"{timestamp}.{file_format}"
319
+ filepath = filename # Top-level file
320
  communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
321
  try:
322
  await communicate.save(filepath)
 
347
  return None
348
  timestamp = format_timestamp_prefix(username)
349
  filename = f"{timestamp}-{img_hash}.png"
350
+ filepath = filename # Top-level file
351
  await asyncio.to_thread(image.save, filepath, "PNG")
352
  st.session_state.image_hashes.add(img_hash)
353
  await save_chat_history_with_image(username, filepath)
 
416
  st.session_state.server_running = True
417
  await server.wait_closed()
418
 
 
 
 
 
 
 
 
 
419
  # Dummy AI lookup function (replace with actual implementation)
420
  async def perform_ai_lookup(query, vocal_summary=True, extended_refs=False, titles_summary=True, full_audio=False, useArxiv=True, useArxivAudio=False):
421
  username = st.session_state.get('username', 'System 🌟')
 
428
 
429
  # Delete all user files function
430
  def delete_user_files():
431
+ protected_files = {'app.py', 'requirements.txt', 'README.md', CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE}
432
  deleted_files = []
433
+ for file in os.listdir('.'):
434
+ if file not in protected_files and not file.endswith('_history.md'):
 
 
 
 
 
 
 
 
 
 
435
  try:
436
+ os.remove(file)
437
+ deleted_files.append(file)
438
  except Exception as e:
439
+ st.error(f"Failed to delete {file}: {e}")
440
  st.session_state.image_hashes.clear()
441
  st.session_state.audio_cache.clear()
442
  st.session_state.base64_cache.clear()
443
  st.session_state.displayed_chat_lines.clear()
444
  return deleted_files
445
 
446
+ # Query parameter checker
447
+ def check_query_params():
448
+ query_params = st.query_params if hasattr(st, 'query_params') else st.experimental_get_query_params()
449
+ user_id = query_params.get("q", [None])[0]
450
+ if user_id:
451
+ st.session_state.user_id = user_id
452
+ return user_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
 
454
  # Main execution - let’s roll! πŸŽ²πŸš€
455
  def main():
 
459
  asyncio.set_event_loop(loop)
460
 
461
  async def async_interface():
462
+ # Generate user ID and hash if not set
463
+ if not st.session_state.user_id:
464
+ st.session_state.user_id = str(uuid.uuid4())
465
+ st.session_state.user_hash = await generate_user_hash()
466
  if 'username' not in st.session_state:
467
  chat_content = await load_chat()
468
  available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
 
470
  st.session_state.voice = FUN_USERNAMES[st.session_state.username]
471
  st.markdown(f"**πŸŽ™οΈ Voice Selected**: {st.session_state.voice} πŸ—£οΈ for {st.session_state.username}")
472
 
473
+ # Check query params and set user ID
474
+ check_query_params()
475
+ user_url = f"/q={st.session_state.user_id}"
476
+ st.write(f"Your unique URL path: [{user_url}]({user_url})")
477
+
478
  st.title(f"πŸ€–πŸ§ MMO {st.session_state.username}πŸ“πŸ”¬")
479
+ st.markdown(f"Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting! πŸŽ‰ User ID: {st.session_state.user_id}")
480
 
481
  if not st.session_state.server_task:
482
  st.session_state.server_task = loop.create_task(run_websocket_server())
483
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  # Unified Chat History at Top
485
  st.subheader(f"{START_ROOM} Chat History πŸ’¬")
486
  chat_content = await load_chat()
487
  chat_lines = chat_content.split('\n')
488
+ chat_lines = [line for line in chat_content.split('\n') if line.strip() and not line.startswith('#')]
489
  if chat_lines:
490
  col1, col2 = st.columns([2, 1])
491
  with col1:
 
494
  col_text, col_audio = st.columns([3, 1])
495
  with col_text:
496
  if "```markdown" in line:
497
+ markdown_content = re.search(r'```markdown\n(.*?)(?=\n\s*\[|$)', line, re.DOTALL)
498
  if markdown_content:
499
  st.markdown(markdown_content.group(1))
500
  else:
 
502
  else:
503
  st.markdown(line)
504
  with col_audio:
505
+ username = line.split(': ')[1].split(' ')[0] if ': ' in line else "Unknown"
506
  cache_key = f"{line}_{FUN_USERNAMES.get(username, 'en-US-AriaNeural')}"
507
+ if cache_key not in st.session_state.audio_cache and "Audio:" in line:
508
+ audio_ref = line.split("Audio: ")[-1].strip()
509
+ if os.path.exists(audio_ref):
510
+ st.session_state.audio_cache[cache_key] = audio_ref
511
+ else:
512
+ cleaned_text = clean_text_for_tts(line.split(': ', 1)[1].split("Audio:")[0])
513
+ voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
514
+ audio_file = await async_edge_tts_generate(cleaned_text, voice)
515
+ st.session_state.audio_cache[cache_key] = audio_file
516
  audio_file = st.session_state.audio_cache.get(cache_key)
517
  if audio_file:
518
  play_and_download_audio(audio_file)
 
530
  filename = await save_pasted_image(paste_result_quote.image_data, st.session_state.username)
531
  if filename:
532
  st.session_state.pasted_image_data = filename
533
+ if st.button("Send Quote οΏ½<|control678|>", key="send_quote"):
534
  markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
535
  if st.session_state.pasted_image_data:
536
  markdown_response += f"\n- **Image**: ![Pasted Image]({st.session_state.pasted_image_data})"
537
+ await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}", quote_line=st.session_state.quote_line)
538
  st.session_state.pasted_image_data = None
539
+ await save_chat_entry(st.session_state.username, markdown_response, is_markdown=True, quote_line=st.session_state.quote_line)
540
  st.session_state.quote_line = None
541
  st.session_state.message_text = ''
542
  st.rerun()
 
578
  if filename:
579
  st.session_state.pasted_image_data = filename
580
 
581
+ tab_main = st.radio("Action:", ["πŸ“Έ Media", "πŸ” ArXiv", "πŸ“ Editor"], horizontal=True)
582
  useArxiv = st.checkbox("Search Arxiv for Research Paper Answers", value=True)
583
  useArxivAudio = st.checkbox("Generate Audio File for Research Paper Answers", value=False)
584
 
 
591
  file_hash = hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]
592
  if file_hash not in st.session_state.image_hashes:
593
  filename = f"{timestamp}-{file_hash}.{ext}"
594
+ file_path = filename # Top-level file
595
  await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
596
  st.success(f"Uploaded {filename}")
597
  await save_chat_entry(username, f"Uploaded media: {file_path}")
 
625
  st.subheader("Media Gallery 🎨🎢πŸŽ₯")
626
  gallery_columns = st.slider("Number of Gallery Tiles", 1, 20, st.session_state.gallery_columns)
627
  st.session_state.gallery_columns = gallery_columns
628
+ media_files = glob.glob("*.png") + glob.glob("*.jpg") + glob.glob("*.mp4")
629
  if media_files:
630
  media_votes = await load_votes(MEDIA_VOTES_FILE)
631
  seen_files = set()
 
641
  if media_file.endswith(('.png', '.jpg')):
642
  st.image(media_file, use_container_width=True)
643
  elif media_file.endswith('.mp4'):
644
+ st.markdown(get_video_html(media_file), unsafe_allow_html=True)
645
  if st.button(f"πŸ‘ {vote_count}", key=f"media_vote_{media_file}"):
646
  await save_vote(MEDIA_VOTES_FILE, media_file, await generate_user_hash(), st.session_state.username)
647
  st.rerun()