awacke1 commited on
Commit
a1e7982
Β·
verified Β·
1 Parent(s): 60cd14e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +382 -0
app.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from urllib.parse import quote
14
+ import base64
15
+ import io
16
+ import streamlit.components.v1 as components
17
+ import edge_tts
18
+ from audio_recorder_streamlit import audio_recorder
19
+ import nest_asyncio
20
+ import re
21
+
22
+ # Patch for nested async - sneaky fix! 🐍✨
23
+ nest_asyncio.apply()
24
+
25
+ # Static config - constants rule! πŸ“πŸ‘‘
26
+ icons = 'πŸ€–πŸ§ πŸ”¬πŸ“'
27
+ START_ROOM = "Sector 🌌"
28
+
29
+ # Page setup - dressing up the window! πŸ–ΌοΈπŸŽ€
30
+ st.set_page_config(
31
+ page_title="πŸ€–πŸ§ MMO Chat BrainπŸ“πŸ”¬",
32
+ page_icon=icons,
33
+ layout="wide",
34
+ initial_sidebar_state="auto"
35
+ )
36
+
37
+ # Funky usernames - who’s who in the zoo with unique voices! πŸŽ­πŸΎπŸŽ™οΈ
38
+ FUN_USERNAMES = {
39
+ "CosmicJester 🌌": "en-US-AriaNeural",
40
+ "PixelPanda 🐼": "en-US-JennyNeural",
41
+ "QuantumQuack πŸ¦†": "en-GB-SoniaNeural",
42
+ "StellarSquirrel 🐿️": "en-AU-NatashaNeural",
43
+ "GizmoGuru βš™οΈ": "en-CA-ClaraNeural",
44
+ "NebulaNinja 🌠": "en-US-GuyNeural",
45
+ "ByteBuster πŸ’Ύ": "en-GB-RyanNeural",
46
+ "GalacticGopher 🌍": "en-AU-WilliamNeural",
47
+ "RocketRaccoon πŸš€": "en-CA-LiamNeural",
48
+ "EchoElf 🧝": "en-US-AnaNeural",
49
+ "PhantomFox 🦊": "en-US-BrandonNeural",
50
+ "WittyWizard πŸ§™": "en-GB-ThomasNeural",
51
+ "LunarLlama πŸŒ™": "en-AU-FreyaNeural",
52
+ "SolarSloth β˜€οΈ": "en-CA-LindaNeural",
53
+ "AstroAlpaca πŸ¦™": "en-US-ChristopherNeural",
54
+ "CyberCoyote 🐺": "en-GB-ElliotNeural",
55
+ "MysticMoose 🦌": "en-AU-JamesNeural",
56
+ "GlitchGnome 🧚": "en-CA-EthanNeural",
57
+ "VortexViper 🐍": "en-US-AmberNeural",
58
+ "ChronoChimp πŸ’": "en-GB-LibbyNeural"
59
+ }
60
+
61
+ # Folders galore - organizing chaos! πŸ“‚πŸŒ€
62
+ CHAT_DIR = "chat_logs"
63
+ VOTE_DIR = "vote_logs"
64
+ STATE_FILE = "user_state.txt"
65
+ AUDIO_DIR = "audio_logs"
66
+ HISTORY_DIR = "history_logs"
67
+ MEDIA_DIR = "media_files" # New directory for uploaded media
68
+ os.makedirs(CHAT_DIR, exist_ok=True)
69
+ os.makedirs(VOTE_DIR, exist_ok=True)
70
+ os.makedirs(AUDIO_DIR, exist_ok=True)
71
+ os.makedirs(HISTORY_DIR, exist_ok=True)
72
+ os.makedirs(MEDIA_DIR, exist_ok=True)
73
+
74
+ CHAT_FILE = os.path.join(CHAT_DIR, "global_chat.md")
75
+ QUOTE_VOTES_FILE = os.path.join(VOTE_DIR, "quote_votes.md")
76
+ MEDIA_VOTES_FILE = os.path.join(VOTE_DIR, "media_votes.md")
77
+ HISTORY_FILE = os.path.join(HISTORY_DIR, "chat_history.md")
78
+
79
+ # Fancy digits - numbers got style! πŸ”’πŸ’ƒ
80
+ UNICODE_DIGITS = {i: f"{i}\uFE0F⃣" for i in range(10)}
81
+
82
+ # Massive font collection - typography bonanza! πŸ–‹οΈπŸŽ¨
83
+ UNICODE_FONTS = [
84
+ ("Normal", lambda x: x),
85
+ ("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)),
86
+ ("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)),
87
+ ("Bold Italic", lambda x: "".join(chr(ord(c) + 0x1D468 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D482 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
88
+ ("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)),
89
+ ("Bold Script", lambda x: "".join(chr(ord(c) + 0x1D4D0 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D4EA - 0x61) if 'a' <= c <= 'z' else c for c in x)),
90
+ ("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)),
91
+ ("Bold Fraktur", lambda x: "".join(chr(ord(c) + 0x1D56C - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D586 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
92
+ ("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)),
93
+ ("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)),
94
+ ("Sans Serif 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)),
95
+ ("Sans Serif Italic", lambda x: "".join(chr(ord(c) + 0x1D608 - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D622 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
96
+ ("Sans Serif Bold Italic", lambda x: "".join(chr(ord(c) + 0x1D63C - 0x41) if 'A' <= c <= 'Z' else chr(ord(c) + 0x1D656 - 0x61) if 'a' <= c <= 'z' else c for c in x)),
97
+ ("Monospace", 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)),
98
+ ("Circled", lambda x: "".join(chr(ord(c) - 0x41 + 0x24B6) if 'A' <= c <= 'Z' else chr(ord(c) - 0x61 + 0x24D0) if 'a' <= c <= 'z' else c for c in x)),
99
+ ("Squared", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F130) if 'A' <= c <= 'Z' else c for c in x)),
100
+ ("Negative Circled", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F150) if 'A' <= c <= 'Z' else c for c in x)),
101
+ ("Negative Squared", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F170) if 'A' <= c <= 'Z' else c for c in x)),
102
+ ("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
103
+ ]
104
+
105
+ # Global state - keeping tabs! πŸŒπŸ“‹
106
+ if 'server_running' not in st.session_state:
107
+ st.session_state.server_running = False
108
+ if 'server_task' not in st.session_state:
109
+ st.session_state.server_task = None
110
+ if 'active_connections' not in st.session_state:
111
+ st.session_state.active_connections = {}
112
+ if 'media_notifications' not in st.session_state:
113
+ st.session_state.media_notifications = []
114
+
115
+ # Timestamp wizardry - clock ticks with flair! ⏰🎩
116
+ def format_timestamp_prefix():
117
+ return datetime.now().strftime("%Y%m%d_%H%M%S")
118
+
119
+ # Node naming - christening the beast! 🌐🍼
120
+ def get_node_name():
121
+ parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
122
+ parser.add_argument('--node-name', type=str, default=None)
123
+ parser.add_argument('--port', type=int, default=8501)
124
+ args = parser.parse_args()
125
+ username = st.session_state.get('username', 'System 🌟')
126
+ log_action(username, "🌐🍼 - Node naming - christening the beast!")
127
+ return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port
128
+
129
+ # Action logger - spying on deeds! πŸ•΅οΈπŸ“œ
130
+ def log_action(username, action):
131
+ if 'action_log' not in st.session_state:
132
+ st.session_state.action_log = {}
133
+ user_log = st.session_state.action_log.setdefault(username, {})
134
+ current_time = time.time()
135
+ user_log = {k: v for k, v in user_log.items() if current_time - v < 10}
136
+ st.session_state.action_log[username] = user_log
137
+ if action not in user_log:
138
+ with open(HISTORY_FILE, 'a') as f:
139
+ f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {username}: {action}\n")
140
+ user_log[action] = current_time
141
+
142
+ # Clean text - strip the fancy stuff! πŸ§ΉπŸ“
143
+ def clean_text_for_tts(text):
144
+ cleaned = re.sub(r'[#*!\[\]]+', '', text)
145
+ cleaned = ' '.join(cleaned.split())
146
+ return cleaned[:200] if cleaned else "No text to speak"
147
+
148
+ # Chat saver - words locked tight! πŸ’¬πŸ”’
149
+ async def save_chat_entry(username, message):
150
+ await asyncio.to_thread(log_action, username, "πŸ’¬πŸ”’ - Chat saver - words locked tight!")
151
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
152
+ entry = f"[{timestamp}] {username}: {message}"
153
+ await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"{entry}\n"))
154
+ voice = FUN_USERNAMES.get(username, "en-US-AriaNeural")
155
+ cleaned_message = clean_text_for_tts(message)
156
+ audio_file = await async_edge_tts_generate(cleaned_message, voice)
157
+ if audio_file:
158
+ with open(HISTORY_FILE, 'a') as f:
159
+ f.write(f"[{timestamp}] {username}: Audio generated - {audio_file}\n")
160
+ await broadcast_message(f"{username}|{message}", "chat") # Notify all clients
161
+
162
+ # Chat loader - history unleashed! πŸ“œπŸš€
163
+ async def load_chat():
164
+ username = st.session_state.get('username', 'System 🌟')
165
+ await asyncio.to_thread(log_action, username, "πŸ“œπŸš€ - Chat loader - history unleashed!")
166
+ if not os.path.exists(CHAT_FILE):
167
+ await asyncio.to_thread(lambda: open(CHAT_FILE, 'a').write(f"# {START_ROOM} Chat\n\nWelcome to the cosmic hub - start chatting! 🎀\n"))
168
+ with open(CHAT_FILE, 'r') as f:
169
+ content = await asyncio.to_thread(f.read)
170
+ return content
171
+
172
+ # User lister - who’s in the gang! πŸ‘₯πŸŽ‰
173
+ async def get_user_list(chat_content):
174
+ username = st.session_state.get('username', 'System 🌟')
175
+ await asyncio.to_thread(log_action, username, "πŸ‘₯πŸŽ‰ - User lister - who’s in the gang!")
176
+ users = set()
177
+ for line in chat_content.split('\n'):
178
+ if line.strip() and ': ' in line:
179
+ user = line.split(': ')[1].split(' ')[0]
180
+ users.add(user)
181
+ return sorted(list(users))
182
+
183
+ # Join checker - been here before? πŸšͺπŸ”
184
+ async def has_joined_before(client_id, chat_content):
185
+ username = st.session_state.get('username', 'System 🌟')
186
+ await asyncio.to_thread(log_action, username, "πŸšͺπŸ” - Join checker - been here before?")
187
+ return any(f"Client-{client_id} has joined" in line for line in chat_content.split('\n'))
188
+
189
+ # Suggestion maker - old quips resurface! πŸ’‘πŸ“
190
+ async def get_message_suggestions(chat_content, prefix):
191
+ username = st.session_state.get('username', 'System 🌟')
192
+ await asyncio.to_thread(log_action, username, "πŸ’‘πŸ“ - Suggestion maker - old quips resurface!")
193
+ lines = chat_content.split('\n')
194
+ messages = [line.split(': ', 1)[1] for line in lines if ': ' in line and line.strip()]
195
+ return [msg for msg in messages if msg.lower().startswith(prefix.lower())][:5]
196
+
197
+ # Vote saver - cheers recorded! πŸ‘πŸ“Š
198
+ async def save_vote(file, item, user_hash, username, comment=""):
199
+ await asyncio.to_thread(log_action, username, "πŸ‘πŸ“Š - Vote saver - cheers recorded!")
200
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
201
+ entry = f"[{timestamp}] {user_hash} voted for {item}"
202
+ await asyncio.to_thread(lambda: open(file, 'a').write(f"{entry}\n"))
203
+ await asyncio.to_thread(lambda: open(HISTORY_FILE, "a").write(f"- {timestamp} - User {user_hash} voted for {item}\n"))
204
+ chat_message = f"{username} upvoted: \"{item}\""
205
+ if comment:
206
+ chat_message += f" - {comment}"
207
+ await save_chat_entry(username, chat_message)
208
+
209
+ # Vote counter - tallying the love! πŸ†πŸ“ˆ
210
+ async def load_votes(file):
211
+ username = st.session_state.get('username', 'System 🌟')
212
+ await asyncio.to_thread(log_action, username, "πŸ†πŸ“ˆ - Vote counter - tallying the love!")
213
+ if not os.path.exists(file):
214
+ await asyncio.to_thread(lambda: open(file, 'w').write("# Vote Tally\n\nNo votes yet - get clicking! πŸ–±οΈ\n"))
215
+ with open(file, 'r') as f:
216
+ content = await asyncio.to_thread(f.read)
217
+ lines = content.strip().split('\n')[2:]
218
+ votes = {}
219
+ user_votes = set()
220
+ for line in lines:
221
+ if line.strip() and 'voted for' in line:
222
+ user_hash = line.split('] ')[1].split(' voted for ')[0]
223
+ item = line.split('voted for ')[1]
224
+ vote_key = f"{user_hash}-{item}"
225
+ if vote_key not in user_votes:
226
+ votes[item] = votes.get(item, 0) + 1
227
+ user_votes.add(vote_key)
228
+ return votes
229
+
230
+ # Hash generator - secret codes ahoy! πŸ”‘πŸ•΅οΈ
231
+ async def generate_user_hash():
232
+ username = st.session_state.get('username', 'System 🌟')
233
+ await asyncio.to_thread(log_action, username, "πŸ”‘πŸ•΅οΈ - Hash generator - secret codes ahoy!")
234
+ if 'user_hash' not in st.session_state:
235
+ st.session_state.user_hash = hashlib.md5(str(random.getrandbits(128)).encode()).hexdigest()[:8]
236
+ return st.session_state.user_hash
237
+
238
+ # Audio maker - voices come alive! 🎢🌟
239
+ async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3"):
240
+ username = st.session_state.get('username', 'System 🌟')
241
+ await asyncio.to_thread(log_action, username, "🎢🌟 - Audio maker - voices come alive!")
242
+ timestamp = format_timestamp_prefix()
243
+ filename = os.path.join(AUDIO_DIR, f"audio_{timestamp}_{random.randint(1000, 9999)}.mp3")
244
+ communicate = edge_tts.Communicate(text, voice, rate=f"{rate:+d}%", pitch=f"{pitch:+d}Hz")
245
+ try:
246
+ await communicate.save(filename)
247
+ return filename if os.path.exists(filename) else None
248
+ except edge_tts.exceptions.NoAudioReceived:
249
+ with open(HISTORY_FILE, 'a') as f:
250
+ f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {username}: Audio failed - No audio received for '{text}'\n")
251
+ return None
252
+
253
+ # Audio player - tunes blast off! πŸ”ŠπŸš€
254
+ def play_and_download_audio(file_path):
255
+ if file_path and os.path.exists(file_path):
256
+ st.audio(file_path)
257
+ with open(file_path, "rb") as f:
258
+ b64 = base64.b64encode(f.read()).decode()
259
+ dl_link = f'<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}">🎡 Download {os.path.basename(file_path)}</a>'
260
+ st.markdown(dl_link, unsafe_allow_html=True)
261
+
262
+ # Image saver - pics preserved with naming! πŸ“ΈπŸ’Ύ
263
+ async def save_pasted_image(image_data, username):
264
+ await asyncio.to_thread(log_action, username, "πŸ“ΈπŸ’Ύ - Image saver - pics preserved!")
265
+ timestamp = format_timestamp_prefix()
266
+ filename = f"paste_{timestamp}_{username}.png"
267
+ filepath = os.path.join(MEDIA_DIR, filename)
268
+ if ',' in image_data:
269
+ image_data = image_data.split(',')[1]
270
+ img_bytes = base64.b64decode(image_data)
271
+ img = Image.open(io.BytesIO(img_bytes))
272
+ await asyncio.to_thread(img.save, filepath, "PNG")
273
+ return filepath
274
+
275
+ # Video renderer - movies roll with autoplay! πŸŽ₯🎬
276
+ async def get_video_html(video_path, width="100%"):
277
+ username = st.session_state.get('username', 'System 🌟')
278
+ await asyncio.to_thread(log_action, username, "πŸŽ₯🎬 - Video renderer - movies roll!")
279
+ with open(video_path, 'rb') as f:
280
+ video_data = await asyncio.to_thread(f.read)
281
+ video_url = f"data:video/mp4;base64,{base64.b64encode(video_data).decode()}"
282
+ return f'<video width="{width}" controls autoplay><source src="{video_url}" type="video/mp4">Your browser does not support the video tag.</video>'
283
+
284
+ # Audio renderer - sounds soar! 🎢✈️
285
+ async def get_audio_html(audio_path, width="100%"):
286
+ username = st.session_state.get('username', 'System 🌟')
287
+ await asyncio.to_thread(log_action, username, "🎢✈️ - Audio renderer - sounds soar!")
288
+ audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, audio_path, 'rb').read()).decode()}"
289
+ return f'<audio controls style="width: {width};"><source src="{audio_url}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
290
+
291
+ # Websocket handler - chat links up! πŸŒπŸ”—
292
+ async def websocket_handler(websocket, path):
293
+ username = st.session_state.get('username', 'System 🌟')
294
+ await asyncio.to_thread(log_action, username, "πŸŒπŸ”— - Websocket handler - chat links up!")
295
+ try:
296
+ client_id = str(uuid.uuid4())
297
+ room_id = "chat"
298
+ st.session_state.active_connections.setdefault(room_id, {})[client_id] = websocket
299
+ chat_content = await load_chat()
300
+ username = st.session_state.get('username', random.choice(list(FUN_USERNAMES.keys())))
301
+ if not await has_joined_before(client_id, chat_content):
302
+ await save_chat_entry(f"Client-{client_id}", f"{username} has joined {START_ROOM}!")
303
+ async for message in websocket:
304
+ parts = message.split('|', 1)
305
+ if len(parts) == 2:
306
+ username, content = parts
307
+ await save_chat_entry(username, content)
308
+ await broadcast_message(f"{username}|{content}", room_id)
309
+ except websockets.ConnectionClosed:
310
+ pass
311
+ finally:
312
+ if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
313
+ del st.session_state.active_connections[room_id][client_id]
314
+
315
+ # Message broadcaster - words fly far! πŸ“’βœˆοΈ
316
+ async def broadcast_message(message, room_id):
317
+ username = st.session_state.get('username', 'System 🌟')
318
+ await asyncio.to_thread(log_action, username, "πŸ“’βœˆοΈ - Message broadcaster - words fly far!")
319
+ if room_id in st.session_state.active_connections:
320
+ disconnected = []
321
+ for client_id, ws in st.session_state.active_connections[room_id].items():
322
+ try:
323
+ await ws.send(message)
324
+ except websockets.ConnectionClosed:
325
+ disconnected.append(client_id)
326
+ for client_id in disconnected:
327
+ del st.session_state.active_connections[room_id][client_id]
328
+
329
+ # Server starter - web spins up! πŸ–₯οΈπŸŒ€
330
+ async def run_websocket_server():
331
+ username = st.session_state.get('username', 'System 🌟')
332
+ await asyncio.to_thread(log_action, username, "πŸ–₯οΈπŸŒ€ - Server starter - web spins up!")
333
+ if not st.session_state.server_running:
334
+ server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
335
+ st.session_state.server_running = True
336
+ await server.wait_closed()
337
+
338
+ # Voice processor - speech to text! πŸŽ€πŸ“
339
+ async def process_voice_input(audio_bytes):
340
+ username = st.session_state.get('username', 'System 🌟')
341
+ await asyncio.to_thread(log_action, username, "πŸŽ€πŸ“ - Voice processor - speech to text!")
342
+ if audio_bytes:
343
+ text = "Voice input simulation"
344
+ await save_chat_entry(username, text)
345
+
346
+ # Interface builder - UI takes shape! πŸŽ¨πŸ–ŒοΈ
347
+ def create_streamlit_interface():
348
+ loop = asyncio.new_event_loop()
349
+ asyncio.set_event_loop(loop)
350
+
351
+ async def async_interface():
352
+ if 'username' not in st.session_state:
353
+ chat_content = await load_chat()
354
+ available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
355
+ st.session_state.username = random.choice(available_names) if available_names else random.choice(list(FUN_USERNAMES.keys()))
356
+
357
+ if 'refresh_rate' not in st.session_state:
358
+ st.session_state.refresh_rate = 5
359
+ if 'timer_start' not in st.session_state:
360
+ st.session_state.timer_start = time.time()
361
+ if 'quote_line' not in st.session_state:
362
+ st.session_state.quote_line = None
363
+ if 'pasted_image_data' not in st.session_state:
364
+ st.session_state.pasted_image_data = None
365
+ if 'message_text' not in st.session_state:
366
+ st.session_state.message_text = ""
367
+ if 'audio_cache' not in st.session_state:
368
+ st.session_state.audio_cache = {}
369
+ if 'chat_history' not in st.session_state:
370
+ st.session_state.chat_history = []
371
+
372
+ st.markdown("""
373
+ <style>
374
+ .chat-box {font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 10px; border-radius: 5px; height: 300px; overflow-y: auto;}
375
+ .timer {font-size: 24px; color: #ffcc00; text-align: center; animation: pulse 1s infinite;}
376
+ @keyframes pulse {0% {transform: scale(1);} 50% {transform: scale(1.1);} 100% {transform: scale(1);}}
377
+ #paste-target {border: 2px dashed #ccc; padding: 20px; text-align: center; cursor: pointer;}
378
+ </style>
379
+ """, unsafe_allow_html=True)
380
+
381
+ st.title(f"πŸ€–πŸ§ MMO {st.session_state.username}πŸ“πŸ”¬")
382
+ st.markdown(f"Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting