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 |
|