Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -106,7 +106,7 @@ def generate_filename(content, username, extension):
|
|
106 |
def initialize_history_file():
|
107 |
ensure_dir(SAVED_WORLDS_DIR)
|
108 |
if not os.path.exists(WORLD_STATE_FILE):
|
109 |
-
initial_state = {"objects": {}, "players": {}, "action_history": []}
|
110 |
with open(WORLD_STATE_FILE, 'w', encoding='utf-8') as f:
|
111 |
json.dump(initial_state, f, indent=2)
|
112 |
|
@@ -115,14 +115,14 @@ def read_history_file():
|
|
115 |
try:
|
116 |
with open(WORLD_STATE_FILE, 'r', encoding='utf-8') as f:
|
117 |
state = json.load(f)
|
118 |
-
# Ensure all expected keys exist
|
119 |
state.setdefault("objects", {})
|
120 |
state.setdefault("players", {})
|
121 |
state.setdefault("action_history", [])
|
|
|
122 |
return state
|
123 |
except Exception as e:
|
124 |
print(f"Error reading history file: {e}")
|
125 |
-
return {"objects": {}, "players": {}, "action_history": []}
|
126 |
|
127 |
def write_history_file(state):
|
128 |
with state_lock:
|
@@ -273,7 +273,7 @@ async def save_and_log_chat(username, message, voice):
|
|
273 |
update_action_history(username, "chat", {"message": message}, state)
|
274 |
return md_file_path, audio_file
|
275 |
|
276 |
-
# πΎ Save World State: Saves current state
|
277 |
def save_world_state(world_name):
|
278 |
if not world_name.strip():
|
279 |
st.error("World name cannot be empty.")
|
@@ -284,6 +284,7 @@ def save_world_state(world_name):
|
|
284 |
save_path = os.path.join(SAVED_WORLDS_DIR, filename)
|
285 |
|
286 |
state = read_history_file()
|
|
|
287 |
try:
|
288 |
with open(save_path, 'w', encoding='utf-8') as f:
|
289 |
json.dump(state, f, indent=2)
|
@@ -295,7 +296,7 @@ def save_world_state(world_name):
|
|
295 |
st.error(f"Failed to save world: {e}")
|
296 |
return False
|
297 |
|
298 |
-
# π Load World State: Loads a saved state
|
299 |
def load_world_state(filename):
|
300 |
load_path = os.path.join(SAVED_WORLDS_DIR, filename)
|
301 |
if not os.path.exists(load_path):
|
@@ -307,9 +308,11 @@ def load_world_state(filename):
|
|
307 |
state.setdefault("objects", {})
|
308 |
state.setdefault("players", {})
|
309 |
state.setdefault("action_history", [])
|
|
|
310 |
write_history_file(state)
|
311 |
st.session_state.world_state = state
|
312 |
st.session_state.action_history = state["action_history"]
|
|
|
313 |
print(f"Loaded world state from {load_path}")
|
314 |
st.success(f"Loaded world {filename}")
|
315 |
st.rerun()
|
@@ -336,6 +339,7 @@ def handle_js_messages():
|
|
336 |
action = data.get("type")
|
337 |
payload = data.get("payload", {})
|
338 |
username = payload.get("username", st.session_state.username)
|
|
|
339 |
if action == "place_object":
|
340 |
state = persist_world_objects(payload["object_data"], username, "place")
|
341 |
st.session_state.world_state = state
|
@@ -348,6 +352,14 @@ def handle_js_messages():
|
|
348 |
state = update_player_state(username, payload["position"])
|
349 |
st.session_state.world_state = state
|
350 |
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
except json.JSONDecodeError:
|
352 |
print(f"Invalid JS message: {message}")
|
353 |
except Exception as e:
|
@@ -385,10 +397,10 @@ def init_session_state():
|
|
385 |
'username': None,
|
386 |
'autosend': False,
|
387 |
'last_message': "",
|
388 |
-
'selected_object':
|
389 |
'paste_image_base64': "",
|
390 |
'new_world_name': "MyWorld",
|
391 |
-
'world_state': {"objects": {}, "players": {}, "action_history": []}
|
392 |
}
|
393 |
for k, v in defaults.items():
|
394 |
if k not in st.session_state:
|
@@ -408,6 +420,7 @@ def init_session_state():
|
|
408 |
state = read_history_file()
|
409 |
st.session_state.world_state = state
|
410 |
st.session_state.action_history = state.get("action_history", [])
|
|
|
411 |
update_player_state(st.session_state.username)
|
412 |
|
413 |
# ==============================================================================
|
@@ -718,8 +731,8 @@ def render_sidebar():
|
|
718 |
"Time": entry["timestamp"],
|
719 |
"Player": entry["username"],
|
720 |
"Action": entry["action"].capitalize(),
|
721 |
-
"
|
722 |
-
"
|
723 |
})
|
724 |
elif entry["action"] == "move":
|
725 |
pos = data.get("position", {})
|
@@ -728,16 +741,33 @@ def render_sidebar():
|
|
728 |
"Time": entry["timestamp"],
|
729 |
"Player": entry["username"],
|
730 |
"Action": "Move",
|
731 |
-
"
|
732 |
-
"
|
733 |
})
|
734 |
elif entry["action"] == "chat":
|
735 |
history_data.append({
|
736 |
"Time": entry["timestamp"],
|
737 |
"Player": entry["username"],
|
738 |
"Action": "Chat",
|
739 |
-
"
|
740 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
741 |
})
|
742 |
if history_data:
|
743 |
st.dataframe(pd.DataFrame(history_data), height=200, use_container_width=True)
|
@@ -798,16 +828,20 @@ def render_sidebar():
|
|
798 |
if cols[col_idx % 5].button(emoji, key=button_key, help=name, type=button_type, use_container_width=True):
|
799 |
if st.session_state.selected_object != name:
|
800 |
st.session_state.selected_object = name
|
801 |
-
|
802 |
-
|
|
|
|
|
803 |
st.rerun()
|
804 |
col_idx += 1
|
805 |
st.markdown("---")
|
806 |
if st.button("π« Clear Tool", key="clear_tool", use_container_width=True):
|
807 |
if st.session_state.selected_object != 'None':
|
808 |
st.session_state.selected_object = 'None'
|
809 |
-
|
810 |
-
|
|
|
|
|
811 |
st.rerun()
|
812 |
|
813 |
st.markdown("---")
|
@@ -843,6 +877,7 @@ def render_main_content():
|
|
843 |
st.header("Shared 3D World")
|
844 |
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.")
|
845 |
state = st.session_state.world_state
|
|
|
846 |
html_file_path = 'index.html'
|
847 |
try:
|
848 |
with open(html_file_path, 'r', encoding='utf-8') as f:
|
@@ -895,10 +930,11 @@ def render_main_content():
|
|
895 |
st.header("π Files & Settings")
|
896 |
st.subheader("πΎ World State Management")
|
897 |
if st.button("Clear World State", key="clear_world_state"):
|
898 |
-
state = {"objects": {}, "players": {}, "action_history": []}
|
899 |
write_history_file(state)
|
900 |
st.session_state.world_state = state
|
901 |
st.session_state.action_history = []
|
|
|
902 |
st.success("World state cleared!")
|
903 |
st.rerun()
|
904 |
|
|
|
106 |
def initialize_history_file():
|
107 |
ensure_dir(SAVED_WORLDS_DIR)
|
108 |
if not os.path.exists(WORLD_STATE_FILE):
|
109 |
+
initial_state = {"objects": {}, "players": {}, "action_history": [], "selected_object": "None"}
|
110 |
with open(WORLD_STATE_FILE, 'w', encoding='utf-8') as f:
|
111 |
json.dump(initial_state, f, indent=2)
|
112 |
|
|
|
115 |
try:
|
116 |
with open(WORLD_STATE_FILE, 'r', encoding='utf-8') as f:
|
117 |
state = json.load(f)
|
|
|
118 |
state.setdefault("objects", {})
|
119 |
state.setdefault("players", {})
|
120 |
state.setdefault("action_history", [])
|
121 |
+
state.setdefault("selected_object", "None")
|
122 |
return state
|
123 |
except Exception as e:
|
124 |
print(f"Error reading history file: {e}")
|
125 |
+
return {"objects": {}, "players": {}, "action_history": [], "selected_object": "None"}
|
126 |
|
127 |
def write_history_file(state):
|
128 |
with state_lock:
|
|
|
273 |
update_action_history(username, "chat", {"message": message}, state)
|
274 |
return md_file_path, audio_file
|
275 |
|
276 |
+
# πΎ Save World State: Saves current state including selected tool
|
277 |
def save_world_state(world_name):
|
278 |
if not world_name.strip():
|
279 |
st.error("World name cannot be empty.")
|
|
|
284 |
save_path = os.path.join(SAVED_WORLDS_DIR, filename)
|
285 |
|
286 |
state = read_history_file()
|
287 |
+
state["selected_object"] = st.session_state.selected_object
|
288 |
try:
|
289 |
with open(save_path, 'w', encoding='utf-8') as f:
|
290 |
json.dump(state, f, indent=2)
|
|
|
296 |
st.error(f"Failed to save world: {e}")
|
297 |
return False
|
298 |
|
299 |
+
# π Load World State: Loads a saved state including selected tool
|
300 |
def load_world_state(filename):
|
301 |
load_path = os.path.join(SAVED_WORLDS_DIR, filename)
|
302 |
if not os.path.exists(load_path):
|
|
|
308 |
state.setdefault("objects", {})
|
309 |
state.setdefault("players", {})
|
310 |
state.setdefault("action_history", [])
|
311 |
+
state.setdefault("selected_object", "None")
|
312 |
write_history_file(state)
|
313 |
st.session_state.world_state = state
|
314 |
st.session_state.action_history = state["action_history"]
|
315 |
+
st.session_state.selected_object = state["selected_object"]
|
316 |
print(f"Loaded world state from {load_path}")
|
317 |
st.success(f"Loaded world {filename}")
|
318 |
st.rerun()
|
|
|
339 |
action = data.get("type")
|
340 |
payload = data.get("payload", {})
|
341 |
username = payload.get("username", st.session_state.username)
|
342 |
+
state = read_history_file()
|
343 |
if action == "place_object":
|
344 |
state = persist_world_objects(payload["object_data"], username, "place")
|
345 |
st.session_state.world_state = state
|
|
|
352 |
state = update_player_state(username, payload["position"])
|
353 |
st.session_state.world_state = state
|
354 |
st.rerun()
|
355 |
+
elif action == "tool_change":
|
356 |
+
tool = payload.get("tool", "None")
|
357 |
+
state["selected_object"] = tool
|
358 |
+
st.session_state.selected_object = tool
|
359 |
+
write_history_file(state)
|
360 |
+
update_action_history(username, "tool_change", {"tool": tool}, state)
|
361 |
+
st.session_state.world_state = state
|
362 |
+
st.rerun()
|
363 |
except json.JSONDecodeError:
|
364 |
print(f"Invalid JS message: {message}")
|
365 |
except Exception as e:
|
|
|
397 |
'username': None,
|
398 |
'autosend': False,
|
399 |
'last_message': "",
|
400 |
+
'selected_object': "None",
|
401 |
'paste_image_base64': "",
|
402 |
'new_world_name': "MyWorld",
|
403 |
+
'world_state': {"objects": {}, "players": {}, "action_history": [], "selected_object": "None"}
|
404 |
}
|
405 |
for k, v in defaults.items():
|
406 |
if k not in st.session_state:
|
|
|
420 |
state = read_history_file()
|
421 |
st.session_state.world_state = state
|
422 |
st.session_state.action_history = state.get("action_history", [])
|
423 |
+
st.session_state.selected_object = state.get("selected_object", "None")
|
424 |
update_player_state(st.session_state.username)
|
425 |
|
426 |
# ==============================================================================
|
|
|
731 |
"Time": entry["timestamp"],
|
732 |
"Player": entry["username"],
|
733 |
"Action": entry["action"].capitalize(),
|
734 |
+
"Type": data.get("type", "N/A"),
|
735 |
+
"Details": position_str
|
736 |
})
|
737 |
elif entry["action"] == "move":
|
738 |
pos = data.get("position", {})
|
|
|
741 |
"Time": entry["timestamp"],
|
742 |
"Player": entry["username"],
|
743 |
"Action": "Move",
|
744 |
+
"Type": "Player",
|
745 |
+
"Details": position_str
|
746 |
})
|
747 |
elif entry["action"] == "chat":
|
748 |
history_data.append({
|
749 |
"Time": entry["timestamp"],
|
750 |
"Player": entry["username"],
|
751 |
"Action": "Chat",
|
752 |
+
"Type": "Message",
|
753 |
+
"Details": data.get("message", "N/A")[:50]
|
754 |
+
})
|
755 |
+
elif entry["action"] == "tool_change":
|
756 |
+
tool = data.get("tool", "None")
|
757 |
+
history_data.append({
|
758 |
+
"Time": entry["timestamp"],
|
759 |
+
"Player": entry["username"],
|
760 |
+
"Action": "Tool Change",
|
761 |
+
"Type": "Tool",
|
762 |
+
"Details": f"Selected {tool}"
|
763 |
+
})
|
764 |
+
elif entry["action"] == "rename":
|
765 |
+
history_data.append({
|
766 |
+
"Time": entry["timestamp"],
|
767 |
+
"Player": entry["username"],
|
768 |
+
"Action": "Rename",
|
769 |
+
"Type": "Username",
|
770 |
+
"Details": f"From {data.get('old_username')} to {data.get('new_username')}"
|
771 |
})
|
772 |
if history_data:
|
773 |
st.dataframe(pd.DataFrame(history_data), height=200, use_container_width=True)
|
|
|
828 |
if cols[col_idx % 5].button(emoji, key=button_key, help=name, type=button_type, use_container_width=True):
|
829 |
if st.session_state.selected_object != name:
|
830 |
st.session_state.selected_object = name
|
831 |
+
state = read_history_file()
|
832 |
+
state["selected_object"] = name
|
833 |
+
write_history_file(state)
|
834 |
+
update_action_history(st.session_state.username, "tool_change", {"tool": name}, state)
|
835 |
st.rerun()
|
836 |
col_idx += 1
|
837 |
st.markdown("---")
|
838 |
if st.button("π« Clear Tool", key="clear_tool", use_container_width=True):
|
839 |
if st.session_state.selected_object != 'None':
|
840 |
st.session_state.selected_object = 'None'
|
841 |
+
state = read_history_file()
|
842 |
+
state["selected_object"] = "None"
|
843 |
+
write_history_file(state)
|
844 |
+
update_action_history(st.session_state.username, "tool_change", {"tool": "None"}, state)
|
845 |
st.rerun()
|
846 |
|
847 |
st.markdown("---")
|
|
|
877 |
st.header("Shared 3D World")
|
878 |
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.")
|
879 |
state = st.session_state.world_state
|
880 |
+
state["selected_object"] = st.session_state.selected_object
|
881 |
html_file_path = 'index.html'
|
882 |
try:
|
883 |
with open(html_file_path, 'r', encoding='utf-8') as f:
|
|
|
930 |
st.header("π Files & Settings")
|
931 |
st.subheader("πΎ World State Management")
|
932 |
if st.button("Clear World State", key="clear_world_state"):
|
933 |
+
state = {"objects": {}, "players": {}, "action_history": [], "selected_object": "None"}
|
934 |
write_history_file(state)
|
935 |
st.session_state.world_state = state
|
936 |
st.session_state.action_history = []
|
937 |
+
st.session_state.selected_object = "None"
|
938 |
st.success("World state cleared!")
|
939 |
st.rerun()
|
940 |
|