awacke1 commited on
Commit
a8499f4
Β·
verified Β·
1 Parent(s): 1a03fd6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -132
app.py CHANGED
@@ -20,15 +20,17 @@ import re
20
  from streamlit_paste_button import paste_image_button
21
  import pytz
22
  import shutil
 
 
23
 
24
- # Patch for nested async - sneaky fix! 🐍✨
25
  nest_asyncio.apply()
26
 
27
- # Static config - constants rule! πŸ“πŸ‘‘
28
  icons = 'πŸ€–πŸ§ πŸ”¬πŸ“'
29
  START_ROOM = "Sector 🌌"
30
 
31
- # Page setup - dressing up the window! πŸ–ΌοΈπŸŽ€
32
  st.set_page_config(
33
  page_title="πŸ€–πŸ§ MMO Chat BrainπŸ“πŸ”¬",
34
  page_icon=icons,
@@ -36,7 +38,7 @@ st.set_page_config(
36
  initial_sidebar_state="auto"
37
  )
38
 
39
- # Funky usernames - who’s who in the zoo with unique voices! πŸŽ­πŸΎπŸŽ™οΈ
40
  FUN_USERNAMES = {
41
  "CosmicJester 🌌": "en-US-AriaNeural",
42
  "PixelPanda 🐼": "en-US-JennyNeural",
@@ -60,7 +62,7 @@ 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"
@@ -78,10 +80,10 @@ QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
78
  MEDIA_VOTES_FILE = os.path.join(VOTE_DIR, "media_votes.md")
79
  HISTORY_FILE = os.path.join(HISTORY_DIR, "chat_history.md")
80
 
81
- # Fancy digits - numbers got style! πŸ”’πŸ’ƒ
82
  UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
83
 
84
- # Massive font collection - typography bonanza! πŸ–‹οΈπŸŽ¨
85
  UNICODE_FONTS = [
86
  ("Normal", lambda x: x),
87
  ("Bold", lambda x: "".join(chr(ord(c) + 0x1D400 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D41A - 0x61) if 'a' <= c <= 'z' else c for c in x)),
@@ -104,7 +106,7 @@ UNICODE_FONTS = [
104
  ("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
105
  ]
106
 
107
- # Global state - keeping tabs! πŸŒπŸ“‹
108
  if 'server_running' not in st.session_state:
109
  st.session_state.server_running = False
110
  if 'server_task' not in st.session_state:
@@ -140,13 +142,12 @@ if 'last_transcript' not in st.session_state:
140
  if 'image_hashes' not in st.session_state:
141
  st.session_state.image_hashes = set()
142
 
143
- # Timestamp wizardry - clock ticks with flair! ⏰🎩
144
  def format_timestamp_prefix(username):
145
  central = pytz.timezone('US/Central')
146
  now = datetime.now(central)
147
  return f"{now.strftime('%I-%M-%p-ct-%m-%d-%Y')}-by-{username}"
148
 
149
- # Compute image hash from binary data
150
  def compute_image_hash(image_data):
151
  if isinstance(image_data, Image.Image):
152
  img_byte_arr = io.BytesIO()
@@ -156,7 +157,6 @@ def compute_image_hash(image_data):
156
  img_bytes = image_data
157
  return hashlib.md5(img_bytes).hexdigest()[:8]
158
 
159
- # Node naming - christening the beast! 🌐🍼
160
  def get_node_name():
161
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
162
  parser.add_argument('--node-name', type=str, default=None)
@@ -166,7 +166,6 @@ def get_node_name():
166
  log_action(username, "🌐🍼 - Node naming - christening the beast!")
167
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
168
 
169
- # Action logger - spying on deeds! πŸ•΅οΈπŸ“œ
170
  def log_action(username, action):
171
  if 'action_log' not in st.session_state:
172
  st.session_state.action_log = {}
@@ -180,13 +179,11 @@ def log_action(username, action):
180
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
181
  user_log[action] = current_time
182
 
183
- # Clean text - strip the fancy stuff! πŸ§ΉπŸ“
184
  def clean_text_for_tts(text):
185
  cleaned = re.sub(r'[#*!\[\]]+', '', text)
186
  cleaned = ' '.join(cleaned.split())
187
  return cleaned[:200] if cleaned else "No text to speak"
188
 
189
- # Chat saver - words locked tight! πŸ’¬πŸ”’
190
  async def save_chat_entry(username, message, is_markdown=False):
191
  await asyncio.to_thread(log_action, username, "πŸ’¬πŸ”’ - Chat saver - words locked tight!")
192
  central = pytz.timezone('US/Central')
@@ -206,7 +203,6 @@ async def save_chat_entry(username, message, is_markdown=False):
206
  st.session_state.last_chat_update = time.time()
207
  return audio_file
208
 
209
- # Chat loader - history unleashed! πŸ“œπŸš€
210
  async def load_chat():
211
  username = st.session_state.get('username', 'System 🌟')
212
  await asyncio.to_thread(log_action, username, "πŸ“œπŸš€ - Chat loader - history unleashed!")
@@ -216,7 +212,6 @@ async def load_chat():
216
  content = await asyncio.to_thread(f.read)
217
  return content
218
 
219
- # User lister - who’s in the gang! πŸ‘₯πŸŽ‰
220
  async def get_user_list(chat_content):
221
  username = st.session_state.get('username', 'System 🌟')
222
  await asyncio.to_thread(log_action, username, "πŸ‘₯πŸŽ‰ - User lister - who’s in the gang!")
@@ -227,13 +222,11 @@ async def get_user_list(chat_content):
227
  users.add(user)
228
  return sorted(list(users))
229
 
230
- # Join checker - been here before? πŸšͺπŸ”
231
  async def has_joined_before(client_id, chat_content):
232
  username = st.session_state.get('username', 'System 🌟')
233
  await asyncio.to_thread(log_action, username, "πŸšͺπŸ” - Join checker - been here before?")
234
  return any(f"Client-{client_id}" in line for line in chat_content.split('\n'))
235
 
236
- # Suggestion maker - old quips resurface! πŸ’‘πŸ“
237
  async def get_message_suggestions(chat_content, prefix):
238
  username = st.session_state.get('username', 'System 🌟')
239
  await asyncio.to_thread(log_action, username, "πŸ’‘πŸ“ - Suggestion maker - old quips resurface!")
@@ -241,7 +234,6 @@ async def get_message_suggestions(chat_content, prefix):
241
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
242
  return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
243
 
244
- # Vote saver - cheers recorded! πŸ‘πŸ“Š
245
  async def save_vote(file, item, user_hash, username, comment=""):
246
  await asyncio.to_thread(log_action, username, "πŸ‘πŸ“Š - Vote saver - cheers recorded!")
247
  central = pytz.timezone('US/Central')
@@ -254,7 +246,6 @@ async def save_vote(file, item, user_hash, username, comment=""):
254
  chat_message += f" - {comment}"
255
  await save_chat_entry(username, chat_message)
256
 
257
- # Vote counter - tallying the love! πŸ†πŸ“ˆ
258
  async def load_votes(file):
259
  username = st.session_state.get('username', 'System 🌟')
260
  await asyncio.to_thread(log_action, username, "πŸ†πŸ“ˆ - Vote counter - tallying the love!")
@@ -275,7 +266,6 @@ async def load_votes(file):
275
  user_votes.add(vote_key)
276
  return votes
277
 
278
- # Hash generator - secret codes ahoy! πŸ”‘πŸ•΅οΈ
279
  async def generate_user_hash():
280
  username = st.session_state.get('username', 'System 🌟')
281
  await asyncio.to_thread(log_action, username, "πŸ”‘πŸ•΅οΈ - Hash generator - secret codes ahoy!")
@@ -283,7 +273,6 @@ async def generate_user_hash():
283
  st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
284
  return st.session_state.user_hash
285
 
286
- # Audio maker - voices come alive! 🎢🌟
287
  async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
288
  username = st.session_state.get('username', 'System 🌟')
289
  await asyncio.to_thread(log_action, username, "🎢🌟 - Audio maker - voices come alive!")
@@ -300,7 +289,6 @@ async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3
300
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: Audio failed - No audio received for '{text}'\n")
301
  return None
302
 
303
- # Audio player - tunes blast off! πŸ”ŠπŸš€
304
  def play_and_download_audio(file_path):
305
  if file_path and os.path.exists(file_path):
306
  st.audio(file_path)
@@ -312,7 +300,6 @@ def play_and_download_audio(file_path):
312
  dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}">🎡 Download {os.path.basename(file_path)}</a>'
313
  st.markdown(dl_link, unsafe_allow_html=True)
314
 
315
- # Image saver - pics preserved with naming! πŸ“ΈπŸ’Ύ
316
  async def save_pasted_image(image, username):
317
  await asyncio.to_thread(log_action, username, "πŸ“ΈπŸ’Ύ - Image saver - pics preserved!")
318
  img_hash = compute_image_hash(image)
@@ -325,7 +312,6 @@ async def save_pasted_image(image, username):
325
  st.session_state.image_hashes.add(img_hash)
326
  return filepath
327
 
328
- # Video renderer - movies roll with autoplay! πŸŽ₯🎬
329
  async def get_video_html(video_path, width="100%"):
330
  username = st.session_state.get('username', 'System 🌟')
331
  await asyncio.to_thread(log_action, username, "πŸŽ₯🎬 - Video renderer - movies roll!")
@@ -334,14 +320,12 @@ async def get_video_html(video_path, width="100%"):
334
  video_url = f"data:video/mp4;base64,{base64.b64encode(video_data).decode()}"
335
  return f'<video width="{width}" controls autoplay><source src="{video_url}" type="video/mp4">Your browser does not support the video tag.</video>'
336
 
337
- # Audio renderer - sounds soar! 🎢✈️
338
  async def get_audio_html(audio_path, width="100%"):
339
  username = st.session_state.get('username', 'System 🌟')
340
  await asyncio.to_thread(log_action, username, "🎢✈️ - Audio renderer - sounds soar!")
341
  audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, audio_path, 'rb').read()).decode()}"
