awacke1 commited on
Commit
e987f4a
·
verified ·
1 Parent(s): b020576

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +336 -404
app.py CHANGED
@@ -1,9 +1,9 @@
 
1
  import streamlit as st
2
  import asyncio
3
  import websockets
4
  import uuid
5
  import argparse
6
- from datetime import datetime
7
  import os
8
  import random
9
  import time
@@ -17,7 +17,6 @@ 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
  import anthropic
@@ -29,23 +28,24 @@ import zipfile
29
  from gradio_client import Client
30
  from dotenv import load_dotenv
31
  from streamlit_marquee import streamlit_marquee
 
 
 
32
 
33
- # Patch for nested async
34
  nest_asyncio.apply()
35
 
36
- # Static config
37
- icons = '🤖🧠🔬📝'
38
- START_ROOM = "Sector 🌌"
39
-
40
- # Page setup
41
  st.set_page_config(
42
- page_title="🤖🧠MMO Chat & Research Brain📝🔬",
43
- page_icon=icons,
44
  layout="wide",
45
  initial_sidebar_state="auto"
46
  )
47
 
48
- # Funky usernames with voices
 
 
49
  FUN_USERNAMES = {
50
  "CosmicJester 🌌": "en-US-AriaNeural",
51
  "PixelPanda 🐼": "en-US-JennyNeural",
@@ -57,481 +57,413 @@ FUN_USERNAMES = {
57
  "GalacticGopher 🌍": "en-AU-WilliamNeural",
58
  "RocketRaccoon 🚀": "en-CA-LiamNeural",
59
  "EchoElf 🧝": "en-US-AnaNeural",
60
- "PhantomFox 🦊": "en-US-BrandonNeural",
61
- "WittyWizard 🧙": "en-GB-ThomasNeural",
62
- "LunarLlama 🌙": "en-AU-FreyaNeural",
63
- "SolarSloth ☀️": "en-CA-LindaNeural",
64
- "AstroAlpaca 🦙": "en-US-ChristopherNeural",
65
- "CyberCoyote 🐺": "en-GB-ElliotNeural",
66
- "MysticMoose 🦌": "en-AU-JamesNeural",
67
- "GlitchGnome 🧚": "en-CA-EthanNeural",
68
- "VortexViper 🐍": "en-US-AmberNeural",
69
- "ChronoChimp 🐒": "en-GB-LibbyNeural"
70
  }
 
 
 
 
 
 
71
 
72
- # Directories
73
- CHAT_DIR = "chat_logs"
74
- VOTE_DIR = "vote_logs"
75
- AUDIO_DIR = "audio_logs"
76
- HISTORY_DIR = "history_logs"
77
  MEDIA_DIR = "media_files"
78
- os.makedirs(CHAT_DIR, exist_ok=True)
79
- os.makedirs(VOTE_DIR, exist_ok=True)
80
- os.makedirs(AUDIO_DIR, exist_ok=True)
81
- os.makedirs(HISTORY_DIR, exist_ok=True)
82
- os.makedirs(MEDIA_DIR, exist_ok=True)
83
-
84
- CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
85
- QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
86
- MEDIA_VOTES_FILE = os.path.join(VOTE_DIR, "media_votes.md")
87
- HISTORY_FILE = os.path.join(HISTORY_DIR, "chat_history.md")
88
-
89
- # Unicode digits
90
- UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
91
-
92
- # Unicode fonts (simplified for brevity)
93
- UNICODE_FONTS = [
94
- ("Normal", lambda x: x),
95
- ("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
- # Add other font styles as needed...
97
- ]
98
-
99
- # Global state
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:
103
- st.session_state.server_task = None
104
- if 'active_connections' not in st.session_state:
105
- st.session_state.active_connections = {}
106
- if 'media_notifications' not in st.session_state:
107
- st.session_state.media_notifications = []
108
- if 'last_chat_update' not in st.session_state:
109
- st.session_state.last_chat_update = 0
110
- if 'displayed_chat_lines' not in st.session_state:
111
- st.session_state.displayed_chat_lines = []
112
- if 'message_text' not in st.session_state:
113
- st.session_state.message_text = ""
114
- if 'audio_cache' not in st.session_state:
115
- st.session_state.audio_cache = {}
116
- if 'pasted_image_data' not in st.session_state:
117
- st.session_state.pasted_image_data = None
118
- if 'quote_line' not in st.session_state:
119
- st.session_state.quote_line = None
120
- if 'refresh_rate' not in st.session_state:
121
- st.session_state.refresh_rate = 5
122
- if 'base64_cache' not in st.session_state:
123
- st.session_state.base64_cache = {}
124
- if 'transcript_history' not in st.session_state:
125
- st.session_state.transcript_history = []
126
- if 'last_transcript' not in st.session_state:
127
- st.session_state.last_transcript = ""
128
- if 'image_hashes' not in st.session_state:
129
- st.session_state.image_hashes = set()
130
- if 'tts_voice' not in st.session_state:
131
- st.session_state.tts_voice = "en-US-AriaNeural"
132
- if 'chat_history' not in st.session_state:
133
- st.session_state.chat_history = []
134
-
135
- # API Keys
136
  load_dotenv()
137
- anthropic_key = os.getenv('ANTHROPIC_API_KEY', "")
138
- openai_api_key = os.getenv('OPENAI_API_KEY', "")
139
- if 'ANTHROPIC_API_KEY' in st.secrets:
140
- anthropic_key = st.secrets['ANTHROPIC_API_KEY']
141
- if 'OPENAI_API_KEY' in st.secrets:
142
- openai_api_key = st.secrets['OPENAI_API_KEY']
143
  openai_client = openai.OpenAI(api_key=openai_api_key)
144
 
145
- # Timestamp formatting
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
- # Image hash computation
152
- def compute_image_hash(image_data):
153
- if isinstance(image_data, Image.Image):
154
- img_byte_arr = io.BytesIO()
155
- image_data.save(img_byte_arr, format='PNG')
156
- img_bytes = img_byte_arr.getvalue()
157
- else:
158
- img_bytes = image_data
159
- return hashlib.md5(img_bytes).hexdigest()[:8]
160
-
161
- # Node naming
162
- def get_node_name():
163
- parser = argparse.ArgumentParser(description='Start a chat node')
164
- parser.add_argument('--node-name', type=str, default=None)
165
- parser.add_argument('--port', type=int, default=8501)
166
- args = parser.parse_args()
167
- return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
168
-
169
- # Action logger
170
- def log_action(username, action):
171
- if 'action_log' not in st.session_state:
172
- st.session_state.action_log = {}
173
- user_log = st.session_state.action_log.setdefault(username, {})
174
- current_time = time.time()
175
- user_log = {k: v for k, v in user_log.items() if current_time - v < 10}
176
- st.session_state.action_log[username] = user_log
177
- if action not in user_log:
178
- central = pytz.timezone('US/Central')
179
- with open(HISTORY_FILE, 'a') as f:
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
- # Text cleaning for TTS
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  async def save_chat_entry(username, message, is_markdown=False):
191
- await asyncio.to_thread(log_action, username, "💬🔒 - Chat saver")
192
  central = pytz.timezone('US/Central')
193
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
194
- if is_markdown:
195
- entry = f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
196
- else:
197
- entry = f"[{timestamp}] {username}: {message}"
198
- await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"{entry}\n"))
199
  voice = FUN_USERNAMES.get(username, "en-US-AriaNeural")
