Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -17,6 +17,7 @@ from datetime import datetime
|
|
17 |
from PyPDF2 import PdfReader
|
18 |
import threading
|
19 |
from PIL import Image
|
|
|
20 |
|
21 |
# ==============================================================================
|
22 |
# Configuration & Constants
|
@@ -148,19 +149,26 @@ def update_player_state(username, position=None):
|
|
148 |
players[username]["last_action_timestamp"] = time.time()
|
149 |
state["players"] = players
|
150 |
write_history_file(state)
|
|
|
151 |
|
152 |
-
def add_object_to_state(obj_data):
|
153 |
state = read_history_file()
|
154 |
state = prune_inactive_players(state)
|
155 |
state["objects"][obj_data["obj_id"]] = obj_data
|
156 |
write_history_file(state)
|
|
|
|
|
157 |
|
158 |
-
def remove_object_from_state(obj_id):
|
159 |
state = read_history_file()
|
160 |
state = prune_inactive_players(state)
|
161 |
if obj_id in state["objects"]:
|
|
|
162 |
del state["objects"][obj_id]
|
163 |
write_history_file(state)
|
|
|
|
|
|
|
164 |
|
165 |
def log_action(username, action_type, data):
|
166 |
timestamp = get_current_time_str()
|
@@ -184,6 +192,41 @@ def log_action(username, action_type, data):
|
|
184 |
except Exception as e:
|
185 |
print(f"Error writing to player history log {player_log_file}: {e}")
|
186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
# ==============================================================================
|
188 |
# User State & Session Init
|
189 |
# ==============================================================================
|
@@ -206,7 +249,7 @@ def load_username():
|
|
206 |
|
207 |
def init_session_state():
|
208 |
defaults = {
|
209 |
-
'
|
210 |
'audio_cache': {},
|
211 |
'tts_voice': "en-US-AriaNeural",
|
212 |
'chat_history': [],
|
@@ -217,7 +260,8 @@ def init_session_state():
|
|
217 |
'last_message': "",
|
218 |
'selected_object': 'None',
|
219 |
'paste_image_base64': "",
|
220 |
-
'new_world_name': "MyWorld"
|
|
|
221 |
}
|
222 |
for k, v in defaults.items():
|
223 |
if k not in st.session_state:
|
@@ -332,6 +376,7 @@ async def save_chat_entry(username, message, voice, is_markdown=False):
|
|
332 |
if st.session_state.get('enable_audio', True):
|
333 |
tts_message = message
|
334 |
audio_file = await async_edge_tts_generate(tts_message, voice, username)
|
|
|
335 |
return md_file, audio_file
|
336 |
|
337 |
async def load_chat_history():
|
@@ -613,12 +658,14 @@ def render_sidebar():
|
|
613 |
if st.session_state.selected_object != name:
|
614 |
st.session_state.selected_object = name
|
615 |
update_player_state(st.session_state.username)
|
|
|
616 |
col_idx += 1
|
617 |
st.markdown("---")
|
618 |
if st.button("🚫 Clear Tool", key="clear_tool", use_container_width=True):
|
619 |
if st.session_state.selected_object != 'None':
|
620 |
st.session_state.selected_object = 'None'
|
621 |
update_player_state(st.session_state.username)
|
|
|
622 |
|
623 |
st.markdown("---")
|
624 |
st.header("🗣️ Voice & User")
|
@@ -650,8 +697,9 @@ def render_main_content():
|
|
650 |
|
651 |
with tab_world:
|
652 |
st.header("Shared 3D World")
|
653 |
-
st.caption("Click to place objects with the selected tool. Right-click to delete. State is saved in history.json.")
|
654 |
state = read_history_file()
|
|
|
655 |
html_file_path = 'index.html'
|
656 |
try:
|
657 |
with open(html_file_path, 'r', encoding='utf-8') as f:
|
@@ -666,6 +714,7 @@ def render_main_content():
|
|
666 |
</script>"""
|
667 |
html_content_with_state = html_template.replace('</head>', js_injection_script + '\n</head>', 1)
|
668 |
components.html(html_content_with_state, height=700, scrolling=False)
|
|
|
669 |
except FileNotFoundError:
|
670 |
st.error(f"CRITICAL ERROR: Could not find '{html_file_path}'.")
|
671 |
except Exception as e:
|
@@ -682,7 +731,7 @@ def render_main_content():
|
|
682 |
else:
|
683 |
st.caption("No chat messages yet.")
|
684 |
|
685 |
-
message_value = st.text_input("Your Message:", key="
|
686 |
send_button_clicked = st.button("Send Chat 💬", key="send_chat_button")
|
687 |
should_autosend = st.session_state.get('autosend', False) and message_value
|
688 |
|
@@ -693,7 +742,7 @@ def render_main_content():
|
|
693 |
voice = FUN_USERNAMES.get(st.session_state.username, "en-US-AriaNeural")
|
694 |
asyncio.run(save_chat_entry(st.session_state.username, message_to_send, voice))
|
695 |
update_player_state(st.session_state.username)
|
696 |
-
st.session_state.
|
697 |
st.rerun()
|
698 |
elif send_button_clicked:
|
699 |
st.toast("Message empty or same as last.")
|
@@ -705,6 +754,7 @@ def render_main_content():
|
|
705 |
if st.button("Clear World State", key="clear_world_state"):
|
706 |
state = {"objects": {}, "players": {}}
|
707 |
write_history_file(state)
|
|
|
708 |
st.success("World state cleared!")
|
709 |
st.rerun()
|
710 |
|
|
|
17 |
from PyPDF2 import PdfReader
|
18 |
import threading
|
19 |
from PIL import Image
|
20 |
+
from streamlit_javascript import st_javascript
|
21 |
|
22 |
# ==============================================================================
|
23 |
# Configuration & Constants
|
|
|
149 |
players[username]["last_action_timestamp"] = time.time()
|
150 |
state["players"] = players
|
151 |
write_history_file(state)
|
152 |
+
return state
|
153 |
|
154 |
+
def add_object_to_state(obj_data, username):
|
155 |
state = read_history_file()
|
156 |
state = prune_inactive_players(state)
|
157 |
state["objects"][obj_data["obj_id"]] = obj_data
|
158 |
write_history_file(state)
|
159 |
+
log_action(username, "place", obj_data)
|
160 |
+
return state
|
161 |
|
162 |
+
def remove_object_from_state(obj_id, username):
|
163 |
state = read_history_file()
|
164 |
state = prune_inactive_players(state)
|
165 |
if obj_id in state["objects"]:
|
166 |
+
obj_data = state["objects"][obj_id]
|
167 |
del state["objects"][obj_id]
|
168 |
write_history_file(state)
|
169 |
+
log_action(username, "delete", {"obj_id": obj_id})
|
170 |
+
return state, obj_data
|
171 |
+
return state, None
|
172 |
|
173 |
def log_action(username, action_type, data):
|
174 |
timestamp = get_current_time_str()
|
|
|
192 |
except Exception as e:
|
193 |
print(f"Error writing to player history log {player_log_file}: {e}")
|
194 |
|
195 |
+
# ==============================================================================
|
196 |
+
# JavaScript Message Handling
|
197 |
+
# ==============================================================================
|
198 |
+
|
199 |
+
def handle_js_messages():
|
200 |
+
message = st_javascript("""
|
201 |
+
window.addEventListener('message', (event) => {
|
202 |
+
return JSON.stringify(event.data);
|
203 |
+
}, {once: true});
|
204 |
+
return null;
|
205 |
+
""")
|
206 |
+
if message:
|
207 |
+
try:
|
208 |
+
data = json.loads(message)
|
209 |
+
action = data.get("type")
|
210 |
+
payload = data.get("payload", {})
|
211 |
+
username = payload.get("username", st.session_state.username)
|
212 |
+
if action == "place_object":
|
213 |
+
state = add_object_to_state(payload["object_data"], username)
|
214 |
+
st.session_state.world_state = state
|
215 |
+
st.rerun()
|
216 |
+
elif action == "delete_object":
|
217 |
+
state, obj_data = remove_object_from_state(payload["obj_id"], username)
|
218 |
+
st.session_state.world_state = state
|
219 |
+
st.rerun()
|
220 |
+
elif action == "move_player":
|
221 |
+
state = update_player_state(username, payload["position"])
|
222 |
+
log_action(username, "move", payload["position"])
|
223 |
+
st.session_state.world_state = state
|
224 |
+
st.rerun()
|
225 |
+
except json.JSONDecodeError:
|
226 |
+
print(f"Invalid JS message: {message}")
|
227 |
+
except Exception as e:
|
228 |
+
print(f"Error handling JS message: {e}")
|
229 |
+
|
230 |
# ==============================================================================
|
231 |
# User State & Session Init
|
232 |
# ==============================================================================
|
|
|
249 |
|
250 |
def init_session_state():
|
251 |
defaults = {
|
252 |
+
'message_counter': 0,
|
253 |
'audio_cache': {},
|
254 |
'tts_voice': "en-US-AriaNeural",
|
255 |
'chat_history': [],
|
|
|
260 |
'last_message': "",
|
261 |
'selected_object': 'None',
|
262 |
'paste_image_base64': "",
|
263 |
+
'new_world_name': "MyWorld",
|
264 |
+
'world_state': {"objects": {}, "players": {}}
|
265 |
}
|
266 |
for k, v in defaults.items():
|
267 |
if k not in st.session_state:
|
|
|
376 |
if st.session_state.get('enable_audio', True):
|
377 |
tts_message = message
|
378 |
audio_file = await async_edge_tts_generate(tts_message, voice, username)
|
379 |
+
log_action(username, "chat", {"message": message})
|
380 |
return md_file, audio_file
|
381 |
|
382 |
async def load_chat_history():
|
|
|
658 |
if st.session_state.selected_object != name:
|
659 |
st.session_state.selected_object = name
|
660 |
update_player_state(st.session_state.username)
|
661 |
+
st.rerun()
|
662 |
col_idx += 1
|
663 |
st.markdown("---")
|
664 |
if st.button("🚫 Clear Tool", key="clear_tool", use_container_width=True):
|
665 |
if st.session_state.selected_object != 'None':
|
666 |
st.session_state.selected_object = 'None'
|
667 |
update_player_state(st.session_state.username)
|
668 |
+
st.rerun()
|
669 |
|
670 |
st.markdown("---")
|
671 |
st.header("🗣️ Voice & User")
|
|
|
697 |
|
698 |
with tab_world:
|
699 |
st.header("Shared 3D World")
|
700 |
+
st.caption("Click to place objects with the selected tool, or click to move player. Right-click to delete. State is saved in history.json.")
|
701 |
state = read_history_file()
|
702 |
+
st.session_state.world_state = state
|
703 |
html_file_path = 'index.html'
|
704 |
try:
|
705 |
with open(html_file_path, 'r', encoding='utf-8') as f:
|
|
|
714 |
</script>"""
|
715 |
html_content_with_state = html_template.replace('</head>', js_injection_script + '\n</head>', 1)
|
716 |
components.html(html_content_with_state, height=700, scrolling=False)
|
717 |
+
handle_js_messages()
|
718 |
except FileNotFoundError:
|
719 |
st.error(f"CRITICAL ERROR: Could not find '{html_file_path}'.")
|
720 |
except Exception as e:
|
|
|
731 |
else:
|
732 |
st.caption("No chat messages yet.")
|
733 |
|
734 |
+
message_value = st.text_input("Your Message:", key=f"message_input_{st.session_state.get('message_counter', 0)}", label_visibility="collapsed")
|
735 |
send_button_clicked = st.button("Send Chat 💬", key="send_chat_button")
|
736 |
should_autosend = st.session_state.get('autosend', False) and message_value
|
737 |
|
|
|
742 |
voice = FUN_USERNAMES.get(st.session_state.username, "en-US-AriaNeural")
|
743 |
asyncio.run(save_chat_entry(st.session_state.username, message_to_send, voice))
|
744 |
update_player_state(st.session_state.username)
|
745 |
+
st.session_state.message_counter = st.session_state.get('message_counter', 0) + 1
|
746 |
st.rerun()
|
747 |
elif send_button_clicked:
|
748 |
st.toast("Message empty or same as last.")
|
|
|
754 |
if st.button("Clear World State", key="clear_world_state"):
|
755 |
state = {"objects": {}, "players": {}}
|
756 |
write_history_file(state)
|
757 |
+
st.session_state.world_state = state
|
758 |
st.success("World state cleared!")
|
759 |
st.rerun()
|
760 |
|