342
  return f'<audio controls style="width: {width};"><source src="{audio_url}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
343
 
344
- # Websocket handler - chat links up! πŸŒπŸ”—
345
  async def websocket_handler(websocket, path):
346
  username = st.session_state.get('username', 'System 🌟')
347
  await asyncio.to_thread(log_action, username, "πŸŒπŸ”— - Websocket handler - chat links up!")
@@ -364,7 +348,6 @@ async def websocket_handler(websocket, path):
364
  if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
365
  del st.session_state.active_connections[room_id][client_id]
366
 
367
- # Message broadcaster - words fly far! πŸ“’βœˆοΈ
368
  async def broadcast_message(message, room_id):
369
  username = st.session_state.get('username', 'System 🌟')
370
  await asyncio.to_thread(log_action, username, "πŸ“’βœˆοΈ - Message broadcaster - words fly far!")
@@ -378,7 +361,6 @@ async def broadcast_message(message, room_id):
378
  for client_id in disconnected:
379
  del st.session_state.active_connections[room_id][client_id]
380
 
381
- # Server starter - web spins up! πŸ–₯οΈπŸŒ€
382
  async def run_websocket_server():
383
  username = st.session_state.get('username', 'System 🌟')
384
  await asyncio.to_thread(log_action, username, "πŸ–₯οΈπŸŒ€ - Server starter - web spins up!")