200
- cleaned_message = clean_text_for_tts(message)
201
- audio_file = await async_edge_tts_generate(cleaned_message, voice)
202
  if audio_file:
203
- with open(HISTORY_FILE, 'a') as f:
204
- f.write(f"[{timestamp}] {username}: Audio generated - {audio_file}\n")
205
  await broadcast_message(f"{username}|{message}", "chat")
206
  st.session_state.last_chat_update = time.time()
207
  return audio_file
208
 
209
- # Chat loader
210
  async def load_chat():
211
- username = st.session_state.get('username', 'System 🌟')
212
- await asyncio.to_thread(log_action, username, "📜🚀 - Chat loader")
213
  if not os.path.exists(CHAT_FILE):
214
- await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"# {START_ROOM} Chat\n\nWelcome to the cosmic hub! 🎤\n"))
215
- with open(CHAT_FILE, 'r') as f:
216
- content = await asyncio.to_thread(f.read)
217
- return content
218
 
219
- # Audio generator
220
- async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
221
- await asyncio.to_thread(log_action, st.session_state.get('username', 'System 🌟'), "🎶🌟 - Audio maker")
222
- timestamp = format_timestamp_prefix(st.session_state.get('username', 'System 🌟'))
223
- filename = f"{timestamp}.{file_format}"
224
- filepath = os.path.join(AUDIO_DIR, filename)
225
- communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
226
- try:
227
- await communicate.save(filepath)
228
- return filepath if os.path.exists(filepath) else None
229
- except edge_tts.exceptions.NoAudioReceived:
230
- with open(HISTORY_FILE, 'a') as f:
231
- central = pytz.timezone('US/Central')
232
- f.write(f"[{datetime.now(central).strftime('%Y-%m-%d %H:%M:%S')}] Audio failed for '{text}'\n")
233
- return None
234
-
235
- # Audio player
236
- def play_and_download_audio(file_path):
237
- if file_path and os.path.exists(file_path):
238
- st.audio(file_path)
239
- if file_path not in st.session_state.base64_cache:
240
- with open(file_path, "rb") as f:
241
- b64 = base64.b64encode(f.read()).decode()
242
- st.session_state.base64_cache[file_path] = b64
243
- b64 = st.session_state.base64_cache[file_path]
244
- dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}">🎵 Download {os.path.basename(file_path)}</a>'
245
- st.markdown(dl_link, unsafe_allow_html=True)
246
-
247
- # Websocket handler
248
  async def websocket_handler(websocket, path):
249
- username = st.session_state.get('username', 'System 🌟')
250
- await asyncio.to_thread(log_action, username, "🌐🔗 - Websocket handler")
 
 
 
 
 
 
251
  try:
252
- client_id = str(uuid.uuid4())
253
- room_id = "chat"
254
- st.session_state.active_connections.setdefault(room_id, {})[client_id] = websocket
255
- chat_content = await load_chat()
256
- username = st.session_state.get('username', random.choice(list(FUN_USERNAMES.keys())))
257
- if not any(f"Client-{client_id}" in line for line in chat_content.split('\n')):
258
- await save_chat_entry(f"Client-{client_id}", f"{username} has joined {START_ROOM}!")
259
  async for message in websocket:
260
- parts = message.split('|', 1)
261
- if len(parts) == 2:
262
- username, content = parts
263
- await save_chat_entry(username, content)
264
- except websockets.ConnectionClosed:
265
- pass
266
  finally:
267
  if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
268
  del st.session_state.active_connections[room_id][client_id]
269
 
270
- # Message broadcaster
271
  async def broadcast_message(message, room_id):
272
- await asyncio.to_thread(log_action, st.session_state.get('username', 'System 🌟'), "📢✈️ - Message broadcaster")
273
  if room_id in st.session_state.active_connections:
274
  disconnected = []
275
  for client_id, ws in st.session_state.active_connections[room_id].items():
276
- try:
277
- await ws.send(message)
278
- except websockets.ConnectionClosed:
279
- disconnected.append(client_id)
280
- for client_id in disconnected:
281
- del st.session_state.active_connections[room_id][client_id]
282
 
283
- # Server starter
284
  async def run_websocket_server():
285
- await asyncio.to_thread(log_action, st.session_state.get('username', 'System 🌟'), "🖥️🌀 - Server starter")
286
  if not st.session_state.server_running:
287
  server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
288
  st.session_state.server_running = True
289
  await server.wait_closed()
290
 
291
- # PDF to Audio Processor
292
  class AudioProcessor:
293
  def __init__(self):
294
- self.cache_dir = "audio_cache"
295
  os.makedirs(self.cache_dir, exist_ok=True)
296
- self.metadata = self._load_metadata()
297
-
298
- def _load_metadata(self):
299
- metadata_file = os.path.join(self.cache_dir, "metadata.json")
300
- return json.load(open(metadata_file)) if os.path.exists(metadata_file) else {}
301
 
302
  def _save_metadata(self):
303
- metadata_file = os.path.join(self.cache_dir, "metadata.json")
304
- with open(metadata_file, 'w') as f:
305
- json.dump(self.metadata, f)
306
 
307
  async def create_audio(self, text, voice='en-US-AriaNeural'):
 
