Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -5,21 +5,13 @@ import uuid
|
|
5 |
from datetime import datetime
|
6 |
import os
|
7 |
import random
|
8 |
-
import time
|
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 |
-
import pytz
|
20 |
from gradio_client import Client
|
21 |
from streamlit_marquee import streamlit_marquee
|
22 |
-
from collections import defaultdict, Counter
|
23 |
|
24 |
# Patch asyncio for nesting
|
25 |
nest_asyncio.apply()
|
@@ -27,83 +19,59 @@ nest_asyncio.apply()
|
|
27 |
# Page Config
|
28 |
st.set_page_config(
|
29 |
layout="wide",
|
30 |
-
page_title="
|
31 |
page_icon="๐ฆ"
|
32 |
)
|
33 |
|
34 |
-
#
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
"Trailblazer Tim ๐": "en-US-GuyNeural",
|
39 |
-
"Meme Queen Mia ๐": "en-US-JennyNeural",
|
40 |
-
"Elk Whisperer Eve ๐ฆ": "en-GB-SoniaNeural",
|
41 |
-
"Tech Titan Tara ๐พ": "en-AU-NatashaNeural",
|
42 |
-
"Ski Guru Sam โท๏ธ": "en-CA-ClaraNeural",
|
43 |
-
"Cosmic Camper Cal ๐ ": "en-US-AriaNeural",
|
44 |
-
"Rasta Ranger Rick ๐": "en-GB-RyanNeural",
|
45 |
-
"Boulder Bro Ben ๐ชจ": "en-AU-WilliamNeural"
|
46 |
}
|
47 |
-
|
48 |
-
FILE_EMOJIS = {"md": "๐", "mp3": "๐ต", "png": "๐ผ๏ธ", "mp4": "๐ฅ", "zip": "๐ฆ"}
|
49 |
|
50 |
# Directories
|
51 |
-
for d in ["chat_logs", "audio_logs"
|
52 |
os.makedirs(d, exist_ok=True)
|
53 |
|
54 |
CHAT_DIR = "chat_logs"
|
55 |
-
AUDIO_CACHE_DIR = "audio_cache"
|
56 |
AUDIO_DIR = "audio_logs"
|
57 |
STATE_FILE = "user_state.txt"
|
58 |
-
CHAT_FILE = os.path.join(CHAT_DIR, "
|
59 |
-
|
60 |
-
# Timestamp Helper
|
61 |
-
def format_timestamp_prefix(username=""):
|
62 |
-
central = pytz.timezone('US/Central')
|
63 |
-
now = datetime.now(central)
|
64 |
-
return f"{now.strftime('%Y%m%d_%H%M%S')}-by-{username}"
|
65 |
|
66 |
# Session State Init
|
67 |
def init_session_state():
|
68 |
defaults = {
|
69 |
'server_running': False, 'server_task': None, 'active_connections': {},
|
70 |
-
'
|
71 |
-
'
|
72 |
-
'
|
73 |
-
"background": "#
|
74 |
-
"animationDuration": "
|
75 |
-
}
|
76 |
-
'mp3_files': {}, 'timer_start': time.time(), 'auto_refresh': True, 'refresh_rate': 10
|
77 |
}
|
78 |
for k, v in defaults.items():
|
79 |
if k not in st.session_state:
|
80 |
st.session_state[k] = v
|
81 |
|
82 |
-
#
|
83 |
-
def
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
st.session_state['marquee_settings']['background'] = st.color_picker("๐ Background", "#1E1E1E")
|
88 |
-
st.session_state['marquee_settings']['color'] = st.color_picker("โ๏ธ Text", "#FFFFFF")
|
89 |
-
with cols[1]:
|
90 |
-
st.session_state['marquee_settings']['font-size'] = f"{st.slider('๐ Size', 10, 24, 14)}px"
|
91 |
-
st.session_state['marquee_settings']['animationDuration'] = f"{st.slider('โฑ๏ธ Speed', 1, 20, 20)}s"
|
92 |
-
|
93 |
-
def display_marquee(text, settings, key_suffix=""):
|
94 |
-
truncated = text[:280] + "..." if len(text) > 280 else text
|
95 |
-
streamlit_marquee(content=truncated, **settings, key=f"marquee_{key_suffix}")
|
96 |
-
st.write("")
|
97 |
-
|
98 |
-
# Text & File Helpers
|
99 |
def clean_text_for_tts(text):
|
100 |
return re.sub(r'[#*!\[\]]+', '', ' '.join(text.split()))[:200] or "No text"
|
101 |
|
102 |
-
def clean_text_for_filename(text):
|
103 |
-
return '_'.join(re.sub(r'[^\w\s-]', '', text.lower()).split())[:50]
|
104 |
-
|
105 |
def generate_filename(prompt, username, file_type="md"):
|
106 |
-
timestamp =
|
107 |
hash_val = hashlib.md5(prompt.encode()).hexdigest()[:8]
|
108 |
return f"{timestamp}-{hash_val}.{file_type}"
|
109 |
|
@@ -116,8 +84,8 @@ def create_file(prompt, username, file_type="md"):
|
|
116 |
def get_download_link(file, file_type="mp3"):
|
117 |
with open(file, "rb") as f:
|
118 |
b64 = base64.b64encode(f.read()).decode()
|
119 |
-
mime_types = {"mp3": "audio/mpeg", "
|
120 |
-
return f'<a href="data:{mime_types.get(file_type, "application/octet-stream")};base64,{b64}" download="{os.path.basename(file)}">{FILE_EMOJIS.get(file_type, "๐ฅ")}
|
121 |
|
122 |
def save_username(username):
|
123 |
with open(STATE_FILE, 'w') as f:
|
@@ -129,20 +97,13 @@ def load_username():
|
|
129 |
return f.read().strip()
|
130 |
return None
|
131 |
|
132 |
-
def load_mp3_viewer():
|
133 |
-
mp3_files = sorted(glob.glob("*.mp3"), key=os.path.getmtime)
|
134 |
-
for i, mp3 in enumerate(mp3_files, 1):
|
135 |
-
filename = os.path.basename(mp3)
|
136 |
-
if filename not in st.session_state['mp3_files']:
|
137 |
-
st.session_state['mp3_files'][filename] = (i, mp3)
|
138 |
-
|
139 |
# Audio Processing
|
140 |
async def async_edge_tts_generate(text, voice, username):
|
141 |
cache_key = f"{text[:100]}_{voice}"
|
142 |
if cache_key in st.session_state['audio_cache']:
|
143 |
return st.session_state['audio_cache'][cache_key]
|
144 |
text = clean_text_for_tts(text)
|
145 |
-
filename = f"{
|
146 |
communicate = edge_tts.Communicate(text, voice)
|
147 |
await communicate.save(filename)
|
148 |
if os.path.exists(filename) and os.path.getsize(filename) > 0:
|
@@ -155,60 +116,60 @@ def play_and_download_audio(file_path):
|
|
155 |
st.audio(file_path)
|
156 |
st.markdown(get_download_link(file_path), unsafe_allow_html=True)
|
157 |
|
|
|
158 |
async def save_chat_entry(username, message, voice, is_markdown=False):
|
159 |
if not message.strip() or message == st.session_state.last_transcript:
|
160 |
return None, None
|
161 |
-
|
162 |
-
|
163 |
-
entry = f"[{timestamp}] {username} ({voice}): {message}" if not is_markdown else f"[{timestamp}] {username} ({voice}):\n```markdown\n{message}\n```"
|
164 |
md_file = create_file(entry, username, "md")
|
165 |
with open(CHAT_FILE, 'a') as f:
|
166 |
f.write(f"{entry}\n")
|
167 |
audio_file = await async_edge_tts_generate(message, voice, username)
|
168 |
-
await broadcast_message(f"{username}|{message}", "
|
169 |
-
st.session_state.last_chat_update = time.time()
|
170 |
st.session_state.chat_history.append(entry)
|
171 |
st.session_state.last_transcript = message
|
|
|
|
|
172 |
return md_file, audio_file
|
173 |
|
174 |
async def load_chat():
|
175 |
if not os.path.exists(CHAT_FILE):
|
176 |
with open(CHAT_FILE, 'a') as f:
|
177 |
-
f.write(f"# {
|
178 |
with open(CHAT_FILE, 'r') as f:
|
179 |
content = f.read().strip()
|
180 |
-
|
181 |
-
return list(dict.fromkeys(line for line in lines if line.strip()))
|
182 |
|
183 |
-
# ArXiv
|
184 |
async def perform_arxiv_search(query, username):
|
185 |
gradio_client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
|
186 |
refs = gradio_client.predict(
|
187 |
-
query,
|
188 |
)[0]
|
189 |
-
result = f"
|
190 |
-
voice =
|
191 |
md_file, audio_file = await save_chat_entry(username, result, voice, True)
|
192 |
return md_file, audio_file
|
193 |
|
194 |
-
# WebSocket
|
195 |
async def websocket_handler(websocket, path):
|
196 |
client_id = str(uuid.uuid4())
|
197 |
-
room_id = "
|
198 |
if room_id not in st.session_state.active_connections:
|
199 |
st.session_state.active_connections[room_id] = {}
|
200 |
st.session_state.active_connections[room_id][client_id] = websocket
|
201 |
-
username = st.session_state.get('username', random.choice(list(
|
202 |
-
await save_chat_entry(
|
203 |
try:
|
204 |
async for message in websocket:
|
205 |
if '|' in message:
|
206 |
username, content = message.split('|', 1)
|
207 |
-
voice =
|
208 |
await save_chat_entry(username, content, voice)
|
209 |
-
await perform_arxiv_search(content, username) # ArXiv response for every chat
|
210 |
except websockets.ConnectionClosed:
|
211 |
-
await save_chat_entry(
|
212 |
finally:
|
213 |
if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
|
214 |
del st.session_state.active_connections[room_id][client_id]
|
@@ -236,116 +197,107 @@ def start_websocket_server():
|
|
236 |
asyncio.set_event_loop(loop)
|
237 |
loop.run_until_complete(run_websocket_server())
|
238 |
|
239 |
-
#
|
240 |
-
def
|
|
|
|
|
241 |
story = f"""
|
242 |
-
๐
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
๐ต {username} looped โ{inputs['song']}โ {inputs['number']} times,
|
249 |
-
๐ผ deciding to {inputs['verb_present']} as a {inputs['occupation']}
|
250 |
-
๐จ in a {inputs['color']} {inputs['noun']}โall ending with a {inputs['sound']}!
|
251 |
"""
|
252 |
return story.strip()
|
253 |
|
254 |
-
# Main
|
255 |
def main():
|
256 |
init_session_state()
|
257 |
-
load_mp3_viewer()
|
258 |
saved_username = load_username()
|
259 |
-
if saved_username and saved_username in
|
260 |
st.session_state.username = saved_username
|
261 |
if not st.session_state.username:
|
262 |
-
st.session_state.username = random.choice(list(
|
263 |
-
st.session_state.
|
264 |
-
asyncio.run(save_chat_entry("System ๐", f"{st.session_state.username} has joined {START_ROOM}!", "en-US-AriaNeural"))
|
265 |
save_username(st.session_state.username)
|
266 |
|
267 |
-
st.title(f"
|
268 |
-
st.subheader("
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
|
273 |
-
tab_main = st.radio("
|
274 |
-
st.checkbox("๐ Autosend Chat", key="autosend")
|
275 |
|
276 |
-
if tab_main == "
|
277 |
-
st.subheader(f"
|
278 |
chat_content = asyncio.run(load_chat())
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
elif tab_main == "๐ต Audio Gallery":
|
295 |
-
st.subheader("๐ต Audio Gallery ๐ง")
|
296 |
-
mp3_files = sorted(glob.glob("*.mp3"), key=os.path.getmtime)
|
297 |
if mp3_files:
|
298 |
-
|
299 |
-
|
|
|
300 |
play_and_download_audio(mp3)
|
301 |
else:
|
302 |
-
st.write("
|
303 |
|
304 |
-
elif tab_main == "
|
305 |
-
st.subheader("
|
306 |
-
|
307 |
inputs = {}
|
|
|
308 |
with col1:
|
309 |
-
inputs['
|
310 |
-
inputs['
|
311 |
-
inputs['
|
312 |
-
inputs['
|
313 |
-
inputs['object'] = st.text_input("๐ ๏ธ Random Object", placeholder="e.g., 'toaster'", key="object")
|
314 |
-
inputs['emotion'] = st.text_input("โค๏ธ Emotion", placeholder="e.g., 'salty'", key="emotion")
|
315 |
-
inputs['animal'] = st.text_input("๐ฆ Animal", placeholder="e.g., 'elk'", key="animal")
|
316 |
-
inputs['verb_past'] = st.text_input("โฎ๏ธ Verb (past tense)", placeholder="e.g., 'yeeted'", key="verb_past")
|
317 |
-
inputs['place'] = st.text_input("๐ Place", placeholder="e.g., 'Rockies'", key="place")
|
318 |
-
inputs['exclamation'] = st.text_input("โ Exclamation", placeholder="e.g., 'Bruh!'", key="exclamation")
|
319 |
with col2:
|
320 |
-
inputs['
|
321 |
-
inputs['
|
322 |
-
inputs['
|
323 |
-
inputs['
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
inputs['noun'] = st.text_input("๐ Noun", placeholder="e.g., 'chaos'", key="noun")
|
329 |
-
inputs['sound'] = st.text_input("๐ Silly Sound Effect", placeholder="e.g., 'Boop!'", key="sound")
|
330 |
-
|
331 |
-
if st.button("๐ Generate Sim Story! ๐ค"):
|
332 |
-
story = generate_mad_libs_story(st.session_state.username, inputs)
|
333 |
-
st.markdown("### ๐ Your Colorado Sim Adventure ๐๏ธ")
|
334 |
st.write(story)
|
335 |
-
voice =
|
336 |
md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, story, voice, True))
|
337 |
if audio_file:
|
338 |
play_and_download_audio(audio_file)
|
|
|
|
|
339 |
|
340 |
-
# Sidebar
|
341 |
-
st.sidebar.subheader("
|
342 |
-
new_username = st.sidebar.selectbox("
|
343 |
if new_username != st.session_state.username:
|
344 |
-
asyncio.run(save_chat_entry(
|
345 |
st.session_state.username = new_username
|
346 |
-
st.session_state.tts_voice = FUN_USERNAMES[new_username]
|
347 |
save_username(st.session_state.username)
|
348 |
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
349 |
|
350 |
-
|
351 |
-
|
|
|
5 |
from datetime import datetime
|
6 |
import os
|
7 |
import random
|
|
|
8 |
import hashlib
|
|
|
9 |
import glob
|
10 |
import base64
|
|
|
|
|
11 |
import edge_tts
|
|
|
12 |
import nest_asyncio
|
|
|
|
|
13 |
from gradio_client import Client
|
14 |
from streamlit_marquee import streamlit_marquee
|
|
|
15 |
|
16 |
# Patch asyncio for nesting
|
17 |
nest_asyncio.apply()
|
|
|
19 |
# Page Config
|
20 |
st.set_page_config(
|
21 |
layout="wide",
|
22 |
+
page_title="Rocky Mountain Quest ๐๏ธ๐ฎ",
|
23 |
page_icon="๐ฆ"
|
24 |
)
|
25 |
|
26 |
+
# Game Config
|
27 |
+
GAME_NAME = "Rocky Mountain Quest ๐๏ธ๐ฎ"
|
28 |
+
START_LOCATION = "Trailhead Camp โบ"
|
29 |
+
CHARACTERS = {
|
30 |
+
"Trailblazer Tim ๐": {"voice": "en-US-GuyNeural", "desc": "Fearless hiker seeking epic trails!"},
|
31 |
+
"Meme Queen Mia ๐": {"voice": "en-US-JennyNeural", "desc": "Spreads laughs with wild memes!"},
|
32 |
+
"Elk Whisperer Eve ๐ฆ": {"voice": "en-GB-SoniaNeural", "desc": "Talks to wildlife, loves nature!"},
|
33 |
+
"Tech Titan Tara ๐พ": {"voice": "en-AU-NatashaNeural", "desc": "Codes her way through the Rockies!"},
|
34 |
+
"Ski Guru Sam โท๏ธ": {"voice": "en-CA-ClaraNeural", "desc": "Shreds slopes, lives for snow!"},
|
35 |
+
"Cosmic Camper Cal ๐ ": {"voice": "en-US-AriaNeural", "desc": "Stargazes and tells epic tales!"},
|
36 |
+
"Rasta Ranger Rick ๐": {"voice": "en-GB-RyanNeural", "desc": "Chills with natureโs vibes!"},
|
37 |
+
"Boulder Bro Ben ๐ชจ": {"voice": "en-AU-WilliamNeural", "desc": "Climbs rocks, bro-style!"}
|
38 |
}
|
39 |
+
FILE_EMOJIS = {"md": "๐", "mp3": "๐ต"}
|
|
|
40 |
|
41 |
# Directories
|
42 |
+
for d in ["chat_logs", "audio_logs"]:
|
43 |
os.makedirs(d, exist_ok=True)
|
44 |
|
45 |
CHAT_DIR = "chat_logs"
|
|
|
46 |
AUDIO_DIR = "audio_logs"
|
47 |
STATE_FILE = "user_state.txt"
|
48 |
+
CHAT_FILE = os.path.join(CHAT_DIR, "quest_log.md")
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
# Session State Init
|
51 |
def init_session_state():
|
52 |
defaults = {
|
53 |
'server_running': False, 'server_task': None, 'active_connections': {},
|
54 |
+
'chat_history': [], 'audio_cache': {}, 'last_transcript': "",
|
55 |
+
'username': None, 'score': 0, 'treasures': 0, 'location': START_LOCATION,
|
56 |
+
'marquee_settings': {
|
57 |
+
"background": "#2E8B57", "color": "#FFFFFF", "font-size": "16px",
|
58 |
+
"animationDuration": "15s", "width": "100%", "lineHeight": "40px"
|
59 |
+
}
|
|
|
60 |
}
|
61 |
for k, v in defaults.items():
|
62 |
if k not in st.session_state:
|
63 |
st.session_state[k] = v
|
64 |
|
65 |
+
# Helpers
|
66 |
+
def format_timestamp(username=""):
|
67 |
+
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
68 |
+
return f"{now}-by-{username}"
|
69 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
def clean_text_for_tts(text):
|
71 |
return re.sub(r'[#*!\[\]]+', '', ' '.join(text.split()))[:200] or "No text"
|
72 |
|
|
|
|
|
|
|
73 |
def generate_filename(prompt, username, file_type="md"):
|
74 |
+
timestamp = format_timestamp(username)
|
75 |
hash_val = hashlib.md5(prompt.encode()).hexdigest()[:8]
|
76 |
return f"{timestamp}-{hash_val}.{file_type}"
|
77 |
|
|
|
84 |
def get_download_link(file, file_type="mp3"):
|
85 |
with open(file, "rb") as f:
|
86 |
b64 = base64.b64encode(f.read()).decode()
|
87 |
+
mime_types = {"mp3": "audio/mpeg", "md": "text/markdown"}
|
88 |
+
return f'<a href="data:{mime_types.get(file_type, "application/octet-stream")};base64,{b64}" download="{os.path.basename(file)}">{FILE_EMOJIS.get(file_type, "๐ฅ")} {os.path.basename(file)}</a>'
|
89 |
|
90 |
def save_username(username):
|
91 |
with open(STATE_FILE, 'w') as f:
|
|
|
97 |
return f.read().strip()
|
98 |
return None
|
99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
# Audio Processing
|
101 |
async def async_edge_tts_generate(text, voice, username):
|
102 |
cache_key = f"{text[:100]}_{voice}"
|
103 |
if cache_key in st.session_state['audio_cache']:
|
104 |
return st.session_state['audio_cache'][cache_key]
|
105 |
text = clean_text_for_tts(text)
|
106 |
+
filename = f"{format_timestamp(username)}-{hashlib.md5(text.encode()).hexdigest()[:8]}.mp3"
|
107 |
communicate = edge_tts.Communicate(text, voice)
|
108 |
await communicate.save(filename)
|
109 |
if os.path.exists(filename) and os.path.getsize(filename) > 0:
|
|
|
116 |
st.audio(file_path)
|
117 |
st.markdown(get_download_link(file_path), unsafe_allow_html=True)
|
118 |
|
119 |
+
# Chat and Quest Log
|
120 |
async def save_chat_entry(username, message, voice, is_markdown=False):
|
121 |
if not message.strip() or message == st.session_state.last_transcript:
|
122 |
return None, None
|
123 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
124 |
+
entry = f"[{timestamp}] {username}: {message}" if not is_markdown else f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
|
|
|
125 |
md_file = create_file(entry, username, "md")
|
126 |
with open(CHAT_FILE, 'a') as f:
|
127 |
f.write(f"{entry}\n")
|
128 |
audio_file = await async_edge_tts_generate(message, voice, username)
|
129 |
+
await broadcast_message(f"{username}|{message}", "quest")
|
|
|
130 |
st.session_state.chat_history.append(entry)
|
131 |
st.session_state.last_transcript = message
|
132 |
+
st.session_state.score += 10 # Points for participation
|
133 |
+
st.session_state.treasures += 1 # Audio treasure collected
|
134 |
return md_file, audio_file
|
135 |
|
136 |
async def load_chat():
|
137 |
if not os.path.exists(CHAT_FILE):
|
138 |
with open(CHAT_FILE, 'a') as f:
|
139 |
+
f.write(f"# {GAME_NAME} Log\n\nThe adventure begins at {START_LOCATION}! ๐๏ธ\n")
|
140 |
with open(CHAT_FILE, 'r') as f:
|
141 |
content = f.read().strip()
|
142 |
+
return content.split('\n')
|
|
|
143 |
|
144 |
+
# ArXiv Integration
|
145 |
async def perform_arxiv_search(query, username):
|
146 |
gradio_client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
|
147 |
refs = gradio_client.predict(
|
148 |
+
query, 5, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md"
|
149 |
)[0]
|
150 |
+
result = f"๐ Ancient Rocky Knowledge:\n{refs}"
|
151 |
+
voice = CHARACTERS[username]["voice"]
|
152 |
md_file, audio_file = await save_chat_entry(username, result, voice, True)
|
153 |
return md_file, audio_file
|
154 |
|
155 |
+
# WebSocket for Multiplayer
|
156 |
async def websocket_handler(websocket, path):
|
157 |
client_id = str(uuid.uuid4())
|
158 |
+
room_id = "quest"
|
159 |
if room_id not in st.session_state.active_connections:
|
160 |
st.session_state.active_connections[room_id] = {}
|
161 |
st.session_state.active_connections[room_id][client_id] = websocket
|
162 |
+
username = st.session_state.get('username', random.choice(list(CHARACTERS.keys())))
|
163 |
+
await save_chat_entry(username, f"๐บ๏ธ Joins the quest at {START_LOCATION}!", CHARACTERS[username]["voice"])
|
164 |
try:
|
165 |
async for message in websocket:
|
166 |
if '|' in message:
|
167 |
username, content = message.split('|', 1)
|
168 |
+
voice = CHARACTERS.get(username, {"voice": "en-US-AriaNeural"})["voice"]
|
169 |
await save_chat_entry(username, content, voice)
|
170 |
+
await perform_arxiv_search(content, username) # ArXiv response for every chat
|
171 |
except websockets.ConnectionClosed:
|
172 |
+
await save_chat_entry(username, "๐ Leaves the quest!", CHARACTERS[username]["voice"])
|
173 |
finally:
|
174 |
if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
|
175 |
del st.session_state.active_connections[room_id][client_id]
|
|
|
197 |
asyncio.set_event_loop(loop)
|
198 |
loop.run_until_complete(run_websocket_server())
|
199 |
|
200 |
+
# Game Quest (Mad Libs)
|
201 |
+
def generate_quest_story(username, inputs):
|
202 |
+
locations = ["Peak Summit ๐๏ธ", "Elk Valley ๐ฒ", "Meme Cave ๐ณ๏ธ", "Tech Outpost ๐ป"]
|
203 |
+
st.session_state.location = random.choice(locations)
|
204 |
story = f"""
|
205 |
+
๐ **{username}โs Quest at {st.session_state.location}:**
|
206 |
+
๐บ๏ธ {username} discovers {inputs['quantity']} {inputs['plural_noun']}!
|
207 |
+
๐ Theyโre {inputs['adjective']} and {inputs['action']} everywhere.
|
208 |
+
๐ฏ With a {inputs['tool']}, {username} faces a {inputs['creature']}
|
209 |
+
โก that {inputs['event']}โshouting โ{inputs['shout']}!โ
|
210 |
+
๐ Victory earns {username} {st.session_state.treasures} audio treasures!
|
|
|
|
|
|
|
211 |
"""
|
212 |
return story.strip()
|
213 |
|
214 |
+
# Main Game Loop
|
215 |
def main():
|
216 |
init_session_state()
|
|
|
217 |
saved_username = load_username()
|
218 |
+
if saved_username and saved_username in CHARACTERS:
|
219 |
st.session_state.username = saved_username
|
220 |
if not st.session_state.username:
|
221 |
+
st.session_state.username = random.choice(list(CHARACTERS.keys()))
|
222 |
+
asyncio.run(save_chat_entry(st.session_state.username, "๐บ๏ธ Begins the Rocky Mountain Quest!", CHARACTERS[st.session_state.username]["voice"]))
|
|
|
223 |
save_username(st.session_state.username)
|
224 |
|
225 |
+
st.title(f"๐ฎ {GAME_NAME}")
|
226 |
+
st.subheader(f"๐ {st.session_state.username}โs Adventure - Score: {st.session_state.score} ๐")
|
227 |
+
chat_text = " ".join([line.split(": ")[-1] for line in asyncio.run(load_chat()) if ": " in line][-5:])
|
228 |
+
streamlit_marquee(content=f"๐๏ธ {st.session_state.location} | ๐๏ธ {st.session_state.username} | ๐ฌ {chat_text}",
|
229 |
+
**st.session_state['marquee_settings'], key="quest_marquee")
|
230 |
|
231 |
+
tab_main = st.radio("๐ฒ Quest Actions:", ["๐ฃ๏ธ Explore & Chat", "๐ต Treasure Vault", "๐บ๏ธ Quest Challenge"], horizontal=True)
|
|
|
232 |
|
233 |
+
if tab_main == "๐ฃ๏ธ Explore & Chat":
|
234 |
+
st.subheader(f"๐ฃ๏ธ Explore {st.session_state.location} ๐๏ธ")
|
235 |
chat_content = asyncio.run(load_chat())
|
236 |
+
st.text_area("๐ Quest Log", "\n".join(chat_content[-10:]), height=200, disabled=True)
|
237 |
+
|
238 |
+
message = st.text_input(f"๐จ๏ธ {st.session_state.username} says:", placeholder="Explore the wilds! ๐ฒ")
|
239 |
+
if st.button("๐ Send & Explore ๐ค"):
|
240 |
+
if message:
|
241 |
+
voice = CHARACTERS[st.session_state.username]["voice"]
|
242 |
+
md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, message, voice))
|
243 |
+
if audio_file:
|
244 |
+
play_and_download_audio(audio_file)
|
245 |
+
st.success(f"๐ +10 points! New Score: {st.session_state.score}")
|
246 |
+
|
247 |
+
elif tab_main == "๐ต Treasure Vault":
|
248 |
+
st.subheader("๐ต Audio Treasure Vault ๐")
|
249 |
+
mp3_files = sorted(glob.glob("*.mp3"), key=os.path.getmtime, reverse=True)
|
|
|
|
|
|
|
|
|
250 |
if mp3_files:
|
251 |
+
st.write(f"๐
Treasures Collected: {st.session_state.treasures}")
|
252 |
+
for i, mp3 in enumerate(mp3_files[:10]):
|
253 |
+
with st.expander(f"๐ต Treasure #{i+1}: {os.path.basename(mp3)}"):
|
254 |
play_and_download_audio(mp3)
|
255 |
else:
|
256 |
+
st.write("๐ No treasures yetโexplore or complete quests to collect audio loot! ๐ค")
|
257 |
|
258 |
+
elif tab_main == "๐บ๏ธ Quest Challenge":
|
259 |
+
st.subheader("๐บ๏ธ Rocky Mountain Quest Challenge ๐")
|
260 |
+
st.write("Fill in the blanks to embark on a wild adventure!")
|
261 |
inputs = {}
|
262 |
+
col1, col2 = st.columns(2)
|
263 |
with col1:
|
264 |
+
inputs['quantity'] = st.text_input("๐ข How Many?", "3", key="quantity")
|
265 |
+
inputs['plural_noun'] = st.text_input("๐ Things?", "elk", key="plural_noun")
|
266 |
+
inputs['adjective'] = st.text_input("โจ Describe Them?", "wild", key="adjective")
|
267 |
+
inputs['action'] = st.text_input("๐ What They Do?", "running", key="action")
|
|
|
|
|
|
|
|
|
|
|
|
|
268 |
with col2:
|
269 |
+
inputs['tool'] = st.text_input("๐ ๏ธ Your Tool?", "map", key="tool")
|
270 |
+
inputs['creature'] = st.text_input("๐ฆ Encounter?", "bear", key="creature")
|
271 |
+
inputs['event'] = st.text_input("โก What Happens?", "roars", key="event")
|
272 |
+
inputs['shout'] = st.text_input("๐ฃ๏ธ Your Cry?", "Yeehaw!", key="shout")
|
273 |
+
|
274 |
+
if st.button("๐ Start Quest! ๐ค"):
|
275 |
+
story = generate_quest_story(st.session_state.username, inputs)
|
276 |
+
st.markdown(f"### ๐ {st.session_state.username}โs Quest Log")
|
|
|
|
|
|
|
|
|
|
|
|
|
277 |
st.write(story)
|
278 |
+
voice = CHARACTERS[st.session_state.username]["voice"]
|
279 |
md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, story, voice, True))
|
280 |
if audio_file:
|
281 |
play_and_download_audio(audio_file)
|
282 |
+
st.session_state.score += 50 # Bonus for quest completion
|
283 |
+
st.success(f"๐ Quest Complete! +50 points! New Score: {st.session_state.score}")
|
284 |
|
285 |
+
# Sidebar: Game HUD
|
286 |
+
st.sidebar.subheader("๐ฎ Adventurerโs HUD")
|
287 |
+
new_username = st.sidebar.selectbox("๐งโโ๏ธ Choose Your Hero", list(CHARACTERS.keys()), index=list(CHARACTERS.keys()).index(st.session_state.username))
|
288 |
if new_username != st.session_state.username:
|
289 |
+
asyncio.run(save_chat_entry(st.session_state.username, f"๐ Transforms into {new_username}!", CHARACTERS[st.session_state.username]["voice"]))
|
290 |
st.session_state.username = new_username
|
|
|
291 |
save_username(st.session_state.username)
|
292 |
st.rerun()
|
293 |
+
st.sidebar.write(f"๐ {CHARACTERS[st.session_state.username]['desc']}")
|
294 |
+
st.sidebar.write(f"๐ Location: {st.session_state.location}")
|
295 |
+
st.sidebar.write(f"๐
Score: {st.session_state.score}")
|
296 |
+
st.sidebar.write(f"๐ต Treasures: {st.session_state.treasures}")
|
297 |
+
|
298 |
+
if not st.session_state.get('server_running', False):
|
299 |
+
st.session_state.server_task = threading.Thread(target=start_websocket_server, daemon=True)
|
300 |
+
st.session_state.server_task.start()
|
301 |
|
302 |
+
if __name__ == "__main__":
|
303 |
+
main()
|