@@ -387,15 +369,31 @@ async def run_websocket_server():
387
  st.session_state.server_running = True
388
  await server.wait_closed()
389
 
390
- # Voice processor - speech to text! πŸŽ€πŸ“
391
- async def process_voice_input(audio_bytes):
392
- username = st.session_state.get('username', 'System 🌟')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  await asyncio.to_thread(log_action, username, "πŸŽ€πŸ“ - Voice processor - speech to text!")
394
  if audio_bytes:
395
- text = "Voice input simulation"
 
396
  await save_chat_entry(username, text)
 
 
397
 
398
- # Dummy AI lookup function (replace with actual implementation)
399
  async def perform_ai_lookup(query, vocal_summary=True, extended_refs=False, titles_summary=True, full_audio=False, useArxiv=True, useArxivAudio=False):
400
  username = st.session_state.get('username', 'System 🌟')
401
  result = f"AI Lookup Result for '{query}' (Arxiv: {useArxiv}, Audio: {useArxivAudio})"
@@ -405,12 +403,9 @@ async def perform_ai_lookup(query, vocal_summary=True, extended_refs=False, titl
405
  if audio_file:
406
  st.audio(audio_file)
407
 
408
- # Delete all user files function
409
  def delete_user_files():
410
  protected_files = {'app.py', 'requirements.txt', 'README.md'}
411
  deleted_files = []
412
-
413
- # Directories to clear
414
  directories = [MEDIA_DIR, AUDIO_DIR, CHAT_DIR, VOTE_DIR, HISTORY_DIR]
415
 
416
  for directory in directories:
@@ -424,14 +419,12 @@ def delete_user_files():
424
  deleted_files.append(file_path)
425
  except Exception as e:
426
  st.error(f"Failed to delete {file_path}: {e}")
427
- # Remove empty directories
428
  try:
429
  shutil.rmtree(directory, ignore_errors=True)
430
- os.makedirs(directory, exist_ok=True) # Recreate empty directory
431
  except Exception as e:
432
  st.error(f"Failed to remove directory {directory}: {e}")
433
 
434
- # Clear session state caches
435
  st.session_state.image_hashes.clear()
436
  st.session_state.audio_cache.clear()
437
  st.session_state.base64_cache.clear()
@@ -439,7 +432,6 @@ def delete_user_files():
439
 
440
  return deleted_files
441
 
442
- # ASR Component HTML
443
  ASR_HTML = """
444
  <html>
445
  <head>
@@ -525,6 +517,13 @@ ASR_HTML = """
525
  status.textContent = 'Stopped';
526
  startButton.disabled = false;
527
  stopButton.disabled = true;
 
 
 
 
 
 
 
528
  };
529
 
530
  clearButton.onclick = () => {
@@ -553,7 +552,11 @@ ASR_HTML = """
553
  lastUpdateTime = Date.now();
554
  output.textContent = fullTranscript + (interimTranscript ? '... ' + interimTranscript : '');
555
  output.scrollTop = output.scrollHeight;
556
- sendDataToPython({value: fullTranscript, dataType: "json"});
 
 
 
 
557
  }
558
  };
559
 
@@ -603,10 +606,8 @@ ASR_HTML = """
603
  </html>
604
  """
605
 
606
- # Main execution - let’s roll! πŸŽ²πŸš€
607
  def main():
608
  NODE_NAME, port = get_node_name()
609
-
610
  loop = asyncio.new_event_loop()
611
  asyncio.set_event_loop(loop)
612
 
@@ -626,21 +627,42 @@ def main():
626
 
627
  audio_bytes = audio_recorder()
628
  if audio_bytes:
629
- await process_voice_input(audio_bytes)
630
- st.rerun()
 
 
631
 
632
- # Continuous Speech Input (ASR)
633
  st.subheader("🎀 Continuous Speech Input")
634
  asr_component = components.html(ASR_HTML, height=400)
635
  if asr_component and isinstance(asr_component, dict) and 'value' in asr_component:
636
  transcript = asr_component['value'].strip()
 
637
  if transcript and transcript != st.session_state.last_transcript:
638
  st.session_state.transcript_history.append(transcript)
639
- await save_chat_entry(st.session_state.username, transcript, is_markdown=True)
640
  st.session_state.last_transcript = transcript
641
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
 
643
- # Load and display chat
644
  st.subheader(f"{START_ROOM} Chat πŸ’¬")
645
  chat_content = await load_chat()
646
  chat_lines = chat_content.split('\n')
@@ -759,85 +781,4 @@ def main():
759
  await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}")
760
  st.session_state.pasted_image_data = None
761
  st.session_state.message_text = ''
762
- st.rerun()
763
-
764
- tab_main = st.radio("Action:", ["🎀 Voice", "πŸ“Έ Media", "πŸ” ArXiv", "πŸ“ Editor"], horizontal=True)
765
- useArxiv = st.checkbox("Search Arxiv for Research Paper Answers", value=True)
766
- useArxivAudio = st.checkbox("Generate Audio File for Research Paper Answers", value=False)
767
-
768
- st.subheader("Upload Media 🎨🎢πŸŽ₯")
769
- uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3'])
770
- if uploaded_file:
771
- timestamp = format_timestamp_prefix(st.session_state.username)
772
- username = st.session_state.username
773
- ext = uploaded_file.name.split('.')[-1]
774
- file_hash = hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]
775
- if file_hash not in st.session_state.image_hashes:
776
- filename = f"{timestamp}-{file_hash}.{ext}"
777
- file_path = os.path.join(MEDIA_DIR, filename)
778
- await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
779
- st.success(f"Uploaded {filename}")
780
- await save_chat_entry(username, f"Uploaded media: {file_path}")
781
- st.session_state.image_hashes.add(file_hash)
782
- if file_path.endswith('.mp4'):
783
- st.session_state.media_notifications.append(file_path)
784
-
785
- # Big Red Delete Button
786
- st.subheader("πŸ›‘ Danger Zone")
787
- 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):
788
- deleted_files = delete_user_files()
789
- if deleted_files:
790
- st.markdown("### πŸ—‘οΈ Deleted Files:\n" + "\n".join([f"- `{file}`" for file in deleted_files]))
791
- else:
792
- st.markdown("### πŸ—‘οΈ Nothing to Delete!")
793
- st.session_state.image_hashes.clear()
794
- st.session_state.audio_cache.clear()
795
- st.session_state.base64_cache.clear()
796
- st.session_state.displayed_chat_lines.clear()
797
- st.rerun()
798
-
799
- st.subheader("Media Gallery 🎨🎢πŸŽ₯")
800
- media_files = glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg") + glob.glob(f"{MEDIA_DIR}/*.mp4") + glob.glob(f"{MEDIA_DIR}/*.mp3")
801
- if media_files:
802
- media_votes = await load_votes(MEDIA_VOTES_FILE)
803
- st.write("### All Media Uploads")
804
- seen_files = set()
805
- for media_file in sorted(media_files, key=os.path.getmtime, reverse=True):
806
- if media_file not in seen_files:
807
- seen_files.add(media_file)
808
- filename = os.path.basename(media_file)
809
- vote_count = media_votes.get(media_file, 0)
810
- col1, col2 = st.columns([3, 1])
811
- with col1:
812
- st.markdown(f"**{filename}**")
813
- if media_file.endswith(('.png', '.jpg')):
814
- st.image(media_file, use_container_width=True)
815
- elif media_file.endswith('.mp4'):
816
- st.markdown(await get_video_html(media_file), unsafe_allow_html=True)
817
- elif media_file.endswith('.mp3'):
818
- st.markdown(await get_audio_html(media_file), unsafe_allow_html=True)
819
- with col2:
820
- if st.button(f"πŸ‘ {vote_count}", key=f"media_vote_{media_file}"):
821
- await save_vote(MEDIA_VOTES_FILE, media_file, await generate_user_hash(), st.session_state.username)
822
- st.rerun()
823
-
824
- st.subheader("Refresh ⏳")
825
- refresh_rate = st.slider("Refresh Rate", 1, 300, st.session_state.refresh_rate)
826
- st.session_state.refresh_rate = refresh_rate
827
- timer_placeholder = st.empty()
828
- for i in range(st.session_state.refresh_rate, -1, -1):
829
- font_name, font_func = random.choice(UNICODE_FONTS)
830
- countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
831
- timer_placeholder.markdown(f"<p class='timer'>⏳ {font_func('Refresh in:')} {countdown_str}</p>", unsafe_allow_html=True)
832
- time.sleep(1)
833
- st.rerun()
834
-
835
- st.sidebar.subheader("Chat History πŸ“œ")
836
- with open(HISTORY_FILE, 'r') as f:
837
- history_content = f.read()
838
- st.sidebar.markdown(history_content)
839
-
840
- loop.run_until_complete(async_interface())
841
-
842
- if __name__ == "__main__":
843
- main()
 
20
  from streamlit_paste_button import paste_image_button
21
  import pytz
22
  import shutil
23
+ import wave
24
+ import audioop
25
 
26
+ # Patch for nested async
27
  nest_asyncio.apply()
28
 
29
+ # Static config
30
  icons = 'πŸ€–πŸ§ πŸ”¬πŸ“'
31
  START_ROOM = "Sector 🌌"
32
 
33
+ # Page setup
34
  st.set_page_config(
35
  page_title="πŸ€–πŸ§ MMO Chat BrainπŸ“πŸ”¬",
36
  page_icon=icons,
 
38
  initial_sidebar_state="auto"
39
  )
40
 
41
+ # Funky usernames with voices
42
  FUN_USERNAMES = {
43
  "CosmicJester 🌌": "en-US-AriaNeural",
44
  "PixelPanda 🐼": "en-US-JennyNeural",
 
62
  "ChronoChimp πŸ’": "en-GB-LibbyNeural"
63
  }
64
 
65
+ # Folders
66
  CHAT_DIR = "chat_logs"
67
  VOTE_DIR = "vote_logs"
68
  STATE_FILE = "user_state.txt"
 
80
  MEDIA_VOTES_FILE = os.path.join(VOTE_DIR, "media_votes.md")
81
  HISTORY_FILE = os.path.join(HISTORY_DIR, "chat_history.md")
82
 
83
+ # Unicode digits
84
  UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
85
 
86
+ # Font collection
87
  UNICODE_FONTS = [
88
  ("Normal", lambda x: x),
89
  ("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)),
 
106
  ("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
107
  ]
108
 
109
+ # Global state
110
  if 'server_running' not in st.session_state:
111
  st.session_state.server_running = False
112
  if 'server_task' not in st.session_state:
 
142
  if 'image_hashes' not in st.session_state:
143
  st.session_state.image_hashes = set()
144
 
145
+ # Utility functions
146
  def format_timestamp_prefix(username):
147
  central = pytz.timezone('US/Central')
148
  now = datetime.now(central)
149
  return f"{now.strftime('%I-%M-%p-ct-%m-%d-%Y')}-by-{username}"
150
 
 
151
  def compute_image_hash(image_data):
152
  if isinstance(image_data, Image.Image):
153
  img_byte_arr = io.BytesIO()
 
157
  img_bytes = image_data
158
  return hashlib.md5(img_bytes).hexdigest()[:8]
159
 
 
160
  def get_node_name():
161
  parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
162
  parser.add_argument('--node-name', type=str, default=None)
 
166
  log_action(username, "🌐🍼 - Node naming - christening the beast!")
167
  return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
168
 
 
169
  def log_action(username, action):
170
  if 'action_log' not in st.session_state:
171
  st.session_state.action_log = {}
 
179
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
180
  user_log[action] = current_time
181
 
 
182
  def clean_text_for_tts(text):
183
  cleaned = re.sub(r'[#*!\[\]]+', '', text)
184
  cleaned = ' '.join(cleaned.split())
185
  return cleaned[:200] if cleaned else "No text to speak"
186
 
 
187
  async def save_chat_entry(username, message, is_markdown=False):
188
  await asyncio.to_thread(log_action, username, "πŸ’¬πŸ”’ - Chat saver - words locked tight!")
189
  central = pytz.timezone('US/Central')
 
203
  st.session_state.last_chat_update = time.time()
204
  return audio_file
205
 
 
206
  async def load_chat():
207
  username = st.session_state.get('username', 'System 🌟')
208
  await asyncio.to_thread(log_action, username, "πŸ“œπŸš€ - Chat loader - history unleashed!")
 
212
  content = await asyncio.to_thread(f.read)
213
  return content
214
 
 
215
  async def get_user_list(chat_content):
216
  username = st.session_state.get('username', 'System 🌟')
217
  await asyncio.to_thread(log_action, username, "πŸ‘₯πŸŽ‰ - User lister - who’s in the gang!")
 
222
  users.add(user)
223
  return sorted(list(users))
224
 
 
225
  async def has_joined_before(client_id, chat_content):
226
  username = st.session_state.get('username', 'System 🌟')
227
  await asyncio.to_thread(log_action, username, "πŸšͺπŸ” - Join checker - been here before?")
228
  return any(f"Client-{client_id}" in line for line in chat_content.split('\n'))
229
 
 
230
  async def get_message_suggestions(chat_content, prefix):
231
  username = st.session_state.get('username', 'System 🌟')
232
  await asyncio.to_thread(log_action, username, "πŸ’‘πŸ“ - Suggestion maker - old quips resurface!")
 
234
  messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
235
  return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
236
 
 
237
  async def save_vote(file, item, user_hash, username, comment=""):
238
  await asyncio.to_thread(log_action, username, "πŸ‘πŸ“Š - Vote saver - cheers recorded!")
239
  central = pytz.timezone('US/Central')
 
246
  chat_message += f" - {comment}"
247
  await save_chat_entry(username, chat_message)
248
 
 
249
  async def load_votes(file):
250
  username = st.session_state.get('username', 'System 🌟')
251
  await asyncio.to_thread(log_action, username, "πŸ†πŸ“ˆ - Vote counter - tallying the love!")
 
266
  user_votes.add(vote_key)
267
  return votes
268
 
 
269
  async def generate_user_hash():
270
  username = st.session_state.get('username', 'System 🌟')
271
  await asyncio.to_thread(log_action, username, "πŸ”‘πŸ•΅οΈ - Hash generator - secret codes ahoy!")
 
273
  st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
274
  return st.session_state.user_hash
275
 
 
276
  async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
277
  username = st.session_state.get('username', 'System 🌟')
278
  await asyncio.to_thread(log_action, username, "🎢🌟 - Audio maker - voices come alive!")
 
289
  f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] {username}: Audio failed - No audio received for '{text}'\n")
290
  return None
291
 
 
292
  def play_and_download_audio(file_path):
293
  if file_path and os.path.exists(file_path):
294
  st.audio(file_path)
 
300
  dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}">🎡 Download {os.path.basename(file_path)}</a>'
301
  st.markdown(dl_link, unsafe_allow_html=True)
302
 
 
303
  async def save_pasted_image(image, username):
304
  await asyncio.to_thread(log_action, username, "πŸ“ΈπŸ’Ύ - Image saver - pics preserved!")
305
  img_hash = compute_image_hash(image)
 
312
  st.session_state.image_hashes.add(img_hash)
313
  return filepath
314
 
 
315
  async def get_video_html(video_path, width="100%"):
316
  username = st.session_state.get('username', 'System 🌟')
317
  await asyncio.to_thread(log_action, username, "πŸŽ₯🎬 - Video renderer - movies roll!")
 
320
  video_url = f"data:video/mp4;base64,{base64.b64encode(video_data).decode()}"
321
  return f'<video width="{width}" controls autoplay><source src="{video_url}" type="video/mp4">Your browser does not support the video tag.</video>'
322
 
 
323
  async def get_audio_html(audio_path, width="100%"):
324
  username = st.session_state.get('username', 'System 🌟')
325
  await asyncio.to_thread(log_action, username, "🎢✈️ - Audio renderer - sounds soar!")
326
  audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, audio_path, 'rb').read()).decode()}"
327
  return f'<audio controls style="width: {width};"><source src="{audio_url}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
328
 
 
329
  async def websocket_handler(websocket, path):
330
  username = st.session_state.get('username', 'System 🌟')
331
  await asyncio.to_thread(log_action, username, "πŸŒπŸ”— - Websocket handler - chat links up!")
 
348
  if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
349
  del st.session_state.active_connections[room_id][client_id]
350
 
 
351
  async def broadcast_message(message, room_id):
352
  username = st.session_state.get('username', 'System 🌟')
353
  await asyncio.to_thread(log_action, username, "πŸ“’βœˆοΈ - Message broadcaster - words fly far!")
 
361
  for client_id in disconnected:
362
  del st.session_state.active_connections[room_id][client_id]
363
 
 
364
  async def run_websocket_server():
365
  username = st.session_state.get('username', 'System 🌟')
366
  await asyncio.to_thread(log_action, username, "πŸ–₯οΈπŸŒ€ - Server starter - web spins up!")
 
369
  st.session_state.server_running = True
370
  await server.wait_closed()
371
 
372
+ async def save_audio_recording(audio_bytes, username, voice):
373
+ timestamp = format_timestamp_prefix(username)
374
+ voice_id = voice.split('-')[-1].lower()
375
+ filename = f"rec_{username}_{voice_id}_{timestamp}.mp3"
376
+ filepath = os.path.join(AUDIO_DIR, filename)
377
+
378
+ with open(filepath, 'wb') as f:
379
+ f.write(audio_bytes)
380
+
381
+ cleaned_text = "Audio recording"
382
+ audio_file = await async_edge_tts_generate(cleaned_text, voice, file_format="mp3")
383
+ if audio_file:
384
+ os.rename(audio_file, filepath)
385
+
386
+ return filepath
387
+
388
+ async def process_voice_input(audio_bytes, username, voice):
389
  await asyncio.to_thread(log_action, username, "πŸŽ€πŸ“ - Voice processor - speech to text!")
390
  if audio_bytes:
391
+ audio_file = await save_audio_recording(audio_bytes, username, voice)
392
+ text = f"Voice recording saved: {os.path.basename(audio_file)}"
393
  await save_chat_entry(username, text)
394
+ return audio_file
395
+ return None
396
 
 
397
  async def perform_ai_lookup(query, vocal_summary=True, extended_refs=False, titles_summary=True, full_audio=False, useArxiv=True, useArxivAudio=False):
398
  username = st.session_state.get('username', 'System 🌟')
399
  result = f"AI Lookup Result for '{query}' (Arxiv: {useArxiv}, Audio: {useArxivAudio})"
 
403
  if audio_file:
404
  st.audio(audio_file)
405
 
 
406
  def delete_user_files():
407
  protected_files = {'app.py', 'requirements.txt', 'README.md'}
408
  deleted_files = []
 
 
409
  directories = [MEDIA_DIR, AUDIO_DIR, CHAT_DIR, VOTE_DIR, HISTORY_DIR]
410
 
411
  for directory in directories:
 
419
  deleted_files.append(file_path)
420
  except Exception as e:
421
  st.error(f"Failed to delete {file_path}: {e}")
 
422
  try:
423
  shutil.rmtree(directory, ignore_errors=True)
424
+ os.makedirs(directory, exist_ok=True)
425
  except Exception as e:
426
  st.error(f"Failed to remove directory {directory}: {e}")
427
 
 
428
  st.session_state.image_hashes.clear()
429
  st.session_state.audio_cache.clear()
430
  st.session_state.base64_cache.clear()
 
432
 
433
  return deleted_files
434
 
 
435
  ASR_HTML = """
436
  <html>
437
  <head>
 
517
  status.textContent = 'Stopped';
518
  startButton.disabled = false;
519
  stopButton.disabled = true;
520
+ if (fullTranscript) {
521
+ sendDataToPython({
522
+ value: fullTranscript,
523
+ dataType: "json",
524
+ stopped: true
525
+ });
526
+ }
527
  };