308
  cache_key = hashlib.md5(f"{text}:{voice}".encode()).hexdigest()
309
- cache_path = os.path.join(self.cache_dir, f"{cache_key}.mp3")
310
  if cache_key in self.metadata and os.path.exists(cache_path):
311
  return open(cache_path, 'rb').read()
312
- text = text.replace("\n", " ").replace("</s>", " ").strip()
313
- if not text:
314
- return None
315
  communicate = edge_tts.Communicate(text, voice)
316
  await communicate.save(cache_path)
317
- self.metadata[cache_key] = {
318
- 'timestamp': datetime.now().isoformat(),
319
- 'text_length': len(text),
320
- 'voice': voice
321
- }
322
  self._save_metadata()
323
  return open(cache_path, 'rb').read()
324
 
325
- def get_download_link(bin_data, filename, size_mb=None):
326
- b64 = base64.b64encode(bin_data).decode()
327
- size_str = f"({size_mb:.1f} MB)" if size_mb else ""
328
- return f'<a href="data:audio/mpeg;base64,{b64}" download="{filename}">📥 {filename} {size_str}</a>'
329
-
330
  def process_pdf(pdf_file, max_pages, voice, audio_processor):
 
331
  reader = PdfReader(pdf_file)
332
  total_pages = min(len(reader.pages), max_pages)
333
  texts, audios = [], {}
334
- async def process_page(i, text):
335
- audio_data = await audio_processor.create_audio(text, voice)
336
- audios[i] = audio_data
337
  for i in range(total_pages):
338
  text = reader.pages[i].extract_text()
339
  texts.append(text)
340
  threading.Thread(target=lambda: asyncio.run(process_page(i, text))).start()
341
  return texts, audios, total_pages
342
 
343
- # AI Lookup
344
- def perform_ai_lookup(q, vocal_summary=True, extended_refs=False, titles_summary=True, full_audio=False, useArxiv=True, useArxivAudio=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  client = anthropic.Anthropic(api_key=anthropic_key)
346
- response = client.messages.create(
347
- model="claude-3-sonnet-20240229",
348
- max_tokens=1000,
349
- messages=[{"role": "user", "content": q}]
350
- )
351
  result = response.content[0].text
352
- st.markdown("### Claude's reply 🧠:")
353
- st.markdown(result)
354
  md_file = create_file(q, result)
355
- audio_file = speak_with_edge_tts(result, st.session_state.tts_voice)
356
  play_and_download_audio(audio_file)
 
357
  if useArxiv:
358
  q += result
359
  gradio_client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
360
  refs = gradio_client.predict(q, 10, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md")[0]
361
  result = f"🔎 {q}\n\n{refs}"
362
- md_file, audio_file = save_qa_with_audio(q, result)
363
  play_and_download_audio(audio_file)
364
  papers = parse_arxiv_refs(refs)
365
- if papers and useArxivAudio:
366
- asyncio.run(create_paper_audio_files(papers, q))
367
  return result, papers
368
  return result, []
369
 
370
- def create_file(prompt, response, file_type="md"):
371
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
372
- filename = f"{timestamp}_{clean_text_for_filename(prompt[:40] + ' ' + response[:40])}.{file_type}"
373
- with open(filename, 'w', encoding='utf-8') as f:
374
- f.write(prompt + "\n\n" + response)
375
- return filename
376
-
377
- def speak_with_edge_tts(text, voice="en-US-AriaNeural", rate=0, pitch=0, file_format="mp3"):
378
- result = asyncio.run(async_edge_tts_generate(text, voice, rate, pitch, file_format))
379
- return result
380
-
381
- def save_qa_with_audio(question, answer, voice=None):
382
- voice = voice or st.session_state.tts_voice
383
- md_file = create_file(question, answer, "md")
384
- audio_file = speak_with_edge_tts(f"{question}\n\nAnswer: {answer}", voice)
385
- return md_file, audio_file
386
-
387
- def clean_text_for_filename(text):
388
- text = text.lower()
389
- text = re.sub(r'[^\w\s-]', '', text)
390
- return '_'.join(text.split())[:200]
391
-
392
- def parse_arxiv_refs(ref_text):
393
- return [{"title": line.strip(), "url": "", "authors": "", "summary": "", "full_audio": None, "download_base64": ""} for line in ref_text.split('\n') if line.strip()]
394
-
395
- async def create_paper_audio_files(papers, input_question):
396
- for paper in papers:
397
- audio_text = f"{paper['title']}"
398
- audio_file = await async_edge_tts_generate(audio_text, st.session_state.tts_voice)
399
- paper['full_audio'] = audio_file
400
- if audio_file:
401
- with open(audio_file, "rb") as f:
402
- b64 = base64.b64encode(f.read()).decode()
403
- paper['download_base64'] = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(audio_file)}">🎵 Download</a>'
404
-
405
- # Main execution
406
- def main():
407
- NODE_NAME, port = get_node_name()
408
- loop = asyncio.new_event_loop()
409
- asyncio.set_event_loop(loop)
410
-
411
- async def async_interface():
412
- if 'username' not in st.session_state:
413
- chat_content = await load_chat()
414
- available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
415
- st.session_state.username = random.choice(available_names) if available_names else random.choice(list(FUN_USERNAMES.keys()))
416
- st.session_state.tts_voice = FUN_USERNAMES[st.session_state.username]
417
- st.markdown(f"**🎙️ Voice**: {st.session_state.tts_voice} 🗣️ for {st.session_state.username}")
418
-
419
- st.title(f"🤖🧠MMO Chat & Research for {st.session_state.username}📝🔬")
420
- st.markdown(f"Welcome to {START_ROOM} - chat, research, upload, and more! 🎉")
421
-
422
- if not st.session_state.server_task:
423
- st.session_state.server_task = loop.create_task(run_websocket_server())
424
-
425
- # Tabs
426
- tab_main = st.radio("Action:", ["🎤 Chat & Voice", "📸 Media", "🔍 ArXiv", "📚 PDF to Audio"], horizontal=True)
427
- useArxiv = st.checkbox("Search Arxiv", value=True)
428
- useArxivAudio = st.checkbox("Generate Arxiv Audio", value=False)
429
-
430
- # Chat & Voice Tab
431
- if tab_main == "🎤 Chat & Voice":
432
- st.subheader(f"{START_ROOM} Chat 💬")
433
- chat_content = await load_chat()
434
- chat_lines = chat_content.split('\n')
435
- for i, line in enumerate(chat_lines):
436
- if line.strip() and ': ' in line:
437
- st.markdown(line)
438
- if st.button("📢 Speak", key=f"speak_{i}"):
439
- audio_file = await async_edge_tts_generate(clean_text_for_tts(line.split(': ', 1)[1]), st.session_state.tts_voice)
440
- play_and_download_audio(audio_file)
441
-
442
- message = st.text_input(f"Message as {st.session_state.username}", key="message_input")
443
- if st.button("Send 🚀") and message.strip():
444
- await save_chat_entry(st.session_state.username, message, is_markdown=True)
445
- st.rerun()
446
 
447
- # Speech intake using the custom component
448
- st.subheader("🎤 Continuous Speech Input")
449
- from mycomponent import speech_component # Import the component
450
- transcript_data = speech_component(default_value=st.session_state.get('last_transcript', ''))
451
- if transcript_data and 'value' in transcript_data:
452
- transcript = transcript_data['value'].strip()
453
- if transcript and transcript != st.session_state.last_transcript:
454
- await save_chat_entry(st.session_state.username, transcript, is_markdown=True)
455
- st.session_state.last_transcript = transcript
456
- st.rerun()
457
-
458
- # Media Tab with Galleries
459
- elif tab_main == "📸 Media":
460
- st.header("📸 Media Gallery")
461
- tabs = st.tabs(["🎵 Audio", "🖼 Images", "🎥 Video"])
462
- with tabs[0]:
463
- st.subheader("🎵 Audio Files")
464
- audio_files = glob.glob(f"{MEDIA_DIR}/*.mp3")
465
- for a in audio_files:
466
- with st.expander(os.path.basename(a)):
467
- play_and_download_audio(a)
468
- with tabs[1]:
469
- st.subheader("🖼 Images")
470
- imgs = glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg")
471
- if imgs:
472
- cols = st.columns(3)
473
- for i, f in enumerate(imgs):
474
- with cols[i % 3]:
475
- st.image(f, use_container_width=True)
476
- with tabs[2]:
477
- st.subheader("🎥 Videos")
478
- vids = glob.glob(f"{MEDIA_DIR}/*.mp4")
479
- for v in vids:
480
- with st.expander(os.path.basename(v)):
481
- st.video(v)
482
-
483
- uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3'])
484
- if uploaded_file:
485
- timestamp = format_timestamp_prefix(st.session_state.username)
486
- ext = uploaded_file.name.split('.')[-1]
487
- file_hash = hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]
488
- filename = f"{timestamp}-{file_hash}.{ext}"
489
- file_path = os.path.join(MEDIA_DIR, filename)
490
- with open(file_path, 'wb') as f:
491
- f.write(uploaded_file.getbuffer())
492
- await save_chat_entry(st.session_state.username, f"Uploaded media: {file_path}")
493
  st.rerun()
