awacke1 commited on
Commit
87fbb6b
Β·
verified Β·
1 Parent(s): 35cbe71

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +502 -0
app.py ADDED
@@ -0,0 +1,502 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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:
165
+ with open(file, 'a') as f:
166
+ f.write(f"{entry}\n")
167
+ with open(HISTORY_FILE, 'a') as f:
168
+ f.write(f"- {timestamp} - User {user_hash} voted for {item}\n")
169
+ return True
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")
180
+ try:
181
+ with open(file, 'r') as f:
182
+ lines = f.read().strip().split('\n')
183
+ votes = {}
184
+ for line in lines[2:]: # Skip header
185
+ if line.strip() and 'voted for' in line:
186
+ item = line.split('voted for ')[1]
187
+ votes[item] = votes.get(item, 0) + 1
188
+ return votes
189
+ except Exception as e:
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)
480
+ for item, count in chat_votes.items():
481
+ st.sidebar.write(f"{item}: {count} votes")
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())