528
 
529
  clearButton.onclick = () => {
 
552
  lastUpdateTime = Date.now();
553
  output.textContent = fullTranscript + (interimTranscript ? '... ' + interimTranscript : '');
554
  output.scrollTop = output.scrollHeight;
555
+ sendDataToPython({
556
+ value: fullTranscript,
557
+ dataType: "json",
558
+ stopped: false
559
+ });
560
  }
561
  };
562
 
 
606
  </html>
607
  """
608
 
 
609
  def main():
610
  NODE_NAME, port = get_node_name()
 
611
  loop = asyncio.new_event_loop()
612
  asyncio.set_event_loop(loop)
613
 
 
627
 
628
  audio_bytes = audio_recorder()
629
  if audio_bytes:
630
+ audio_file = await process_voice_input(audio_bytes, st.session_state.username, st.session_state.voice)
631
+ if audio_file:
632
+ st.audio(audio_file)
633
+ st.rerun()
634
 
 
635
  st.subheader("🎀 Continuous Speech Input")
636
  asr_component = components.html(ASR_HTML, height=400)
637
  if asr_component and isinstance(asr_component, dict) and 'value' in asr_component:
638
  transcript = asr_component['value'].strip()
639
+ stopped = asr_component.get('stopped', False)
640
  if transcript and transcript != st.session_state.last_transcript:
641
  st.session_state.transcript_history.append(transcript)
 
642
  st.session_state.last_transcript = transcript
643
+ if stopped:
644
+ audio_file = await save_audio_recording(audio_bytes, st.session_state.username, st.session_state.voice)
645
+ await save_chat_entry(st.session_state.username, f"Voice message: {transcript}\nAudio file: {os.path.basename(audio_file)}", is_markdown=True)
646
+ st.rerun()
647
+
648
+ st.subheader("🎡 Recorded Audio Files")
649
+ audio_files = glob.glob(f"{AUDIO_DIR}/rec_{st.session_state.username}_*.mp3")
650
+ if audio_files:
651
+ st.write(f"Found {len(audio_files)} recordings for {st.session_state.username}")
652
+ for audio_file in sorted(audio_files, key=os.path.getmtime, reverse=True):
653
+ col1, col2 = st.columns([3, 1])
654
+ with col1:
655
+ st.audio(audio_file)
656
+ st.write(f"File: {os.path.basename(audio_file)}")
657
+ with col2:
658
+ if audio_file not in st.session_state.base64_cache:
659
+ with open(audio_file, "rb") as f:
660
+ b64 = base64.b64encode(f.read()).decode()
661
+ st.session_state.base64_cache[audio_file] = b64
662
+ b64 = st.session_state.base64_cache[audio_file]
663
+ dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(audio_file)}">🎡 Download</a>'
664
+ st.markdown(dl_link, unsafe_allow_html=True)
665
 
 
666
  st.subheader(f"{START_ROOM} Chat πŸ’¬")
667
  chat_content = await load_chat()
668
  chat_lines = chat_content.split('\n')
 
781
  await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}")
782
  st.session_state.pasted_image_data = None
783
  st.session_state.message_text = ''
784
+ st.rerun()