494
 
495
- # ArXiv Tab
496
- elif tab_main == "🔍 ArXiv":
497
- st.subheader("🔍 Query ArXiv")
498
- q = st.text_input("🔍 Query:")
499
- if q and st.button("🔍 Run"):
500
- result, papers = perform_ai_lookup(q, useArxiv=useArxiv, useArxivAudio=useArxivAudio)
501
- for paper in papers:
502
- with st.expander(paper['title']):
503
- st.markdown(f"**Summary**: {paper['summary']}")
504
- if paper['full_audio']:
505
- play_and_download_audio(paper['full_audio'])
506
-
507
- # PDF to Audio Tab
508
- elif tab_main == "📚 PDF to Audio":
509
- st.subheader("📚 PDF to Audio Converter")
510
- audio_processor = AudioProcessor()
511
- uploaded_file = st.file_uploader("Choose a PDF file", "pdf")
512
- max_pages = st.slider('Pages to process', 1, 100, 10)
513
- if uploaded_file:
514
- with st.spinner('Processing PDF...'):
515
- texts, audios, total_pages = process_pdf(uploaded_file, max_pages, st.session_state.tts_voice, audio_processor)
516
- for i, text in enumerate(texts):
517
- with st.expander(f"Page {i+1}"):
518
- st.markdown(text)
519
- while i not in audios:
520
- time.sleep(0.1)
521
- if audios[i]:
522
- st.audio(audios[i], format='audio/mp3')
523
- st.markdown(get_download_link(audios[i], f'page_{i+1}.mp3', len(audios[i]) / (1024 * 1024)), unsafe_allow_html=True)
524
-
525
- # Sidebar
526
- st.sidebar.subheader("Voice Settings")
527
- new_username = st.sidebar.selectbox("Change Name/Voice", list(FUN_USERNAMES.keys()), index=list(FUN_USERNAMES.keys()).index(st.session_state.username))
528
- if new_username != st.session_state.username:
529
- await save_chat_entry("System 🌟", f"{st.session_state.username} changed to {new_username}")
530
- st.session_state.username = new_username
531
- st.session_state.tts_voice = FUN_USERNAMES[new_username]
532
  st.rerun()
533
 
534
- loop.run_until_complete(async_interface())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
 
536
  if __name__ == "__main__":
537
  main()
 
1
+ # 🚀 Main App - TalkingAIResearcher with Chat, Voice, Media, ArXiv, and More
2
  import streamlit as st
3
  import asyncio
4
  import websockets
5
  import uuid
6
  import argparse
 
7
  import os
8
  import random
9
  import time
 
17
  from audio_recorder_streamlit import audio_recorder
18
  import nest_asyncio
19
  import re
 
20
  import pytz
21
  import shutil
22
  import anthropic
 
28
  from gradio_client import Client
29
  from dotenv import load_dotenv
30
  from streamlit_marquee import streamlit_marquee
31
+ from datetime import datetime
32
+ from collections import defaultdict, Counter
33
+ import pandas as pd
34
 
35
+ # 🛠️ Patch asyncio for nesting glory
36
  nest_asyncio.apply()
37
 
38
+ # 🎨 Page Config
 
 
 
 
39
  st.set_page_config(
40
+ page_title="🚲TalkingAIResearcher🏆",
41
+ page_icon="🚲🏆",
42
  layout="wide",
43
  initial_sidebar_state="auto"
44
  )
