awacke1 commited on
Commit
13ac473
·
verified ·
1 Parent(s): 87fbb6b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +545 -408
app.py CHANGED
@@ -2,7 +2,6 @@ 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
@@ -10,155 +9,387 @@ import time
10
  import hashlib
11
  from PIL import Image
12
  import glob
 
 
 
 
 
 
13
  import re
14
- from urllib.parse import quote
15
-
16
- # App Configuration
17
- Site_Name = '🤖🧠Chat & Quote Node📝🔬'
18
- title = "🤖🧠Chat & Quote Node📝🔬"
19
- icons = '🤖🧠🔬📝'
20
- START_ROOM = "Sector 🌌"
21
-
 
 
 
 
 
 
 
 
 
 
 
22
  st.set_page_config(
23
- page_title=title,
24
- page_icon=icons,
25
  layout="wide",
26
  initial_sidebar_state="auto"
27
  )
28
 
29
- # Fun usernames with emojis - the VIP list of quirky characters! 🎉😜
30
- FUN_USERNAMES = [
31
- "CosmicJester 🌌", "PixelPanda 🐼", "QuantumQuack 🦆", "StellarSquirrel 🐿️",
32
- "GizmoGuru ⚙️", "NebulaNinja 🌠", "ByteBuster 💾", "GalacticGopher 🌍",
33
- "RocketRaccoon 🚀", "EchoElf 🧝", "PhantomFox 🦊", "WittyWizard 🧙",
34
- "LunarLlama 🌙", "SolarSloth ☀️", "AstroAlpaca 🦙", "CyberCoyote 🐺",
35
- "MysticMoose 🦌", "GlitchGnome 🧚", "VortexViper 🐍", "ChronoChimp 🐒"
36
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- # Directories for chat, votes, and media - the secret vaults where treasures are stashed! 🗄️🔒
39
  CHAT_DIR = "chat_logs"
40
  VOTE_DIR = "vote_logs"
41
- MEDIA_DIR = "media"
 
 
42
  STATE_FILE = "user_state.txt"
43
- os.makedirs(CHAT_DIR, exist_ok=True)
44
- os.makedirs(VOTE_DIR, exist_ok=True)
45
- os.makedirs(MEDIA_DIR, exist_ok=True)
46
 
47
- # Persistent files - the grand tomes of chatter and votes! 📖✨
48
  CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
49
  QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
50
  IMAGE_VOTES_FILE = os.path.join(VOTE_DIR, "image_votes.md")
51
  HISTORY_FILE = os.path.join(VOTE_DIR, "vote_history.md")
52
 
53
- # Unicode digit conversion table - the magic map for emoji numbers! 🔢✨
54
- UNICODE_DIGITS = {
55
- 0: "0️⃣", 1: "1️⃣", 2: "2️⃣", 3: "3️⃣", 4: "4️⃣",
56
- 5: "5️⃣", 6: "6️⃣", 7: "7️⃣", 8: "8️⃣", 9: "9️⃣"
57
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- # Unicode font examples - the stylish wardrobe of text flair! 👗📝
60
- UNICODE_FONTS = [
61
- ("Normal", lambda x: x),
62
- ("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)),
63
- ("Italic", lambda x: "".join(chr(ord(c) + 0x1D434 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D44E - 0x61) if 'a' <= c <= 'z' else c for c in x)),
64
- ("Script", lambda x: "".join(chr(ord(c) + 0x1D49C - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D4B6 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
65
- ("Fraktur", lambda x: "".join(chr(ord(c) + 0x1D504 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D51E - 0x61) if 'a' <= c <= 'z' else c for c in x)),
66
- ("Double Struck", lambda x: "".join(chr(ord(c) + 0x1D538 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D552 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
67
- ("Sans Serif", lambda x: "".join(chr(ord(c) + 0x1D5A0 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D5BA - 0x61) if 'a' <= c <= 'z' else c for c in x)),
68
- ("Sans Bold", lambda x: "".join(chr(ord(c) + 0x1D5D4 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D5EE - 0x61) if 'a' <= c <= 'z' else c for c in x)),
69
- ("Mono", lambda x: "".join(chr(ord(c) + 0x1D670 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D68A - 0x61) if 'a' <= c <= 'z' else c for c in x)),
70
- ("Circle", lambda x: "".join(chr(ord(c) + 0x24B6 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x24D0 - 0x61) if 'a' <= c <= 'z' else c for c in x))
71
- ]
72
-
73
- # Global WebSocket server flag
74
- server_running = False
75
- server_task = None
76
-
77
- # Node name - the app’s codename generator, sneaky and slick! 🕵️‍♂️💾
78
- def get_node_name():
79
- """🎲 Spins the wheel of fate to name our node - a random alias or user pick! 🏷️"""
80
- parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
81
- parser.add_argument('--node-name', type=str, default=None, help='Name for this chat node')
82
- parser.add_argument('--port', type=int, default=8501, help='Port to run the Streamlit interface on')
83
- args = parser.parse_args()
84
- return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
85
-
86
- # Chat saver - the scribe etching epic messages into the eternal scroll! 🖋️📜
87
- def save_chat_entry(username, message):
88
- """🖌️ Carves a chat line into the grand Markdown tome - history in the making! 🏛️"""
89
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
90
- entry = f"[{timestamp}] {username}: {message}"
91
  try:
92
- with open(CHAT_FILE, 'a') as f:
93
- f.write(f"{entry}\n")
94
- return True
95
  except Exception as e:
96
- print(f"Oops! Failed to save chat: {e}")
97
- return False
98
 
99
- # Chat loader - the archaeologist unearthing the chat saga! ⛏️📚
100
- def load_chat():
101
- """🔍 Digs up the chat treasure from the filesystem - tales of old and new! 💰"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  if not os.path.exists(CHAT_FILE):
103
- with open(CHAT_FILE, 'w') as f:
104
- f.write(f"# {START_ROOM} Chat\n\nWelcome to the cosmic hub - start chatting! 🎤\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  try:
106
- with open(CHAT_FILE, 'r') as f:
107
- content = f.read()
108
- lines = content.strip().split('\n')
109
- numbered_content = "\n".join(f"{i+1}. {line}" for i, line in enumerate(lines) if line.strip())
110
- return numbered_content
111
- except Exception as e:
112
- print(f"Chat load hiccup: {e}")
113
- return f"# Error loading {START_ROOM} chat\nSomething went wonky! 😵"
114
-
115
- # User list grabber - the social butterfly spotting all the chatters! 🦋👥
116
- def get_user_list(chat_content):
117
- """👀 Peeks at the chat to spot all the cool cats talking - who’s in the club? 🎸"""
118
- users = set()
119
- for line in chat_content.split('\n'):
120
- if line.strip() and ': ' in line:
121
- user = line.split(': ')[1].split(' ')[0]
122
- users.add(user)
123
- return sorted(list(users))
124
-
125
- # Quote loader - the sage pulling wisdom from the ages! 📜🧙
126
- def load_quotes(source="famous"):
127
- """📚 Grabs a stack of wise words from famous folks or custom quips! 🗣️"""
128
- famous_quotes = [
129
- "The true sign of intelligence is not knowledge but imagination. – Albert Einstein",
130
- "I have not failed. I've just found 10,000 ways that won't work. – Thomas Edison",
131
- "Innovation distinguishes between a leader and a follower. – Steve Jobs",
132
- "Research is what I'm doing when I don't know what I'm doing. – Wernher von Braun",
133
- "The only way to discover the limits of the possible is to go beyond them into the impossible. Arthur C. Clarke",
134
- "Success is a science; if you have the conditions, you get the result. – Oscar Wilde",
135
- "An expert is a person who has made all the mistakes that can be made in a very narrow field. – Niels Bohr",
136
- "The important thing is to not stop questioning. Curiosity has its own reason for existing. – Albert Einstein",
137
- "The best way to predict the future is to invent it. – Alan Kay",
138
- "If I have seen further it is by standing on the shoulders of Giants. – Isaac Newton",
139
- "Logic will get you from A to B. Imagination will take you everywhere. – Albert Einstein",
140
- "Imagination is more important than knowledge. Knowledge is limited. Imagination encircles the world. – Albert Einstein",
141
- "Science is a way of thinking much more than it is a body of knowledge. Carl Sagan",
142
- "We cannot solve our problems with the same thinking we used when we created them. – Albert Einstein",
143
- "The true method of knowledge is experiment. – William Blake",
144
- "The scientist is not a person who gives the right answers, he's one who asks the right questions. – Claude Levi-Strauss",
145
- "It's kind of fun to do the impossible. – Walt Disney",
146
- "Any sufficiently advanced technology is indistinguishable from magic. – Arthur C. Clarke",
147
- "Creativity is intelligence having fun. – Albert Einstein",
148
- "To invent, you need a good imagination and a pile of junk. – Thomas Edison"
149
- ]
150
- custom_quotes = [
151
- "Every age unfolds a new lesson. Life's chapters evolve, each teaching us anew.",
152
- "From infancy to twilight, our journey is painted in growth. Every stage shines with its own wisdom.",
153
- "Love is the universal language, transcending boundaries and touching souls.",
154
- "Through love, we find connection, unity, and the essence of existence."
155
- ]
156
- quotes = famous_quotes if source == "famous" else custom_quotes
157
- return quotes if quotes else ["No quotes available - check back later! 📭"]
158
-
159
- # Vote saver - the tally keeper counting thumbs up! 👍📊
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  def save_vote(file, item, user_hash):
161
- """✍️ Tallies a vote in the grand ledger - your opinion matters! 🗳️"""
162
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
163
  entry = f"[{timestamp}] {user_hash} voted for {item}"
164
  try:
@@ -170,10 +401,8 @@ def save_vote(file, item, user_hash):
170
  except Exception as e:
171
  print(f"Vote save flop: {e}")
172
  return False
173
-
174
- # Vote loader - the scorekeeper tallying the crowd’s cheers! 🎉🏅
175
  def load_votes(file):
176
- """📈 Counts the votes from the ledger - who’s winning the popularity contest? 🏆"""
177
  if not os.path.exists(file):
178
  with open(file, 'w') as f:
179
  f.write("# Vote Tally\n\nNo votes yet - get clicking! 🖱️\n")
@@ -190,290 +419,203 @@ def load_votes(file):
190
  print(f"Vote load oopsie: {e}")
191
  return {}
192
 
193
- # User hash generator - the secret agent giving you a cool code! 🕵️‍♂️🔑
194
  def generate_user_hash():
195
- """🕵️ Crafts a snazzy 8-digit ID badge - you’re in the club now! 🎟️"""
196
  if 'user_hash' not in st.session_state:
197
  session_id = str(random.getrandbits(128))
198
  hash_object = hashlib.md5(session_id.encode())
199
  st.session_state['user_hash'] = hash_object.hexdigest()[:8]
200
  return st.session_state['user_hash']
201
 
202
- # Username persistence - save and load across refreshes! 💾🔄
203
- def save_username(username):
204
- """💾 Stashes your cosmic alias before the refresh void claims it! 🌌"""
205
- try:
206
- with open(STATE_FILE, 'w') as f:
207
- f.write(username)
208
- except Exception as e:
209
- print(f"Failed to save username: {e}")
210
-
211
- def load_username():
212
- """🔄 Resurrects your cosmic alias from the refresh abyss! 🌠"""
213
- if os.path.exists(STATE_FILE):
214
- try:
215
- with open(STATE_FILE, 'r') as f:
216
- return f.read().strip()
217
- except Exception as e:
218
- print(f"Failed to load username: {e}")
219
- return None
220
-
221
- # Media HTML generators - IVA’s multimedia magic! 🎥🎶🖼️
222
- def get_video_html(video_path, width="100%"):
223
- """🎬 Rolls out the red carpet for videos - autoplay and loop like a star! 🌟"""
224
- video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
225
- return f'''
226
- <video width="{width}" controls autoplay muted loop>
227
- <source src="{video_url}" type="video/mp4">
228
- Your browser does not support the video tag.
229
- </video>
230
- '''
231
-
232
- def get_audio_html(audio_path, width="100%"):
233
- """🎶 Drops a beat with audio - your personal DJ in action! 🎧"""
234
- audio_url = f"data:audio/mpeg;base64,{base64.b64encode(open(audio_path, 'rb').read()).decode()}"
235
- return f'''
236
- <audio controls style="width: {width};">
237
- <source src="{audio_url}" type="audio/mpeg">
238
- Your browser does not support the audio element.
239
- </audio>
240
- '''
241
-
242
- active_connections = {}
243
-
244
- # WebSocket handler - the bouncer at the Sector rave, keeping it hopping! 🎉🚪
245
- async def websocket_handler(websocket, path):
246
- """🎧 Guards the cosmic gate, welcoming all to Sector and booting crashers! 🚨"""
247
- try:
248
- client_id = str(uuid.uuid4())
249
- room_id = "chat" # Single room "Sector 🌌"
250
- active_connections.setdefault(room_id, {})[client_id] = websocket
251
- print(f"Client {client_id} joined the Sector party!")
252
- # Auto-join message for every connection
253
- username = st.session_state.get('username', random.choice(FUN_USERNAMES))
254
- save_chat_entry("System 🌟", f"{username} has joined {START_ROOM}!")
255
-
256
- async for message in websocket:
257
- try:
258
- parts = message.split('|', 1)
259
- if len(parts) == 2:
260
- username, content = parts
261
- save_chat_entry(username, content)
262
- await broadcast_message(f"{username}|{content}", room_id)
263
- except Exception as e:
264
- print(f"Message mishap: {e}")
265
- await websocket.send(f"ERROR|Oops, bad message format! 😬")
266
- except websockets.ConnectionClosed:
267
- print(f"Client {client_id} bailed from Sector!")
268
- save_chat_entry("System 🌟", f"{username} has left {START_ROOM}!")
269
- finally:
270
- if room_id in active_connections and client_id in active_connections[room_id]:
271
- del active_connections[room_id][client_id]
272
- if not active_connections[room_id]:
273
- del active_connections[room_id]
274
-
275
- # Broadcaster - the megaphone blasting Sector vibes to all! 📣🎶
276
- async def broadcast_message(message, room_id):
277
- """📢 Shouts the latest Sector beat to every cosmic dancer - hear it loud! 🎵"""
278
- if room_id in active_connections:
279
- disconnected = []
280
- for client_id, ws in active_connections[room_id].items():
281
- try:
282
- await ws.send(message)
283
- except websockets.ConnectionClosed:
284
- disconnected.append(client_id)
285
- for client_id in disconnected:
286
- del active_connections[room_id][client_id]
287
-
288
- # WebSocket server runner - the DJ spinning up the Sector tunes once! 🎧🔥
289
- async def run_websocket_server():
290
- """🌐 Cranks up the WebSocket jukebox once, keeping Sector rocking! 🎸"""
291
- global server_running, server_task
292
- if not server_running:
293
- server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
294
- print(f"WebSocket server jamming on ws://0.0.0.0:8765")
295
- server_running = True
296
- await server.wait_closed()
297
-
298
- # Chat interface maker - the stage builder for our Sector extravaganza! 🎭🏟️
299
- def create_streamlit_interface(initial_username):
300
- """🖌️ Sets up the Sector stage with pulsing timers, voting, and IVA media flair! 🌟"""
301
- # Custom CSS for a sleek chat box and timer
302
- st.markdown("""
303
- <style>
304
- .chat-box {
305
- font-family: monospace;
306
- background: #1e1e1e;
307
- color: #d4d4d4;
308
- padding: 10px;
309
- border-radius: 5px;
310
- height: 300px;
311
- overflow-y: auto;
312
- }
313
- .timer {
314
- font-size: 24px;
315
- color: #ffcc00;
316
- text-align: center;
317
- animation: pulse 1s infinite;
318
- }
319
- @keyframes pulse {
320
- 0% { transform: scale(1); }
321
- 50% { transform: scale(1.1); }
322
- 100% { transform: scale(1); }
323
- }
324
- </style>
325
- """, unsafe_allow_html=True)
326
-
327
- # Title and intro
328
- st.title(f"{Site_Name}")
329
- st.markdown(f"Welcome to {START_ROOM} - chat, vote, and enjoy IVA’s media magic! 🎉")
330
-
331
- # Load or set username
332
  saved_username = load_username()
333
  if saved_username and saved_username in FUN_USERNAMES:
334
- initial_username = saved_username
335
- if 'username' not in st.session_state:
336
- st.session_state.username = initial_username
337
- save_chat_entry("System 🌟", f"{initial_username} has joined {START_ROOM}!")
338
- save_username(st.session_state.username) # Persist before any refresh
339
-
340
- # Session state for refresh rate and quote index
341
- if 'refresh_rate' not in st.session_state:
342
- st.session_state.refresh_rate = 5
343
- if 'timer_start' not in st.session_state:
344
- st.session_state.timer_start = time.time() # Autostart timer
345
- if 'quote_index' not in st.session_state:
346
- quotes = load_quotes("famous")
347
- st.session_state.quote_index = random.randint(0, max(0, len(quotes) - 1)) if quotes else 0
348
- if 'quote_source' not in st.session_state:
349
- st.session_state.quote_source = "famous"
350
-
351
- # Chat section
352
- st.subheader(f"{START_ROOM} Chat 💬")
353
- chat_content = load_chat()
354
- chat_lines = chat_content.split('\n')
355
- for i, line in enumerate(chat_lines):
356
- if line.strip() and ': ' in line:
357
- col1, col2 = st.columns([5, 1])
358
- with col1:
359
- st.markdown(line)
360
- with col2:
361
- if st.button(f"👍", key=f"chat_vote_{i}"):
362
- user_hash = generate_user_hash()
363
- save_vote(QUOTE_VOTES_FILE, line, user_hash)
364
- st.session_state.timer_start = time.time()
365
- save_username(st.session_state.username)
366
- st.rerun()
367
-
368
- user_list = get_user_list(chat_content)
369
- new_username = st.selectbox("Switch User", user_list + [st.session_state.username], index=len(user_list))
370
- if new_username != st.session_state.username:
371
- save_chat_entry("System 🌟", f"{st.session_state.username} switched to {new_username} in {START_ROOM}!")
372
- st.session_state.username = new_username
373
- st.session_state.timer_start = time.time()
374
- save_username(st.session_state.username)
375
-
376
- message = st.text_input("Message", placeholder="Type your epic line here! ✍️")
377
- if st.button("Send 🚀") and message.strip():
378
- save_chat_entry(st.session_state.username, message)
379
- st.session_state.timer_start = time.time()
380
- save_username(st.session_state.username)
381
- st.rerun()
382
 
383
- # Quote section
384
- st.subheader("Quote of the Moment 📝")
385
- quotes = load_quotes(st.session_state.quote_source)
386
- if quotes:
387
- st.session_state.quote_index = st.session_state.quote_index % len(quotes)
388
- quote = quotes[st.session_state.quote_index]
389
- col1, col2 = st.columns([5, 1])
390
- with col1:
391
- st.markdown(quote)
392
- with col2:
393
- if st.button("👍 Upvote", key="quote_vote"):
394
- user_hash = generate_user_hash()
395
- save_vote(QUOTE_VOTES_FILE, quote, user_hash)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  st.session_state.timer_start = time.time()
397
  save_username(st.session_state.username)
398
  st.rerun()
399
- if time.time() - st.session_state.timer_start > 10: # 10s quote refresh
400
- st.session_state.quote_index = (st.session_state.quote_index + 1) % len(quotes)
401
- st.session_state.quote_source = "custom" if st.session_state.quote_source == "famous" else "famous"
402
- quotes = load_quotes(st.session_state.quote_source)
403
- st.session_state.quote_index = st.session_state.quote_index % len(quotes) if quotes else 0
404
- st.session_state.timer_start = time.time()
405
- save_username(st.session_state.username)
406
- st.rerun()
407
- else:
408
- st.markdown("No quotes available - check back later! 📭")
409
-
410
- # IVA Media Gallery
411
- st.subheader("IVA Media Gallery 🎨🎶🎥")
412
- media_files = (
413
- glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg") + glob.glob(f"{MEDIA_DIR}/*.jpeg") +
414
- glob.glob(f"{MEDIA_DIR}/*.mp3") + glob.glob(f"{MEDIA_DIR}/*.wav") +
415
- glob.glob(f"{MEDIA_DIR}/*.mp4")
416
- )
417
- if media_files:
418
- media_cols = st.slider("Gallery Columns", min_value=1, max_value=5, value=3)
419
- cols = st.columns(media_cols)
420
- for idx, media_file in enumerate(media_files):
421
- with cols[idx % media_cols]:
422
- if media_file.endswith(('.png', '.jpg', '.jpeg')):
423
- st.image(media_file, use_container_width=True)
424
- elif media_file.endswith(('.mp3', '.wav')):
425
- st.markdown(get_audio_html(media_file, width="100%"), unsafe_allow_html=True)
426
- elif media_file.endswith('.mp4'):
427
- st.markdown(get_video_html(media_file, width="100%"), unsafe_allow_html=True)
428
- if st.button(f"👍 Upvote {os.path.basename(media_file)}", key=f"media_vote_{idx}"):
429
- user_hash = generate_user_hash()
430
- save_vote(IMAGE_VOTES_FILE, media_file, user_hash)
431
  st.session_state.timer_start = time.time()
432
  save_username(st.session_state.username)
433
  st.rerun()
434
- else:
435
- st.error("No media files (images, audio, video) found in 'media' directory!")
436
-
437
- # Refresh rate controls with pulsing timer
438
- st.subheader("Set Refresh Rate ⏳")
439
- refresh_rate = st.slider("Refresh Rate (seconds)", min_value=1, max_value=300, value=st.session_state.refresh_rate, step=1)
440
- if refresh_rate != st.session_state.refresh_rate:
441
- st.session_state.refresh_rate = refresh_rate
442
- st.session_state.timer_start = time.time()
443
- save_username(st.session_state.username)
444
 
445
- col1, col2, col3 = st.columns(3)
446
- with col1:
447
- if st.button("🐇 Small (1s)"):
448
- st.session_state.refresh_rate = 1
449
- st.session_state.timer_start = time.time()
450
- save_username(st.session_state.username)
451
- with col2:
452
- if st.button("🐢 Medium (5s)"):
453
- st.session_state.refresh_rate = 5
454
- st.session_state.timer_start = time.time()
455
- save_username(st.session_state.username)
456
- with col3:
457
- if st.button("🐘 Large (5m)"):
458
- st.session_state.refresh_rate = 300
 
 
 
 
 
 
 
459
  st.session_state.timer_start = time.time()
460
  save_username(st.session_state.username)
 
461
 
462
- # Pulsing countdown timer with emoji digits and random Unicode font
463
- timer_placeholder = st.empty()
464
- start_time = st.session_state.timer_start
465
- for i in range(st.session_state.refresh_rate, -1, -1):
466
- font_name, font_func = random.choice(UNICODE_FONTS)
467
- countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
468
- timer_emoji = "⏳" if i % 2 == 0 else "💓" # Pulse effect
469
- timer_placeholder.markdown(f"<p class='timer'>{timer_emoji} {font_func('Next refresh in:')} {countdown_str} {font_func('seconds')}</p>", unsafe_allow_html=True)
470
- time.sleep(1) # Pulse every second
471
- st.session_state.timer_start = time.time()
472
- st.session_state.last_refresh = time.time()
473
- save_username(st.session_state.username)
474
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
- # Sidebar vote stats
477
  st.sidebar.subheader("Vote Totals")
478
  chat_votes = load_votes(QUOTE_VOTES_FILE)
479
  image_votes = load_votes(IMAGE_VOTES_FILE)
@@ -482,21 +624,16 @@ def create_streamlit_interface(initial_username):
482
  for image, count in image_votes.items():
483
  st.sidebar.write(f"{image}: {count} votes")
484
 
485
- # Main event - the ringmaster kicking off the Sector circus! 🎪🤡
486
- async def main():
487
- """🎤 Drops the mic and starts the Sector party - it’s showtime, folks! 🎉"""
488
- global NODE_NAME, server_task
489
- NODE_NAME, port = get_node_name()
490
-
491
- # Start WebSocket server only once as a background task
492
- if server_task is None:
493
- server_task = asyncio.create_task(run_websocket_server())
494
-
495
- query_params = st.query_params if hasattr(st, 'query_params') else {}
496
- initial_username = query_params.get("username", random.choice(FUN_USERNAMES)) if query_params else random.choice(FUN_USERNAMES)
497
- print(f"Welcoming {initial_username} to the Sector bash!")
498
 
499
- create_streamlit_interface(initial_username)
 
500
 
501
  if __name__ == "__main__":
502
- asyncio.run(main())
 
2
  import asyncio
3
  import websockets
4
  import uuid
 
5
  from datetime import datetime
6
  import os
7
  import random
 
9
  import hashlib
10
  from PIL import Image
11
  import glob
12
+ import base64
13
+ import io
14
+ import streamlit.components.v1 as components
15
+ import edge_tts
16
+ from audio_recorder_streamlit import audio_recorder
17
+ import nest_asyncio
18
  import re
19
+ from streamlit_paste_button import paste_image_button
20
+ import pytz
21
+ import shutil
22
+ import anthropic
23
+ import openai
24
+ from PyPDF2 import PdfReader
25
+ import threading
26
+ import json
27
+ import zipfile
28
+ from gradio_client import Client
29
+ from dotenv import load_dotenv
30
+ from streamlit_marquee import streamlit_marquee
31
+ from collections import defaultdict, Counter
32
+ import pandas as pd
33
+
34
+ # 🛠️ Patch asyncio for nesting
35
+ nest_asyncio.apply()
36
+
37
+ # 🎨 Page Config
38
  st.set_page_config(
39
+ page_title="🚲TalkingAIResearcher🏆",
40
+ page_icon="🚲🏆",
41
  layout="wide",
42
  initial_sidebar_state="auto"
43
  )
44
 
45
+ # 🌟 Static Config
46
+ icons = '🤖🧠🔬📝'
47
+ Site_Name = '🤖🧠Chat & Quote Node📝🔬'
48
+ START_ROOM = "Sector 🌌"
49
+ FUN_USERNAMES = {
50
+ "CosmicJester 🌌": "en-US-AriaNeural",
51
+ "PixelPanda 🐼": "en-US-JennyNeural",
52
+ "QuantumQuack 🦆": "en-GB-SoniaNeural",
53
+ "StellarSquirrel 🐿️": "en-AU-NatashaNeural",
54
+ "GizmoGuru ⚙️": "en-CA-ClaraNeural",
55
+ "NebulaNinja 🌠": "en-US-GuyNeural",
56
+ "ByteBuster 💾": "en-GB-RyanNeural",
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
+ EDGE_TTS_VOICES = list(set(FUN_USERNAMES.values()))
72
+ FILE_EMOJIS = {"md": "📝", "mp3": "🎵", "wav": "🔊"}
73
+
74
+ # 📁 Directories
75
+ for d in ["chat_logs", "vote_logs", "audio_logs", "history_logs", "media_files", "audio_cache"]:
76
+ os.makedirs(d, exist_ok=True)
77
 
 
78
  CHAT_DIR = "chat_logs"
79
  VOTE_DIR = "vote_logs"
80
+ MEDIA_DIR = "media_files"
81
+ AUDIO_CACHE_DIR = "audio_cache"
82
+ AUDIO_DIR = "audio_logs"
83
  STATE_FILE = "user_state.txt"
 
 
 
84
 
 
85
  CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
86
  QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
87
  IMAGE_VOTES_FILE = os.path.join(VOTE_DIR, "image_votes.md")
88
  HISTORY_FILE = os.path.join(VOTE_DIR, "vote_history.md")
89
 
90
+ # 🔑 API Keys
91
+ load_dotenv()
92
+ anthropic_key = os.getenv('ANTHROPIC_API_KEY', st.secrets.get('ANTHROPIC_API_KEY', ""))
93
+ openai_api_key = os.getenv('OPENAI_API_KEY', st.secrets.get('OPENAI_API_KEY', ""))
94
+ openai_client = openai.OpenAI(api_key=openai_api_key)
95
+
96
+ # 🕒 Timestamp Helper
97
+ def format_timestamp_prefix(username=""):
98
+ central = pytz.timezone('US/Central')
99
+ now = datetime.now(central)
100
+ return f"{now.strftime('%Y%m%d_%H%M%S')}-by-{username}"
101
+
102
+ # 📈 Performance Timer
103
+ class PerformanceTimer:
104
+ def __init__(self, name): self.name, self.start = name, None
105
+ def __enter__(self):
106
+ self.start = time.time()
107
+ return self
108
+ def __exit__(self, *args):
109
+ duration = time.time() - self.start
110
+ st.session_state['operation_timings'][self.name] = duration
111
+ st.session_state['performance_metrics'][self.name].append(duration)
112
+
113
+ # 🎛️ Session State Init
114
+ def init_session_state():
115
+ defaults = {
116
+ 'server_running': False, 'server_task': None, 'active_connections': {},
117
+ 'media_notifications': [], 'last_chat_update': 0, 'displayed_chat_lines': [],
118
+ 'message_text': "", 'audio_cache': {}, 'pasted_image_data': None,
119
+ 'quote_line': None, 'refresh_rate': 5, 'base64_cache': {},
120
+ 'transcript_history': [], 'last_transcript': "", 'image_hashes': set(),
121
+ 'tts_voice': "en-US-AriaNeural", 'chat_history': [], 'marquee_settings': {
122
+ "background": "#1E1E1E", "color": "#FFFFFF", "font-size": "14px",
123
+ "animationDuration": "20s", "width": "100%", "lineHeight": "35px"
124
+ }, 'operation_timings': {}, 'performance_metrics': defaultdict(list),
125
+ 'enable_audio': True, 'download_link_cache': {}, 'username': None,
126
+ 'autosend': True, 'autosearch': True, 'last_message': "", 'last_query': "",
127
+ 'mp3_files': {}, 'timer_start': time.time(), 'quote_index': 0,
128
+ 'quote_source': "famous"
129
+ }
130
+ for k, v in defaults.items():
131
+ if k not in st.session_state: st.session_state[k] = v
132
+
133
+ # 🖌️ Marquee Helpers
134
+ def update_marquee_settings_ui():
135
+ st.sidebar.markdown("### 🎯 Marquee Settings")
136
+ cols = st.sidebar.columns(2)
137
+ with cols[0]:
138
+ st.session_state['marquee_settings']['background'] = st.color_picker("🎨 Background", "#1E1E1E")
139
+ st.session_state['marquee_settings']['color'] = st.color_picker("✍️ Text", "#FFFFFF")
140
+ with cols[1]:
141
+ st.session_state['marquee_settings']['font-size'] = f"{st.slider('📏 Size', 10, 24, 14)}px"
142
+ st.session_state['marquee_settings']['animationDuration'] = f"{st.slider('⏱️ Speed', 1, 20, 20)}s"
143
+
144
+ def display_marquee(text, settings, key_suffix=""):
145
+ truncated = text[:280] + "..." if len(text) > 280 else text
146
+ streamlit_marquee(content=truncated, **settings, key=f"marquee_{key_suffix}")
147
+ st.write("")
148
+
149
+ # 📝 Text & File Helpers
150
+ def clean_text_for_tts(text): return re.sub(r'[#*!\[\]]+', '', ' '.join(text.split()))[:200] or "No text"
151
+ def clean_text_for_filename(text): return '_'.join(re.sub(r'[^\w\s-]', '', text.lower()).split())[:200]
152
+ def get_high_info_terms(text, top_n=10):
153
+ stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with'}
154
+ words = re.findall(r'\b\w+(?:-\w+)*\b', text.lower())
155
+ bi_grams = [' '.join(pair) for pair in zip(words, words[1:])]
156
+ filtered = [t for t in words + bi_grams if t not in stop_words and len(t.split()) <= 2]
157
+ return [t for t, _ in Counter(filtered).most_common(top_n)]
158
+
159
+ def generate_filename(prompt, response, file_type="md"):
160
+ prefix = format_timestamp_prefix()
161
+ terms = get_high_info_terms(prompt + " " + response, 5)
162
+ snippet = clean_text_for_filename(prompt[:40] + " " + response[:40])
163
+ wct, sw = len(prompt.split()), len(response.split())
164
+ dur = round((wct + sw) / 2.5)
165
+ base = '_'.join(list(dict.fromkeys(terms + [snippet])))[:200 - len(prefix) - len(f"_wct{wct}_sw{sw}_dur{dur}.{file_type}")]
166
+ return f"{prefix}{base}_wct{wct}_sw{sw}_dur{dur}.{file_type}"
167
+
168
+ def create_file(prompt, response, file_type="md"):
169
+ filename = generate_filename(prompt, response, file_type)
170
+ with open(filename, 'w', encoding='utf-8') as f: f.write(prompt + "\n\n" + response)
171
+ return filename
172
+
173
+ def get_download_link(file, file_type="mp3"):
174
+ cache_key = f"dl_{file}"
175
+ if cache_key not in st.session_state['download_link_cache']:
176
+ with open(file, "rb") as f:
177
+ b64 = base64.b64encode(f.read()).decode()
178
+ 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")} Download {os.path.basename(file)}</a>'
179
+ return st.session_state['download_link_cache'][cache_key]
180
 
181
+ def save_username(username):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  try:
183
+ with open(STATE_FILE, 'w') as f:
184
+ f.write(username)
 
185
  except Exception as e:
186
+ print(f"Failed to save username: {e}")
 
187
 
188
+ def load_username():
189
+ if os.path.exists(STATE_FILE):
190
+ try:
191
+ with open(STATE_FILE, 'r') as f:
192
+ return f.read().strip()
193
+ except Exception as e:
194
+ print(f"Failed to load username: {e}")
195
+ return None
196
+
197
+ # 🎶 Audio Processing
198
+ async def async_edge_tts_generate(text, voice, username, rate=0, pitch=0, file_format="mp3"):
199
+ cache_key = f"{text[:100]}_{voice}_{rate}_{pitch}_{file_format}"
200
+ if cache_key in st.session_state['audio_cache']: return st.session_state['audio_cache'][cache_key], 0
201
+ start_time = time.time()
202
+ text = clean_text_for_tts(text)
203
+ if not text: return None, 0
204
+ filename = f"{AUDIO_DIR}/{format_timestamp_prefix(username)}_{voice}.{file_format}"
205
+ communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
206
+ await communicate.save(filename)
207
+ st.session_state['audio_cache'][cache_key] = filename
208
+
209
+ md_filename = filename.replace(".mp3", ".md")
210
+ md_content = f"# Chat Audio Log\n\n**Player:** {username}\n**Voice:** {voice}\n**Text:**\n```markdown\n{text}\n```"
211
+ with open(md_filename, 'w', encoding='utf-8') as f: f.write(md_content)
212
+
213
+ return filename, time.time() - start_time
214
+
215
+ def play_and_download_audio(file_path):
216
+ if file_path and os.path.exists(file_path):
217
+ st.audio(file_path)
218
+ st.markdown(get_download_link(file_path), unsafe_allow_html=True)
219
+
220
+ def load_mp3_viewer():
221
+ mp3_files = glob.glob(f"{AUDIO_DIR}/*.mp3")
222
+ for mp3 in mp3_files:
223
+ filename = os.path.basename(mp3)
224
+ if filename not in st.session_state['mp3_files']:
225
+ st.session_state['mp3_files'][filename] = mp3
226
+
227
+ async def save_chat_entry(username, message, is_markdown=False):
228
+ central = pytz.timezone('US/Central')
229
+ timestamp = datetime.now(central).strftime("%Y-%m-%d %H:%M:%S")
230
+ entry = f"[{timestamp}] {username}: {message}" if not is_markdown else f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
231
+ with open(CHAT_FILE, 'a') as f: f.write(f"{entry}\n")
232
+ voice = FUN_USERNAMES.get(username, "en-US-AriaNeural")
233
+ audio_file, _ = await async_edge_tts_generate(message, voice, username)
234
+ if audio_file:
235
+ with open(HISTORY_FILE, 'a') as f: f.write(f"[{timestamp}] {username}: Audio - {audio_file}\n")
236
+ st.session_state['mp3_files'][os.path.basename(audio_file)] = audio_file
237
+ await broadcast_message(f"{username}|{message}", "chat")
238
+ st.session_state.last_chat_update = time.time()
239
+ st.session_state.chat_history.append(entry)
240
+ return audio_file
241
+
242
+ async def load_chat():
243
  if not os.path.exists(CHAT_FILE):
244
+ with open(CHAT_FILE, 'a') as f: f.write(f"# {START_ROOM} Chat\n\nWelcome to the cosmic hub! 🎤\n")
245
+ with open(CHAT_FILE, 'r') as f:
246
+ content = f.read().strip()
247
+ lines = content.split('\n')
248
+ numbered_content = "\n".join(f"{i+1}. {line}" for i, line in enumerate(lines) if line.strip())
249
+ return numbered_content
250
+
251
+ # 🌐 WebSocket Handling
252
+ async def websocket_handler(websocket, path):
253
+ client_id = str(uuid.uuid4())
254
+ room_id = "chat"
255
+ if room_id not in st.session_state.active_connections:
256
+ st.session_state.active_connections[room_id] = {}
257
+ st.session_state.active_connections[room_id][client_id] = websocket
258
+ username = st.session_state.get('username', random.choice(list(FUN_USERNAMES.keys())))
259
+ chat_content = await load_chat()
260
+ if not any(f"Client-{client_id}" in line for line in chat_content.split('\n')):
261
+ await save_chat_entry("System 🌟", f"{username} has joined {START_ROOM}!")
262
  try:
263
+ async for message in websocket:
264
+ if '|' in message:
265
+ username, content = message.split('|', 1)
266
+ await save_chat_entry(username, content)
267
+ else:
268
+ await websocket.send("ERROR|Message format: username|content")
269
+ except websockets.ConnectionClosed:
270
+ await save_chat_entry("System 🌟", f"{username} has left {START_ROOM}!")
271
+ finally:
272
+ if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
273
+ del st.session_state.active_connections[room_id][client_id]
274
+
275
+ async def broadcast_message(message, room_id):
276
+ if room_id in st.session_state.active_connections:
277
+ disconnected = []
278
+ for client_id, ws in st.session_state.active_connections[room_id].items():
279
+ try:
280
+ await ws.send(message)
281
+ except websockets.ConnectionClosed:
282
+ disconnected.append(client_id)
283
+ for client_id in disconnected:
284
+ if client_id in st.session_state.active_connections[room_id]:
285
+ del st.session_state.active_connections[room_id][client_id]
286
+
287
+ async def run_websocket_server():
288
+ if not st.session_state.server_running:
289
+ server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
290
+ st.session_state.server_running = True
291
+ await server.wait_closed()
292
+
293
+ # 📚 PDF to Audio
294
+ class AudioProcessor:
295
+ def __init__(self):
296
+ self.cache_dir = AUDIO_CACHE_DIR
297
+ os.makedirs(self.cache_dir, exist_ok=True)
298
+ self.metadata = json.load(open(f"{self.cache_dir}/metadata.json")) if os.path.exists(f"{self.cache_dir}/metadata.json") else {}
299
+
300
+ def _save_metadata(self):
301
+ with open(f"{self.cache_dir}/metadata.json", 'w') as f: json.dump(self.metadata, f)
302
+
303
+ async def create_audio(self, text, voice='en-US-AriaNeural'):
304
+ cache_key = hashlib.md5(f"{text}:{voice}".encode()).hexdigest()
305
+ cache_path = f"{self.cache_dir}/{cache_key}.mp3"
306
+ if cache_key in self.metadata and os.path.exists(cache_path):
307
+ return open(cache_path, 'rb').read()
308
+ text = clean_text_for_tts(text)
309
+ if not text: return None
310
+ communicate = edge_tts.Communicate(text, voice)
311
+ await communicate.save(cache_path)
312
+ self.metadata[cache_key] = {'timestamp': datetime.now().isoformat(), 'text_length': len(text), 'voice': voice}
313
+ self._save_metadata()
314
+ return open(cache_path, 'rb').read()
315
+
316
+ def process_pdf(pdf_file, max_pages, voice, audio_processor):
317
+ reader = PdfReader(pdf_file)
318
+ total_pages = min(len(reader.pages), max_pages)
319
+ texts, audios = [], {}
320
+ async def process_page(i, text): audios[i] = await audio_processor.create_audio(text, voice)
321
+ for i in range(total_pages):
322
+ text = reader.pages[i].extract_text()
323
+ texts.append(text)
324
+ threading.Thread(target=lambda: asyncio.run(process_page(i, text))).start()
325
+ return texts, audios, total_pages
326
+
327
+ # 🔍 ArXiv & AI Lookup
328
+ def parse_arxiv_refs(ref_text):
329
+ if not ref_text: return []
330
+ papers = []
331
+ current = {}
332
+ for line in ref_text.split('\n'):
333
+ if line.count('|') == 2:
334
+ if current: papers.append(current)
335
+ date, title, *_ = line.strip('* ').split('|')
336
+ 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)}"
337
+ current = {'date': date, 'title': title, 'url': url, 'authors': '', 'summary': '', 'full_audio': None, 'download_base64': ''}
338
+ elif current:
339
+ if not current['authors']: current['authors'] = line.strip('* ')
340
+ else: current['summary'] += ' ' + line.strip() if current['summary'] else line.strip()
341
+ if current: papers.append(current)
342
+ return papers[:20]
343
+
344
+ def generate_5min_feature_markdown(paper):
345
+ title, summary, authors, date, url = paper['title'], paper['summary'], paper['authors'], paper['date'], paper['url']
346
+ pdf_url = url.replace("abs", "pdf") + (".pdf" if not url.endswith(".pdf") else "")
347
+ wct, sw = len(title.split()), len(summary.split())
348
+ terms = get_high_info_terms(summary, 15)
349
+ rouge = round((len(terms) / max(sw, 1)) * 100, 2)
350
+ 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```"
351
+ return f"""
352
+ ## 📄 {title}
353
+ **Authors:** {authors} | **Date:** {date} | **Words:** Title: {wct}, Summary: {sw}
354
+ **Links:** [Abstract]({url}) | [PDF]({pdf_url})
355
+ **Terms:** {', '.join(terms)} | **ROUGE:** {rouge}%
356
+ ### 🎤 TTF Read Aloud
357
+ - **Title:** {title} | **Terms:** {', '.join(terms)} | **ROUGE:** {rouge}%
358
+ #### Concepts Graph
359
+ {mermaid}
360
+ ---
361
+ """
362
+
363
+ def create_detailed_paper_md(papers): return "# Detailed Summary\n" + "\n".join(generate_5min_feature_markdown(p) for p in papers)
364
+
365
+ async def create_paper_audio_files(papers, query):
366
+ for p in papers:
367
+ audio_text = clean_text_for_tts(f"{p['title']} by {p['authors']}. {p['summary']}")
368
+ p['full_audio'], _ = await async_edge_tts_generate(audio_text, st.session_state['tts_voice'], p['authors'])
369
+ if p['full_audio']: p['download_base64'] = get_download_link(p['full_audio'])
370
+
371
+ async def perform_ai_lookup(q, useArxiv=True, useArxivAudio=False):
372
+ client = anthropic.Anthropic(api_key=anthropic_key)
373
+ response = client.messages.create(model="claude-3-sonnet-20240229", max_tokens=1000, messages=[{"role": "user", "content": q}])
374
+ result = response.content[0].text
375
+ st.markdown("### Claude's Reply 🧠\n" + result)
376
+ md_file = create_file(q, result)
377
+ audio_file, _ = await async_edge_tts_generate(result, st.session_state['tts_voice'], "System")
378
+ play_and_download_audio(audio_file)
379
+
380
+ if useArxiv:
381
+ q += result
382
+ gradio_client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
383
+ refs = gradio_client.predict(q, 10, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md")[0]
384
+ result = f"🔎 {q}\n\n{refs}"
385
+ md_file, audio_file = create_file(q, result), (await async_edge_tts_generate(result, st.session_state['tts_voice'], "System"))[0]
386
+ play_and_download_audio(audio_file)
387
+ papers = parse_arxiv_refs(refs)
388
+ if papers and useArxivAudio: await create_paper_audio_files(papers, q)
389
+ return result, papers
390
+ return result, []
391
+
392
  def save_vote(file, item, user_hash):
 
393
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
394
  entry = f"[{timestamp}] {user_hash} voted for {item}"
395
  try:
 
401
  except Exception as e:
402
  print(f"Vote save flop: {e}")
403
  return False
404
+
 
405
  def load_votes(file):
 
406
  if not os.path.exists(file):
407
  with open(file, 'w') as f:
408
  f.write("# Vote Tally\n\nNo votes yet - get clicking! 🖱️\n")
 
419
  print(f"Vote load oopsie: {e}")
420
  return {}
421
 
 
422
  def generate_user_hash():
 
423
  if 'user_hash' not in st.session_state:
424
  session_id = str(random.getrandbits(128))
425
  hash_object = hashlib.md5(session_id.encode())
426
  st.session_state['user_hash'] = hash_object.hexdigest()[:8]
427
  return st.session_state['user_hash']
428
 
429
+ # 📦 Zip Files
430
+ def create_zip_of_files(md_files, mp3_files, query):
431
+ all_files = md_files + mp3_files
432
+ if not all_files: return None
433
+ 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)
434
+ zip_name = f"{format_timestamp_prefix()}_{'-'.join(terms)[:20]}.zip"
435
+ with zipfile.ZipFile(zip_name, 'w') as z: [z.write(f) for f in all_files]
436
+ return zip_name
437
+
438
+ async def save_pasted_image(image, username):
439
+ img_hash = hashlib.md5(image.tobytes()).hexdigest()[:8]
440
+ if img_hash in st.session_state.image_hashes:
441
+ return None
442
+ timestamp = format_timestamp_prefix(username)
443
+ filename = f"{timestamp}-{img_hash}.png"
444
+ filepath = os.path.join(MEDIA_DIR, filename)
445
+ image.save(filepath, "PNG")
446
+ st.session_state.image_hashes.add(img_hash)
447
+ return filepath
448
+
449
+ # 🎮 Main Interface
450
+ async def async_interface():
451
+ init_session_state()
452
+ load_mp3_viewer()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  saved_username = load_username()
454
  if saved_username and saved_username in FUN_USERNAMES:
455
+ st.session_state.username = saved_username
456
+ if not st.session_state.username:
457
+ available = [n for n in FUN_USERNAMES if not any(f"{n} has joined" in l for l in (await load_chat()).split('\n'))]
458
+ st.session_state.username = random.choice(available or list(FUN_USERNAMES.keys()))
459
+ st.session_state.tts_voice = FUN_USERNAMES[st.session_state.username]
460
+ await save_chat_entry("System 🌟", f"{st.session_state.username} has joined {START_ROOM}!")
461
+ save_username(st.session_state.username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
 
463
+ st.title(f"{Site_Name} for {st.session_state.username}")
464
+ update_marquee_settings_ui()
465
+ display_marquee(f"🚀 Welcome to {START_ROOM} | 🤖 {st.session_state.username}", st.session_state['marquee_settings'], "welcome")
466
+
467
+ if not st.session_state.server_task:
468
+ st.session_state.server_task = asyncio.create_task(run_websocket_server())
469
+
470
+ tab_main = st.radio("Action:", ["🎤 Chat & Voice", "📸 Media", "🔍 ArXiv", "📚 PDF to Audio"], horizontal=True)
471
+ useArxiv, useArxivAudio = st.checkbox("Search ArXiv", True), st.checkbox("ArXiv Audio", False)
472
+ st.session_state.autosend = st.checkbox("Autosend Chat", value=True)
473
+ st.session_state.autosearch = st.checkbox("Autosearch ArXiv", value=True)
474
+
475
+ # 🎤 Chat & Voice
476
+ if tab_main == "🎤 Chat & Voice":
477
+ st.subheader(f"{START_ROOM} Chat 💬")
478
+ chat_content = await load_chat()
479
+ chat_container = st.container()
480
+ with chat_container:
481
+ lines = chat_content.split('\n')
482
+ for i, line in enumerate(lines):
483
+ if line.strip():
484
+ col1, col2 = st.columns([5, 1])
485
+ with col1:
486
+ st.markdown(line)
487
+ for mp3_name, mp3_path in st.session_state['mp3_files'].items():
488
+ if st.session_state.username in mp3_name and any(word in mp3_name for word in line.split()):
489
+ st.audio(mp3_path)
490
+ break
491
+ with col2:
492
+ if st.button(f"👍", key=f"chat_vote_{i}"):
493
+ user_hash = generate_user_hash()
494
+ save_vote(QUOTE_VOTES_FILE, line, user_hash)
495
+ st.session_state.timer_start = time.time()
496
+ save_username(st.session_state.username)
497
+ st.rerun()
498
+
499
+ message = st.text_input(f"Message as {st.session_state.username}", key="message_input")
500
+ paste_result = paste_image_button("📋 Paste Image or Text", key="paste_button_msg")
501
+ if paste_result.image_data is not None:
502
+ if isinstance(paste_result.image_data, str):
503
+ st.session_state.message_text = paste_result.image_data
504
+ st.text_input(f"Message as {st.session_state.username}", key="message_input_paste", value=st.session_state.message_text)
505
+ else:
506
+ st.image(paste_result.image_data, caption="Pasted Image")
507
+ filename = await save_pasted_image(paste_result.image_data, st.session_state.username)
508
+ if filename:
509
+ st.session_state.pasted_image_data = filename
510
+ if (message and message != st.session_state.last_message) or st.session_state.pasted_image_data:
511
+ st.session_state.last_message = message
512
+ if st.session_state.autosend or st.button("Send 🚀"):
513
+ if message.strip():
514
+ await save_chat_entry(st.session_state.username, message, True)
515
+ if st.session_state.pasted_image_data:
516
+ await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}")
517
+ st.session_state.pasted_image_data = None
518
  st.session_state.timer_start = time.time()
519
  save_username(st.session_state.username)
520
  st.rerun()
521
+
522
+ st.subheader("🎤 Speech-to-Chat")
523
+ from mycomponent import speech_component
524
+ transcript_data = speech_component(default_value=st.session_state.get('last_transcript', ''))
525
+ if transcript_data and 'value' in transcript_data:
526
+ transcript = transcript_data['value'].strip()
527
+ st.write(f"🎙️ You said: {transcript}")
528
+ if transcript and transcript != st.session_state.last_transcript:
529
+ st.session_state.last_transcript = transcript
530
+ if st.session_state.autosend:
531
+ await save_chat_entry(st.session_state.username, transcript, True)
532
+ st.session_state.timer_start = time.time()
533
+ save_username(st.session_state.username)
534
+ st.rerun()
535
+ elif st.button("Send to Chat"):
536
+ await save_chat_entry(st.session_state.username, transcript, True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  st.session_state.timer_start = time.time()
538
  save_username(st.session_state.username)
539
  st.rerun()
 
 
 
 
 
 
 
 
 
 
540
 
541
+ # 📸 Media
542
+ elif tab_main == "📸 Media":
543
+ st.header("📸 Media Gallery")
544
+ tabs = st.tabs(["🎵 Audio", "🖼 Images", "🎥 Video"])
545
+ with tabs[0]:
546
+ for a in glob.glob(f"{MEDIA_DIR}/*.mp3"):
547
+ with st.expander(os.path.basename(a)): play_and_download_audio(a)
548
+ with tabs[1]:
549
+ imgs = glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg")
550
+ if imgs:
551
+ cols = st.columns(3)
552
+ for i, f in enumerate(imgs): cols[i % 3].image(f, use_container_width=True)
553
+ with tabs[2]:
554
+ for v in glob.glob(f"{MEDIA_DIR}/*.mp4"):
555
+ with st.expander(os.path.basename(v)): st.video(v)
556
+
557
+ uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3'])
558
+ if uploaded_file:
559
+ filename = f"{format_timestamp_prefix(st.session_state.username)}-{hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]}.{uploaded_file.name.split('.')[-1]}"
560
+ with open(f"{MEDIA_DIR}/{filename}", 'wb') as f: f.write(uploaded_file.getbuffer())
561
+ await save_chat_entry(st.session_state.username, f"Uploaded: {filename}")
562
  st.session_state.timer_start = time.time()
563
  save_username(st.session_state.username)
564
+ st.rerun()
565
 
566
+ # 🔍 ArXiv
567
+ elif tab_main == "🔍 ArXiv":
568
+ q = st.text_input("🔍 Query:", key="arxiv_query")
569
+ if q and q != st.session_state.last_query:
570
+ st.session_state.last_query = q
571
+ if st.session_state.autosearch or st.button("🔍 Run"):
572
+ result, papers = await perform_ai_lookup(q, useArxiv, useArxivAudio)
573
+ for i, p in enumerate(papers, 1):
574
+ with st.expander(f"{i}. 📄 {p['title']}"):
575
+ st.markdown(f"**{p['date']} | {p['title']}** — [Link]({p['url']})")
576
+ st.markdown(generate_5min_feature_markdown(p))
577
+ if p.get('full_audio'): play_and_download_audio(p['full_audio'])
578
+
579
+ # 📚 PDF to Audio
580
+ elif tab_main == "📚 PDF to Audio":
581
+ audio_processor = AudioProcessor()
582
+ pdf_file = st.file_uploader("Choose PDF", "pdf")
583
+ max_pages = st.slider('Pages', 1, 100, 10)
584
+ if pdf_file:
585
+ with st.spinner('Processing...'):
586
+ texts, audios, total = process_pdf(pdf_file, max_pages, st.session_state['tts_voice'], audio_processor)
587
+ for i, text in enumerate(texts):
588
+ with st.expander(f"Page {i+1}"):
589
+ st.markdown(text)
590
+ while i not in audios: time.sleep(0.1)
591
+ if audios[i]:
592
+ st.audio(audios[i], format='audio/mp3')
593
+ st.markdown(get_download_link(io.BytesIO(audios[i]), "mp3"), unsafe_allow_html=True)
594
+
595
+ # 🗂️ Sidebar with Dialog and Audio
596
+ st.sidebar.subheader("Voice Settings")
597
+ new_username = st.sidebar.selectbox("Change Name/Voice", list(FUN_USERNAMES.keys()), index=list(FUN_USERNAMES.keys()).index(st.session_state.username))
598
+ if new_username != st.session_state.username:
599
+ await save_chat_entry("System 🌟", f"{st.session_state.username} changed to {new_username}")
600
+ st.session_state.username, st.session_state.tts_voice = new_username, FUN_USERNAMES[new_username]
601
+ st.session_state.timer_start = time.time()
602
+ save_username(st.session_state.username)
603
+ st.rerun()
604
+
605
+ st.sidebar.markdown("### 💬 Chat Dialog & Audio")
606
+ chat_content = await load_chat()
607
+ lines = chat_content.split('\n')
608
+ audio_files = sorted(glob.glob(f"{AUDIO_DIR}/*.mp3"), key=os.path.getmtime, reverse=True)
609
+ for line in lines[-10:]:
610
+ if line.strip():
611
+ st.sidebar.markdown(f"**{line}**")
612
+ for mp3 in audio_files:
613
+ mp3_name = os.path.basename(mp3)
614
+ if st.session_state.username in mp3_name and any(word in mp3_name for word in line.split()):
615
+ st.sidebar.audio(mp3)
616
+ st.sidebar.markdown(get_download_link(mp3), unsafe_allow_html=True)
617
+ break
618
 
 
619
  st.sidebar.subheader("Vote Totals")
620
  chat_votes = load_votes(QUOTE_VOTES_FILE)
621
  image_votes = load_votes(IMAGE_VOTES_FILE)
 
624
  for image, count in image_votes.items():
625
  st.sidebar.write(f"{image}: {count} votes")
626
 
627
+ md_files, mp3_files = glob.glob("*.md"), glob.glob(f"{AUDIO_DIR}/*.mp3")
628
+ st.sidebar.markdown("### 📂 File History")
629
+ for f in sorted(md_files + mp3_files, key=os.path.getmtime, reverse=True)[:10]:
630
+ st.sidebar.write(f"{FILE_EMOJIS.get(f.split('.')[-1], '📄')} {os.path.basename(f)}")
631
+ if st.sidebar.button("⬇️ Zip All"):
632
+ zip_name = create_zip_of_files(md_files, mp3_files, "latest_query")
633
+ if zip_name: st.sidebar.markdown(get_download_link(zip_name, "zip"), unsafe_allow_html=True)
 
 
 
 
 
 
634
 
635
+ def main():
636
+ asyncio.run(async_interface())
637
 
638
  if __name__ == "__main__":
639
+ main()