45
 
46
+ # 🌟 Static Config
47
+ icons = '🤖🧠🔬📝'
48
+ START_ROOM = "Sector 🌌"
49
  FUN_USERNAMES = {
50
  "CosmicJester 🌌": "en-US-AriaNeural",
51
  "PixelPanda 🐼": "en-US-JennyNeural",
 
57
  "GalacticGopher 🌍": "en-AU-WilliamNeural",
58
  "RocketRaccoon 🚀": "en-CA-LiamNeural",
59
  "EchoElf 🧝": "en-US-AnaNeural",
 
 
 
 
 
 
 
 
 
 
60
  }
61
+ EDGE_TTS_VOICES = list(set(FUN_USERNAMES.values())) # 🎙️ Voice options
62
+ FILE_EMOJIS = {"md": "📝", "mp3": "🎵", "wav": "🔊"}
63
+
64
+ # 📁 Directories
65
+ for d in ["chat_logs", "vote_logs", "audio_logs", "history_logs", "media_files", "audio_cache"]:
66
+ os.makedirs(d, exist_ok=True)
67
 
68
+ CHAT_FILE = "chat_logs/global_chat.md"
69
+ HISTORY_FILE = "history_logs/chat_history.md"
 
 
 
70
  MEDIA_DIR = "media_files"
71
+ AUDIO_CACHE_DIR = "audio_cache"
72
+
73
+ # 🔑 API Keys
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  load_dotenv()
75
+ anthropic_key = os.getenv('ANTHROPIC_API_KEY', st.secrets.get('ANTHROPIC_API_KEY', ""))
76
+ openai_api_key = os.getenv('OPENAI_API_KEY', st.secrets.get('OPENAI_API_KEY', ""))
 
 
 
 
77
  openai_client = openai.OpenAI(api_key=openai_api_key)
78
 
79
+ # 🕒 Timestamp Helper
80
+ def format_timestamp_prefix(username=""):
81
  central = pytz.timezone('US/Central')
82
  now = datetime.now(central)
83
+ return f"{now.strftime('%Y%m%d_%H%M%S')}-by-{username}"
84
+
85
+ # 📈 Performance Timer
86
+ class PerformanceTimer:
87
+ def __init__(self, name): self.name, self.start = name, None
88
+ def __enter__(self): self.start = time.time(); return self
89
+ def __exit__(self, *args):
90
+ duration = time.time() - self.start
91
+ st.session_state['operation_timings'][self.name] = duration
92
+ st.session_state['performance_metrics'][self.name].append(duration)
93
+
94
+ # 🎛️ Session State Init
95
+ def init_session_state():
96
+ defaults = {
97
+ 'server_running': False, 'server_task': None, 'active_connections': {},
98
+ 'media_notifications': [], 'last_chat_update': 0, 'displayed_chat_lines': [],
99
+ 'message_text': "", 'audio_cache': {}, 'pasted_image_data': None,
100
+ 'quote_line': None, 'refresh_rate': 5, 'base64_cache': {},
101
+ 'transcript_history': [], 'last_transcript': "", 'image_hashes': set(),
102
+ 'tts_voice': "en-US-AriaNeural", 'chat_history': [], 'marquee_settings': {
103
+ "background": "#1E1E1E", "color": "#FFFFFF", "font-size": "14px",
104
+ "animationDuration": "20s", "width": "100%", "lineHeight": "35px"
105
+ }, 'operation_timings': {}, 'performance_metrics': defaultdict(list),
106
+ 'enable_audio': True, 'download_link_cache': {}, 'username': None
107
+ }
108
+ for k, v in defaults.items():
109
+ if k not in st.session_state: st.session_state[k] = v
110
+
111
+ # 🖌️ Marquee Helpers
112
+ def update_marquee_settings_ui():
113
+ # 🎨 Sidebar marquee controls
114
+ st.sidebar.markdown("### 🎯 Marquee Settings")
115
+ cols = st.sidebar.columns(2)
116
+ with cols[0]:
117
+ st.session_state['marquee_settings']['background'] = st.color_picker("🎨 Background", "#1E1E1E")
118
+ st.session_state['marquee_settings']['color'] = st.color_picker("✍️ Text", "#FFFFFF")
119
+ with cols[1]:
120
+ st.session_state['marquee_settings']['font-size'] = f"{st.slider('📏 Size', 10, 24, 14)}px"
121
+ st.session_state['marquee_settings']['animationDuration'] = f"{st.slider('⏱️ Speed', 1, 20, 20)}s"
122
+
123
+ def display_marquee(text, settings, key_suffix=""):
124
+ # 🌈 Show marquee with truncation
125
+ truncated = text[:280] + "..." if len(text) > 280 else text
126
+ streamlit_marquee(content=truncated, **settings, key=f"marquee_{key_suffix}")
127
+ st.write("")
128
+
129
+ # 📝 Text & File Helpers
130
+ def clean_text_for_tts(text): return re.sub(r'[#*!\[\]]+', '', ' '.join(text.split()))[:200] or "No text"
131
+ def clean_text_for_filename(text): return '_'.join(re.sub(r'[^\w\s-]', '', text.lower()).split())[:200]
132
+ def get_high_info_terms(text, top_n=10):
133
+ stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with'}
134
+ words = re.findall(r'\b\w+(?:-\w+)*\b', text.lower())
135
+ bi_grams = [' '.join(pair) for pair in zip(words, words[1:])]
136
+ filtered = [t for t in words + bi_grams if t not in stop_words and len(t.split()) <= 2]
137
+ return [t for t, _ in Counter(filtered).most_common(top_n)]
138
+
139
+ def generate_filename(prompt, response, file_type="md"):
140
+ # 📁 Smart filename with info terms
141
+ prefix = format_timestamp_prefix()
142
+ terms = get_high_info_terms(prompt + " " + response, 5)
143
+ snippet = clean_text_for_filename(prompt[:40] + " " + response[:40])
144
+ wct, sw = len(prompt.split()), len(response.split())
145
+ dur = round((wct + sw) / 2.5)
146
+ base = '_'.join(list(dict.fromkeys(terms + [snippet])))[:200 - len(prefix) - len(f"_wct{wct}_sw{sw}_dur{dur}.{file_type}")]
147
+ return f"{prefix}{base}_wct{wct}_sw{sw}_dur{dur}.{file_type}"
148
+
149
+ def create_file(prompt, response, file_type="md"):
150
+ # 📝 Save file with Q&A
151
+ filename = generate_filename(prompt, response, file_type)
152
+ with open(filename, 'w', encoding='utf-8') as f: f.write(prompt + "\n\n" + response)
153
+ return filename
154
+
155
+ def get_download_link(file, file_type="mp3"):
156
+ # ⬇️ Cached download link
157
+ cache_key = f"dl_{file}"
158
+ if cache_key not in st.session_state['download_link_cache']:
159
+ with open(file, "rb") as f:
160
+ b64 = base64.b64encode(f.read()).decode()
161
+ st.session_state['download_link_cache'][cache_key] = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file)}">{FILE_EMOJIS.get(file_type, '⬇️')} Download {os.path.basename(file)}</a>'
162
+ return st.session_state['download_link_cache'][cache_key]
163
+
164
+ # 🎶 Audio Processing
165
+ async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
166
+ # 🎵 Async TTS with caching
167
+ cache_key = f"{text[:100]}_{voice}_{rate}_{pitch}_{file_format}"
168
+ if cache_key in st.session_state['audio_cache']: return st.session_state['audio_cache'][cache_key], 0
169
+ with PerformanceTimer("tts_generation"):
170
+ text = clean_text_for_tts(text)
171
+ if not text: return None, 0
172
+ filename = f"audio_{format_timestamp_prefix()}_{random.randint(1000, 9999)}.{file_format}"
173
+ communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
174
+ await communicate.save(filename)
175
+ st.session_state['audio_cache'][cache_key] = filename
176
+ return filename, time.time() - st.session_state['operation_timings']['tts_generation']
177
+
178
+ def play_and_download_audio(file_path):
179
+ # 🔊 Play + download
180
+ if file_path and os.path.exists(file_path):
181
+ st.audio(file_path)
182
+ st.markdown(get_download_link(file_path), unsafe_allow_html=True)
183
+
184
  async def save_chat_entry(username, message, is_markdown=False):
185
+ # 💬 Save chat with audio
186
  central = pytz.timezone('US/Central')
187
  timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
188
+ entry = f"[{timestamp}] {username}: {message}" if not is_markdown else f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
189
+ with open(CHAT_FILE, 'a') as f: f.write(f"{entry}\n")
 
 
 
190
  voice = FUN_USERNAMES.get(username, "en-US-AriaNeural")
191
+ audio_file, _ = await async_edge_tts_generate(clean_text_for_tts(message), voice)
 
192
  if audio_file:
193
+ with open(HISTORY_FILE, 'a') as f: f.write(f"[{timestamp}] {username}: Audio - {audio_file}\n")
 
194
  await broadcast_message(f"{username}|{message}", "chat")
195
  st.session_state.last_chat_update = time.time()
196
  return audio_file
197
 
 
198
  async def load_chat():
199
+ # 📜 Load chat history
 
200
  if not os.path.exists(CHAT_FILE):
201
+ with open(CHAT_FILE, 'a') as f: f.write(f"# {START_ROOM} Chat\n\nWelcome to the cosmic hub! 🎤\n")
202
+ with open(CHAT_FILE, 'r') as f: return f.read()
 
 
203
 
204
+ # 🌐 WebSocket Handling
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  async def websocket_handler(websocket, path):
206
+ # 🤝 Handle WebSocket clients
207
+ client_id = str(uuid.uuid4())
208
+ room_id = "chat"
209
+ st.session_state.active_connections.setdefault(room_id, {})[client_id] = websocket
210
+ chat_content = await load_chat()
211
+ username = st.session_state.get('username', random.choice(list(FUN_USERNAMES.keys())))
212
+ if not any(f"Client-{client_id}" in line for line in chat_content.split('\n')):
213
+ await save_chat_entry(f"Client-{client_id}", f"{username} has joined {START_ROOM}!")
214
  try:
 
 
 
 
 
 
 
215
  async for message in websocket:
216
+ username, content = message.split('|', 1)
217
+ await save_chat_entry(username, content)
 
 
 
 
218
  finally:
219
  if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
220
  del st.session_state.active_connections[room_id][client_id]
221
 
 
222
  async def broadcast_message(message, room_id):
223
+ # 📢 Broadcast to all clients
224
  if room_id in st.session_state.active_connections:
225
  disconnected = []
226
  for client_id, ws in st.session_state.active_connections[room_id].items():
227
+ try: await ws.send(message)
228
+ except websockets.ConnectionClosed: disconnected.append(client_id)
229
+ for client_id in disconnected: del st.session_state.active_connections[room_id][client_id]
 
 
 
230
 
 
231
  async def run_websocket_server():
232
+ # 🖥️ Start WebSocket server
233
  if not st.session_state.server_running:
234
  server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
235
  st.session_state.server_running = True
236
  await server.wait_closed()
237
 
238
+ # 📚 PDF to Audio
239
  class AudioProcessor:
240
  def __init__(self):
241
+ self.cache_dir = AUDIO_CACHE_DIR
242
  os.makedirs(self.cache_dir, exist_ok=True)
243
+ self.metadata = json.load(open(f"{self.cache_dir}/metadata.json")) if os.path.exists(f"{self.cache_dir}/metadata.json") else {}
 
 
 
 
244
 
245
  def _save_metadata(self):
246
+ with open(f"{self.cache_dir}/metadata.json", 'w') as f: json.dump(self.metadata, f)
 
 
247
 
248
  async def create_audio(self, text, voice='en-US-AriaNeural'):
249
+ # 🎶 Generate cached audio
250
  cache_key = hashlib.md5(f"{text}:{voice}".encode()).hexdigest()
251
+ cache_path = f"{self.cache_dir}/{cache_key}.mp3"
252
  if cache_key in self.metadata and os.path.exists(cache_path):
253
  return open(cache_path, 'rb').read()
254
+ text = clean_text_for_tts(text)
255
+ if not text: return None
 
256
  communicate = edge_tts.Communicate(text, voice)
257
  await communicate.save(cache_path)
258
+ self.metadata[cache_key] = {'timestamp': datetime.now().isoformat(), 'text_length': len(text), 'voice': voice}
 
 
 
 
259
  self._save_metadata()
260
  return open(cache_path, 'rb').read()
261
 
 
 
 
 
 
262
  def process_pdf(pdf_file, max_pages, voice, audio_processor):
263
+ # 📄 Convert PDF to audio
264
  reader = PdfReader(pdf_file)
265
  total_pages = min(len(reader.pages), max_pages)
266
  texts, audios = [], {}
267
+ async def process_page(i, text): audios[i] = await audio_processor.create_audio(text, voice)
 
 
268
  for i in range(total_pages):
269
  text = reader.pages[i].extract_text()
270
  texts.append(text)
271
  threading.Thread(target=lambda: asyncio.run(process_page(i, text))).start()
272
  return texts, audios, total_pages
273
 
274
+ # 🔍 ArXiv & AI Lookup
275
+ def parse_arxiv_refs(ref_text):
276
+ # 📜 Parse ArXiv refs into dicts
277
+ if not ref_text: return []
278
+ papers = []
279
+ current = {}
280
+ for line in ref_text.split('\n'):
281
+ if line.count('|') == 2:
282
+ if current: papers.append(current)
283
+ date, title, *_ = line.strip('* ').split('|')
284
+ url = re.search(r'(https://arxiv.org/\S+)', line).group(1) if re.search(r'(https://arxiv.org/\S+)', line) else f"paper_{len(papers)}"
285
+ current = {'date': date, 'title': title, 'url': url, 'authors': '', 'summary': '', 'full_audio': None, 'download_base64': ''}
286
+ elif current:
287
+ if not current['authors']: current['authors'] = line.strip('* ')
288
+ else: current['summary'] += ' ' + line.strip() if current['summary'] else line.strip()
289
+ if current: papers.append(current)
290
+ return papers[:20]
291
+
292
+ def generate_5min_feature_markdown(paper):
293
+ # ✨ 5-min research paper feature
294
+ title, summary, authors, date, url = paper['title'], paper['summary'], paper['authors'], paper['date'], paper['url']
295
+ pdf_url = url.replace("abs", "pdf") + (".pdf" if not url.endswith(".pdf") else "")
296
+ wct, sw = len(title.split()), len(summary.split())
297
+ terms = get_high_info_terms(summary, 15)
298
+ rouge = round((len(terms) / max(sw, 1)) * 100, 2)
299
+ mermaid = "```mermaid\nflowchart TD\n" + "\n".join(f' T{i+1}["{t}"] --> T{i+2}["{terms[i+1]}"]' for i in range(len(terms)-1)) + "\n```"
300
+ return f"""
301
+ ## 📄 {title}
302
+ **Authors:** {authors} | **Date:** {date} | **Words:** Title: {wct}, Summary: {sw}
303
+ **Links:** [Abstract]({url}) | [PDF]({pdf_url})
304
+ **Terms:** {', '.join(terms)} | **ROUGE:** {rouge}%
305
+ ### 🎤 TTF Read Aloud
306
+ - **Title:** {title} | **Terms:** {', '.join(terms)} | **ROUGE:** {rouge}%
307
+ #### Concepts Graph
308
+ {mermaid}
309
+ ---
310
+ """
311
+
312
+ def create_detailed_paper_md(papers): return "# Detailed Summary\n" + "\n".join(generate_5min_feature_markdown(p) for p in papers)
313
+
314
+ async def create_paper_audio_files(papers, query):
315
+ # 🎧 Generate paper audio
316
+ for p in papers:
317
+ audio_text = clean_text_for_tts(f"{p['title']} by {p['authors']}. {p['summary']}")
318
+ p['full_audio'], _ = await async_edge_tts_generate(audio_text, st.session_state['tts_voice'])
319
+ if p['full_audio']: p['download_base64'] = get_download_link(p['full_audio'])
320
+
321
+ def perform_ai_lookup(q, useArxiv=True, useArxivAudio=False):
322
+ # 🔮 AI-powered research
323
  client = anthropic.Anthropic(api_key=anthropic_key)
324
+ response = client.messages.create(model="claude-3-sonnet-20240229", max_tokens=1000, messages=[{"role": "user", "content": q}])
 
 
 
 
325
  result = response.content[0].text
326
+ st.markdown("### Claude's Reply 🧠\n" + result)
 
327
  md_file = create_file(q, result)
328
+ audio_file, _ = await async_edge_tts_generate(result, st.session_state['tts_voice'])
329
  play_and_download_audio(audio_file)
330
+
331
  if useArxiv:
332
  q += result
333
  gradio_client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
334
  refs = gradio_client.predict(q, 10, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md")[0]
335
  result = f"🔎 {q}\n\n{refs}"
336
+ md_file, audio_file = create_file(q, result), (await async_edge_tts_generate(result, st.session_state['tts_voice']))[0]
337
  play_and_download_audio(audio_file)
338
  papers = parse_arxiv_refs(refs)
339
+ if papers and useArxivAudio: await create_paper_audio_files(papers, q)
 
340
  return result, papers
341
  return result, []
342
 
343
+ # 📦 Zip Files
344
+ def create_zip_of_files(md_files, mp3_files, query):
345
+ # 📦 Zip it up
346
+ all_files = md_files + mp3_files
347
+ if not all_files: return None
348
+ terms = get_high_info_terms(" ".join([open(f, 'r', encoding='utf-8').read() if f.endswith('.md') else os.path.splitext(os.path.basename(f))[0].replace('_', ' ') for f in all_files] + [query]), 5)
349
+ zip_name = f"{format_timestamp_prefix()}_{'-'.join(terms)[:20]}.zip"
350
+ with zipfile.ZipFile(zip_name, 'w') as z: [z.write(f) for f in all_files]
351
+ return zip_name
352
+
353
+ # 🎮 Main Interface
354
+ async def async_interface():
355
+ init_session_state()
356
+ if not st.session_state.username:
357
+ available = [n for n in FUN_USERNAMES if not any(f"{n} has joined" in l for l in (await load_chat()).split('\n'))]
358
+ st.session_state.username = random.choice(available or list(FUN_USERNAMES.keys()))
359
+ st.session_state.tts_voice = FUN_USERNAMES[st.session_state.username]
360
+
361
+ st.title(f"🤖🧠MMO Chat & Research for {st.session_state.username}📝🔬")
362
+ update_marquee_settings_ui()
363
+ display_marquee(f"🚀 Welcome to {START_ROOM} | 🤖 {st.session_state.username}", st.session_state['marquee_settings'], "welcome")
364
+
365
+ if not st.session_state.server_task:
366
+ st.session_state.server_task = asyncio.create_task(run_websocket_server())
367
+
368
+ tab_main = st.radio("Action:", ["🎤 Chat & Voice", "📸 Media", "🔍 ArXiv", "📚 PDF to Audio"], horizontal=True)
369
+ useArxiv, useArxivAudio = st.checkbox("Search ArXiv", True), st.checkbox("ArXiv Audio", False)
370
+
371
+ # 🎤 Chat & Voice
372
+ if tab_main == "🎤 Chat & Voice":
373
+ st.subheader(f"{START_ROOM} Chat 💬")
374
+ chat_content = await load_chat()
375
+ for i, line in enumerate(chat_content.split('\n')):
376
+ if line.strip() and ': ' in line:
377
+ st.markdown(line)
378
+ if st.button("📢 Speak", key=f"speak_{i}"):
379
+ audio_file, _ = await async_edge_tts_generate(line.split(': ', 1)[1], st.session_state['tts_voice'])
380
+ play_and_download_audio(audio_file)
381
+
382
+ message = st.text_input(f"Message as {st.session_state.username}", key="message_input")
383
+ if st.button("Send 🚀") and message.strip():
384
+ await save_chat_entry(st.session_state.username, message, True)
385
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
+ st.subheader("🎤 Speech-to-Chat")
388
+ speech_component = components.declare_component("speech_component", path="mycomponent")
389
+ transcript_data = speech_component(default_value=st.session_state.get('last_transcript', ''))
390
+ if transcript_data and 'value' in transcript_data:
391
+ transcript = transcript_data['value'].strip()
392
+ st.write(f"🎙️ You said: {transcript}")
393
+ if st.button("Send to Chat"):
394
+ await save_chat_entry(st.session_state.username, transcript, True)
395
+ st.session_state.last_transcript = transcript
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  st.rerun()
397
 
398
+ # 📸 Media
399
+ elif tab_main == "📸 Media":
400
+ st.header("📸 Media Gallery")
401
+ tabs = st.tabs(["🎵 Audio", "🖼 Images", "🎥 Video"])
402
+ with tabs[0]:
403
+ for a in glob.glob(f"{MEDIA_DIR}/*.mp3"):
404
+ with st.expander(os.path.basename(a)): play_and_download_audio(a)
405
+ with tabs[1]:
406
+ imgs = glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg")
407
+ if imgs:
408
+ cols = st.columns(3)
409
+ for i, f in enumerate(imgs): cols[i % 3].image(f, use_container_width=True)
410
+ with tabs[2]:
411
+ for v in glob.glob(f"{MEDIA_DIR}/*.mp4"):
412
+ with st.expander(os.path.basename(v)): st.video(v)
413
+
414
+ uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3'])
415
+ if uploaded_file:
416
+ filename = f"{format_timestamp_prefix(st.session_state.username)}-{hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]}.{uploaded_file.name.split('.')[-1]}"
417
+ with open(f"{MEDIA_DIR}/{filename}", 'wb') as f: f.write(uploaded_file.getbuffer())
418
+ await save_chat_entry(st.session_state.username, f"Uploaded: {filename}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  st.rerun()
420
 
421
+ # 🔍 ArXiv
422
+ elif tab_main == "🔍 ArXiv":
423
+ q = st.text_input("🔍 Query:")
424
+ if q and st.button("🔍 Run"):
425
+ result, papers = perform_ai_lookup(q, useArxiv, useArxivAudio)
426
+ for i, p in enumerate(papers, 1):
427
+ with st.expander(f"{i}. 📄 {p['title']}"):
428
+ st.markdown(f"**{p['date']} | {p['title']}** — [Link]({p['url']})")
429
+ st.markdown(generate_5min_feature_markdown(p))
430
+ if p.get('full_audio'): play_and_download_audio(p['full_audio'])
431
+
432
+ # 📚 PDF to Audio
433
+ elif tab_main == "📚 PDF to Audio":
434
+ audio_processor = AudioProcessor()
435
+ pdf_file = st.file_uploader("Choose PDF", "pdf")
436
+ max_pages = st.slider('Pages', 1, 100, 10)
437
+ if pdf_file:
438
+ with st.spinner('Processing...'):
439
+ texts, audios, total = process_pdf(pdf_file, max_pages, st.session_state['tts_voice'], audio_processor)
440
+ for i, text in enumerate(texts):
441
+ with st.expander(f"Page {i+1}"):
442
+ st.markdown(text)
443
+ while i not in audios: time.sleep(0.1)
444
+ if audios[i]:
445
+ st.audio(audios[i], format='audio/mp3')
446
+ st.markdown(get_download_link(io.BytesIO(audios[i]), "mp3"), unsafe_allow_html=True)
447
+
448
+ # 🗂️ Sidebar
449
+ st.sidebar.subheader("Voice Settings")
450
+ new_username = st.sidebar.selectbox("Change Name/Voice", list(FUN_USERNAMES.keys()), index=list(FUN_USERNAMES.keys()).index(st.session_state.username))
451
+ if new_username != st.session_state.username:
452
+ await save_chat_entry("System 🌟", f"{st.session_state.username} changed to {new_username}")
453
+ st.session_state.username, st.session_state.tts_voice = new_username, FUN_USERNAMES[new_username]
454
+ st.rerun()
455
+
456
+ md_files, mp3_files = glob.glob("*.md"), glob.glob("*.mp3")
457
+ st.sidebar.markdown("### 📂 File History")
458
+ for f in sorted(md_files + mp3_files, key=os.path.getmtime, reverse=True)[:10]:
459
+ st.sidebar.write(f"{FILE_EMOJIS.get(f.split('.')[-1], '📄')} {os.path.basename(f)}")
460
+ if st.sidebar.button("⬇️ Zip All"):
461
+ zip_name = create_zip_of_files(md_files, mp3_files, "latest_query")
462
+ if zip_name: st.sidebar.markdown(get_download_link(zip_name, "zip"), unsafe_allow_html=True)
463
+
464
+ def main():
465
+ # 🎉 Kick it off
466
+ asyncio.run(async_interface())
467
 
468
  if __name__ == "__main__":
469
  main()