awacke1 commited on
Commit
3189b2e
·
verified ·
1 Parent(s): f2268b5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +210 -102
app.py CHANGED
@@ -31,15 +31,23 @@ def load_world_map():
31
 
32
  def save_world_map(world_data):
33
  """Saves the world map metadata."""
34
- with open(WORLD_MAP_FILE, 'w') as f:
35
- json.dump(world_data, f, indent=4)
 
 
 
 
36
 
37
  def save_space_data(space_id, objects_data):
38
  """Saves the object data for a specific space."""
39
  file_path = os.path.join(SAVE_DIR, f"{space_id}.json")
40
- with open(file_path, 'w') as f:
41
- # Store objects directly, could add metadata later
42
- json.dump({"objects": objects_data}, f, indent=4)
 
 
 
 
43
 
44
  def load_space_data(space_id):
45
  """Loads object data for a specific space."""
@@ -52,6 +60,9 @@ def load_space_data(space_id):
52
  except json.JSONDecodeError:
53
  st.error(f"Error reading space file {space_id}.json.")
54
  return []
 
 
 
55
  return [] # Return empty list if file doesn't exist
56
 
57
  def find_next_available_grid_slot(world_data):
@@ -59,18 +70,38 @@ def find_next_available_grid_slot(world_data):
59
  occupied = set((d["grid_x"], d["grid_y"]) for d in world_data.get("spaces", {}).values())
60
  x, y = 0, 0
61
  dx, dy = 0, -1
62
- steps = 0
63
- limit = 1
64
- count = 0
65
- while (x, y) in occupied:
66
- if x == y or (x < 0 and x == -y) or (x > 0 and x == 1-y):
67
- dx, dy = -dy, dx # Change direction (spiral)
 
 
 
68
  x, y = x + dx, y + dy
69
- count += 1
70
- if count > 1000: # Safety break
71
- st.error("Could not find empty grid slot easily!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  return None, None
73
- return x, y
74
 
75
  # --- Minimap Generation ---
76
  def generate_minimap(world_data, current_space_id=None):
@@ -78,33 +109,44 @@ def generate_minimap(world_data, current_space_id=None):
78
  if not spaces:
79
  return None # No map if no spaces saved
80
 
81
- coords = [(d["grid_x"], d["grid_y"]) for d in spaces.values()]
82
- min_x = min(c[0] for c in coords)
83
- max_x = max(c[0] for c in coords)
84
- min_y = min(c[1] for c in coords)
85
- max_y = max(c[1] for c in coords)
86
-
87
- img_width = (max_x - min_x + 1) * MINIMAP_CELL_SIZE
88
- img_height = (max_y - min_y + 1) * MINIMAP_CELL_SIZE
89
-
90
- img = Image.new('RGB', (img_width, img_height), color = 'lightgrey')
91
- draw = ImageDraw.Draw(img)
92
-
93
- for space_id, data in spaces.items():
94
- cell_x = (data["grid_x"] - min_x) * MINIMAP_CELL_SIZE
95
- cell_y = (data["grid_y"] - min_y) * MINIMAP_CELL_SIZE
96
- color = "blue"
97
- if space_id == current_space_id:
98
- color = "red" # Highlight current space
99
- elif current_space_id is None and space_id == list(spaces.keys())[0]: # Highlight first if none selected
100
- color = "red"
101
-
102
- draw.rectangle(
103
- [cell_x, cell_y, cell_x + MINIMAP_CELL_SIZE -1, cell_y + MINIMAP_CELL_SIZE -1],
104
- fill=color, outline="black"
105
- )
106
-
107
- return img
 
 
 
 
 
 
 
 
 
 
 
108
 
109
 
110
  # --- Page Config ---
@@ -122,6 +164,9 @@ if 'space_name' not in st.session_state:
122
  st.session_state.space_name = ""
123
  if 'initial_objects' not in st.session_state:
124
  st.session_state.initial_objects = [] # Objects to load into JS
 
 
 
125
 
126
  # --- Load initial world data ---
127
  world_data = load_world_map()
@@ -130,50 +175,83 @@ world_data = load_world_map()
130
  query_params = st.query_params.to_dict()
131
  save_data_encoded = query_params.get("save_data")
132
 
133
- save_triggered = False
134
  if save_data_encoded:
 
 
135
  try:
136
  save_data_json = urllib.parse.unquote(save_data_encoded[0]) # Get first value if list
137
  objects_to_save = json.loads(save_data_json)
138
 
139
- space_id_to_save = query_params.get("space_id", [st.session_state.current_space_id])[0] # Get from URL or state
140
- space_name_to_save = query_params.get("space_name", [st.session_state.space_name])[0]
 
 
141
 
142
- if not space_id_to_save:
143
  space_id_to_save = str(uuid.uuid4()) # Create new ID
 
144
  st.session_state.current_space_id = space_id_to_save # Update state
 
 
 
 
 
 
 
 
 
 
 
145
  grid_x, grid_y = find_next_available_grid_slot(world_data)
146
  if grid_x is not None:
147
- world_data.setdefault("spaces", {})[space_id_to_save] = {
148
  "grid_x": grid_x,
149
  "grid_y": grid_y,
150
- "name": space_name_to_save or f"Space {len(world_data.get('spaces',{}))+1}"
151
  }
152
- save_world_map(world_data)
153
  else:
154
  st.error("Failed to assign grid position!")
 
 
 
 
155
 
 
 
156
 
157
- # Save the actual object data
158
- save_space_data(space_id_to_save, objects_to_save)
159
- st.success(f"Space '{space_name_to_save or space_id_to_save}' saved successfully!")
160
 
161
- # Update name in world map if it changed and space exists
162
- if space_id_to_save in world_data.get("spaces", {}) and space_name_to_save:
163
- world_data["spaces"][space_id_to_save]["name"] = space_name_to_save
164
- save_world_map(world_data)
165
 
166
- # IMPORTANT: Clear query param to prevent resave on refresh
167
- st.query_params.clear()
168
- save_triggered = True # Flag to maybe skip immediate rerun if needed below
169
 
170
  except Exception as e:
171
  st.error(f"Error processing save data: {e}")
172
- st.exception(e) # Show traceback
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
- # Need to reload objects if just saved or loading a new space
175
- if 'current_space_id' in st.session_state and st.session_state.current_space_id:
 
 
176
  st.session_state.initial_objects = load_space_data(st.session_state.current_space_id)
 
 
 
177
 
178
 
179
  # --- Sidebar Controls ---
@@ -184,62 +262,86 @@ with st.sidebar:
184
  st.subheader("Manage Spaces")
185
  saved_spaces = list(world_data.get("spaces", {}).items()) # List of (id, data) tuples
186
  space_options = {sid: data.get("name", f"Unnamed ({sid[:6]}...)") for sid, data in saved_spaces}
187
- space_options["_new_"] = "✨ Create New Space ✨" # Special option
 
 
 
 
 
 
 
 
188
 
189
- selected_space_display = st.selectbox(
190
  "Load or Create Space:",
191
- options = ["_new_"] + list(space_options.keys()),
192
  format_func = lambda x: space_options.get(x, "Select...") if x != "_new_" else "✨ Create New Space ✨",
193
- index=0, # Default to Create New
194
- key="space_selection_key" # Unique key might be needed if dynamically changing options
195
- # Note: Changing this will trigger rerun. Need logic below to handle load.
196
  )
197
 
198
- # Handle Load/Create based on selection
199
- if st.session_state.space_selection_key != "_new_":
200
- # Load existing selected
201
- if st.session_state.current_space_id != st.session_state.space_selection_key:
202
- st.session_state.current_space_id = st.session_state.space_selection_key
203
- st.session_state.initial_objects = load_space_data(st.session_state.current_space_id)
204
- st.session_state.space_name = world_data["spaces"][st.session_state.current_space_id].get("name", "")
205
- st.rerun() # Rerun to load data and update JS injection
206
- elif st.session_state.space_selection_key == "_new_" and st.session_state.current_space_id is not None:
207
- # Handle switch from existing to "Create New"
208
- st.session_state.current_space_id = None
209
- st.session_state.initial_objects = []
210
- st.session_state.space_name = ""
211
- st.rerun()
212
-
213
-
 
 
 
214
  current_name = st.text_input(
215
  "Current Space Name:",
216
- value=st.session_state.space_name,
217
  key="current_space_name_input"
218
  )
219
- # Update state immediately if name changes
220
  if current_name != st.session_state.space_name:
221
  st.session_state.space_name = current_name
222
- # If editing an existing space, maybe update world_map.json immediately or on save? Let's do on save for simplicity.
223
-
224
 
225
  st.info(f"Current Space ID: {st.session_state.current_space_id or 'None (New)'}")
226
- st.caption("Saving uses the name above. A unique ID is assigned automatically for new spaces.")
227
  st.markdown("---")
228
 
229
 
230
  # --- Object Placement Controls ---
231
  st.subheader("Place Objects")
232
  object_types = ["None", "Simple House", "Tree", "Rock", "Fence Post"]
233
- selected = st.selectbox(
 
 
 
 
 
 
 
234
  "Select Object to Place:",
235
  options=object_types,
236
- key='selected_object'
 
237
  )
 
 
 
 
 
238
  st.markdown("---")
239
 
240
  # --- Minimap ---
241
  st.subheader("World Minimap")
242
- minimap_img = generate_minimap(world_data, st.session_state.current_space_id)
 
 
243
  if minimap_img:
244
  st.image(minimap_img, caption="Blue: Saved Spaces, Red: Current", use_column_width=True)
245
  else:
@@ -252,12 +354,15 @@ st.caption(f"Editing: {st.session_state.space_name or 'New Space'}")
252
 
253
  # --- Load and Prepare HTML ---
254
  html_file_path = 'index.html'
 
255
 
256
  try:
 
257
  with open(html_file_path, 'r', encoding='utf-8') as f:
258
  html_template = f.read()
259
 
260
- # --- Inject Python state into JavaScript ---
 
261
  js_injection_script = f"""
262
  <script>
263
  // Set global variables BEFORE the main script runs
@@ -265,28 +370,31 @@ try:
265
  window.INITIAL_OBJECTS = {json.dumps(st.session_state.initial_objects)};
266
  window.CURRENT_SPACE_ID = {json.dumps(st.session_state.current_space_id)};
267
  window.CURRENT_SPACE_NAME = {json.dumps(st.session_state.space_name)};
268
- console.log("Streamlit State:", {{
 
269
  selectedObject: window.SELECTED_OBJECT_TYPE,
270
- initialObjects: window.INITIAL_OBJECTS,
271
  spaceId: window.CURRENT_SPACE_ID,
272
  spaceName: window.CURRENT_SPACE_NAME
273
  }});
274
  </script>
275
  """
276
- # Insert the injection script just before the closing </head>
277
- # Using placeholder is safer if you modify index.html: html_content_with_state = html_template.replace('</head>', js_injection_script + '\n</head>', 1)
278
-
279
 
280
- # --- Embed HTML Component ---
281
  components.html(
282
  html_content_with_state,
283
  height=750, # Adjust height as needed
284
  scrolling=False
285
  )
286
 
 
287
  except FileNotFoundError:
288
- st.error(f"Error: Could not find the file '{html_file_path}'.")
289
  st.warning(f"Please make sure `{html_file_path}` is in the same directory as `app.py` and that the `{SAVE_DIR}` directory exists.")
290
  except Exception as e:
291
- st.error(f"An error occurred: {e}")
292
- st.exception(e)
 
 
31
 
32
  def save_world_map(world_data):
33
  """Saves the world map metadata."""
34
+ try:
35
+ with open(WORLD_MAP_FILE, 'w') as f:
36
+ json.dump(world_data, f, indent=4)
37
+ except Exception as e:
38
+ st.error(f"Error saving world map: {e}")
39
+
40
 
41
  def save_space_data(space_id, objects_data):
42
  """Saves the object data for a specific space."""
43
  file_path = os.path.join(SAVE_DIR, f"{space_id}.json")
44
+ try:
45
+ with open(file_path, 'w') as f:
46
+ # Store objects directly, could add metadata later
47
+ json.dump({"objects": objects_data}, f, indent=4)
48
+ except Exception as e:
49
+ st.error(f"Error saving space data for {space_id}: {e}")
50
+
51
 
52
  def load_space_data(space_id):
53
  """Loads object data for a specific space."""
 
60
  except json.JSONDecodeError:
61
  st.error(f"Error reading space file {space_id}.json.")
62
  return []
63
+ except Exception as e:
64
+ st.error(f"Error loading space data for {space_id}: {e}")
65
+ return []
66
  return [] # Return empty list if file doesn't exist
67
 
68
  def find_next_available_grid_slot(world_data):
 
70
  occupied = set((d["grid_x"], d["grid_y"]) for d in world_data.get("spaces", {}).values())
71
  x, y = 0, 0
72
  dx, dy = 0, -1
73
+ limit = 1 # How many steps in current direction
74
+ steps = 0 # Steps taken in current direction
75
+ leg = 0 # Current leg of the spiral (0=right, 1=up, 2=left, 3=down)
76
+
77
+ while True:
78
+ if (x, y) not in occupied:
79
+ return x, y
80
+
81
+ # Move
82
  x, y = x + dx, y + dy
83
+ steps += 1
84
+
85
+ # Check if direction needs changing
86
+ if steps == limit:
87
+ steps = 0
88
+ leg = (leg + 1) % 4
89
+ if leg == 0: # Right
90
+ dx, dy = 1, 0
91
+ elif leg == 1: # Up
92
+ dx, dy = 0, 1
93
+ limit += 1 # Increase steps after moving up
94
+ elif leg == 2: # Left
95
+ dx, dy = -1, 0
96
+ elif leg == 3: # Down
97
+ dx, dy = 0, -1
98
+ limit += 1 # Increase steps after moving down
99
+
100
+ # Safety break (increase if expecting huge maps)
101
+ if limit > 100:
102
+ st.error("Could not find empty grid slot easily! Map too full?")
103
  return None, None
104
+
105
 
106
  # --- Minimap Generation ---
107
  def generate_minimap(world_data, current_space_id=None):
 
109
  if not spaces:
110
  return None # No map if no spaces saved
111
 
112
+ try:
113
+ coords = [(d["grid_x"], d["grid_y"]) for d in spaces.values()]
114
+ # Handle case where coords might be empty if spaces dict is malformed
115
+ if not coords: return None
116
+
117
+ min_x = min(c[0] for c in coords)
118
+ max_x = max(c[0] for c in coords)
119
+ min_y = min(c[1] for c in coords)
120
+ max_y = max(c[1] for c in coords)
121
+
122
+ # Add padding around the edges
123
+ padding = 1
124
+ img_width = (max_x - min_x + 1 + 2 * padding) * MINIMAP_CELL_SIZE
125
+ img_height = (max_y - min_y + 1 + 2 * padding) * MINIMAP_CELL_SIZE
126
+
127
+ img = Image.new('RGB', (img_width, img_height), color = 'lightgrey')
128
+ draw = ImageDraw.Draw(img)
129
+
130
+ for space_id, data in spaces.items():
131
+ # Calculate position including padding offset
132
+ cell_x = (data["grid_x"] - min_x + padding) * MINIMAP_CELL_SIZE
133
+ cell_y = (data["grid_y"] - min_y + padding) * MINIMAP_CELL_SIZE
134
+ color = "blue"
135
+ if space_id == current_space_id:
136
+ color = "red" # Highlight current space
137
+
138
+ draw.rectangle(
139
+ [cell_x, cell_y, cell_x + MINIMAP_CELL_SIZE -1, cell_y + MINIMAP_CELL_SIZE -1],
140
+ fill=color, outline="black"
141
+ )
142
+ # Optional: Draw space name/ID lightly
143
+ # draw.text((cell_x + 1, cell_y + 1), data.get("name", sid[:2]), fill="white", font_size=8)
144
+
145
+
146
+ return img
147
+ except Exception as e:
148
+ st.error(f"Error generating minimap: {e}")
149
+ return None
150
 
151
 
152
  # --- Page Config ---
 
164
  st.session_state.space_name = ""
165
  if 'initial_objects' not in st.session_state:
166
  st.session_state.initial_objects = [] # Objects to load into JS
167
+ if 'trigger_rerun' not in st.session_state:
168
+ st.session_state.trigger_rerun = 0 # Counter to force reruns cleanly
169
+
170
 
171
  # --- Load initial world data ---
172
  world_data = load_world_map()
 
175
  query_params = st.query_params.to_dict()
176
  save_data_encoded = query_params.get("save_data")
177
 
178
+
179
  if save_data_encoded:
180
+ st.session_state.trigger_rerun += 1 # Increment to signal change
181
+ save_successful = False
182
  try:
183
  save_data_json = urllib.parse.unquote(save_data_encoded[0]) # Get first value if list
184
  objects_to_save = json.loads(save_data_json)
185
 
186
+ # Get ID/Name from URL or fall back to session state if not in URL (e.g., if redirect failed partially)
187
+ space_id_to_save = query_params.get("space_id", [st.session_state.current_space_id])[0]
188
+ space_name_to_save = urllib.parse.unquote(query_params.get("space_name", [st.session_state.space_name])[0])
189
+
190
 
191
+ if not space_id_to_save or space_id_to_save == 'null': # JS might send 'null' string
192
  space_id_to_save = str(uuid.uuid4()) # Create new ID
193
+ is_new_space = True
194
  st.session_state.current_space_id = space_id_to_save # Update state
195
+ else:
196
+ is_new_space = False
197
+
198
+ # Save the actual object data
199
+ save_space_data(space_id_to_save, objects_to_save)
200
+
201
+ # Update world map only if needed (new space or name change)
202
+ map_updated = False
203
+ world_spaces = world_data.setdefault("spaces", {})
204
+
205
+ if is_new_space:
206
  grid_x, grid_y = find_next_available_grid_slot(world_data)
207
  if grid_x is not None:
208
+ world_spaces[space_id_to_save] = {
209
  "grid_x": grid_x,
210
  "grid_y": grid_y,
211
+ "name": space_name_to_save or f"Space_{space_id_to_save[:6]}"
212
  }
213
+ map_updated = True
214
  else:
215
  st.error("Failed to assign grid position!")
216
+ elif space_id_to_save in world_spaces and world_spaces[space_id_to_save].get("name") != space_name_to_save:
217
+ # Update name if it changed for existing space
218
+ world_spaces[space_id_to_save]["name"] = space_name_to_save
219
+ map_updated = True
220
 
221
+ if map_updated:
222
+ save_world_map(world_data)
223
 
224
+ st.session_state.space_name = space_name_to_save # Ensure state matches saved name
 
 
225
 
226
+ st.success(f"Space '{space_name_to_save or space_id_to_save}' saved successfully!")
227
+ save_successful = True
 
 
228
 
 
 
 
229
 
230
  except Exception as e:
231
  st.error(f"Error processing save data: {e}")
232
+ st.exception(e) # Show full traceback for debugging
233
+
234
+ # IMPORTANT: Clear query param to prevent resave on refresh/rerun
235
+ st.query_params.clear()
236
+
237
+ # Force a rerun IF save was processed, to reload objects and clear URL state visually
238
+ # Do this AFTER clearing query_params
239
+ if save_successful:
240
+ # No need to explicitly call st.rerun() here, Streamlit's flow after
241
+ # clearing query params and updating state often handles it.
242
+ # If visual updates lag, uncommenting st.rerun() might be necessary,
243
+ # but can sometimes cause double reruns.
244
+ # st.rerun()
245
+ pass
246
 
247
+
248
+ # --- Load Space Data if ID is set ---
249
+ # This runs on every script execution, including after save/load actions
250
+ if st.session_state.current_space_id:
251
  st.session_state.initial_objects = load_space_data(st.session_state.current_space_id)
252
+ # Ensure name is loaded if space ID exists but name state is empty
253
+ if not st.session_state.space_name and st.session_state.current_space_id in world_data.get("spaces", {}):
254
+ st.session_state.space_name = world_data["spaces"][st.session_state.current_space_id].get("name", "")
255
 
256
 
257
  # --- Sidebar Controls ---
 
262
  st.subheader("Manage Spaces")
263
  saved_spaces = list(world_data.get("spaces", {}).items()) # List of (id, data) tuples
264
  space_options = {sid: data.get("name", f"Unnamed ({sid[:6]}...)") for sid, data in saved_spaces}
265
+ options_list = ["_new_"] + list(space_options.keys())
266
+
267
+ # Determine current index for selectbox
268
+ current_selection_index = 0 # Default to "Create New"
269
+ if st.session_state.current_space_id in space_options:
270
+ try:
271
+ current_selection_index = options_list.index(st.session_state.current_space_id)
272
+ except ValueError:
273
+ current_selection_index = 0 # Fallback if ID somehow not in list
274
 
275
+ selected_space_option = st.selectbox(
276
  "Load or Create Space:",
277
+ options = options_list,
278
  format_func = lambda x: space_options.get(x, "Select...") if x != "_new_" else "✨ Create New Space ✨",
279
+ index=current_selection_index,
280
+ key="space_selection_widget" # Use a dedicated key for the widget
 
281
  )
282
 
283
+ # --- Handle Load/Create Logic ---
284
+ # Compare widget state to session state to detect user change
285
+ if selected_space_option != st.session_state.get('_last_selected_space', None):
286
+ st.session_state._last_selected_space = selected_space_option # Track selection change
287
+ if selected_space_option == "_new_":
288
+ # User explicitly selected "Create New"
289
+ if st.session_state.current_space_id is not None: # Check if switching *from* an existing space
290
+ st.session_state.current_space_id = None
291
+ st.session_state.initial_objects = []
292
+ st.session_state.space_name = ""
293
+ st.rerun() # Rerun to clear the space
294
+ else:
295
+ # User selected an existing space
296
+ if st.session_state.current_space_id != selected_space_option:
297
+ st.session_state.current_space_id = selected_space_option
298
+ # Loading data and name happens naturally at top of script now
299
+ st.rerun() # Rerun to reflect the load
300
+
301
+ # --- Name Input ---
302
  current_name = st.text_input(
303
  "Current Space Name:",
304
+ value=st.session_state.space_name, # Reflect current loaded/new name state
305
  key="current_space_name_input"
306
  )
307
+ # Update session state if user types a new name
308
  if current_name != st.session_state.space_name:
309
  st.session_state.space_name = current_name
310
+ # Note: The name is only saved to world_map.json when the JS save is triggered
 
311
 
312
  st.info(f"Current Space ID: {st.session_state.current_space_id or 'None (New)'}")
313
+ st.caption("Click 'Save Work' in the 3D view to save changes.")
314
  st.markdown("---")
315
 
316
 
317
  # --- Object Placement Controls ---
318
  st.subheader("Place Objects")
319
  object_types = ["None", "Simple House", "Tree", "Rock", "Fence Post"]
320
+ # Ensure selectbox reflects current state
321
+ current_object_index = 0
322
+ try:
323
+ current_object_index = object_types.index(st.session_state.selected_object)
324
+ except ValueError:
325
+ st.session_state.selected_object = "None" # Reset if invalid
326
+
327
+ selected_object_type_widget = st.selectbox(
328
  "Select Object to Place:",
329
  options=object_types,
330
+ index=current_object_index,
331
+ key="selected_object_widget"
332
  )
333
+ # Update state based on widget interaction
334
+ if selected_object_type_widget != st.session_state.selected_object:
335
+ st.session_state.selected_object = selected_object_type_widget
336
+ st.rerun() # Rerun to update JS injection
337
+
338
  st.markdown("---")
339
 
340
  # --- Minimap ---
341
  st.subheader("World Minimap")
342
+ # Regenerate map data fresh each time
343
+ current_world_data_for_map = load_world_map()
344
+ minimap_img = generate_minimap(current_world_data_for_map, st.session_state.current_space_id)
345
  if minimap_img:
346
  st.image(minimap_img, caption="Blue: Saved Spaces, Red: Current", use_column_width=True)
347
  else:
 
354
 
355
  # --- Load and Prepare HTML ---
356
  html_file_path = 'index.html'
357
+ html_content_with_state = None # Initialize to None
358
 
359
  try:
360
+ # --- Read the HTML template file ---
361
  with open(html_file_path, 'r', encoding='utf-8') as f:
362
  html_template = f.read()
363
 
364
+ # --- Prepare JavaScript code to inject state ---
365
+ # Ensure all state variables are correctly serialized as JSON
366
  js_injection_script = f"""
367
  <script>
368
  // Set global variables BEFORE the main script runs
 
370
  window.INITIAL_OBJECTS = {json.dumps(st.session_state.initial_objects)};
371
  window.CURRENT_SPACE_ID = {json.dumps(st.session_state.current_space_id)};
372
  window.CURRENT_SPACE_NAME = {json.dumps(st.session_state.space_name)};
373
+ // Basic logging to verify state in browser console
374
+ console.log("Streamlit State Injected:", {{
375
  selectedObject: window.SELECTED_OBJECT_TYPE,
376
+ initialObjectsCount: window.INITIAL_OBJECTS ? window.INITIAL_OBJECTS.length : 0,
377
  spaceId: window.CURRENT_SPACE_ID,
378
  spaceName: window.CURRENT_SPACE_NAME
379
  }});
380
  </script>
381
  """
382
+ # --- Inject the script into the HTML template ---
383
+ # Replacing just before </head> is generally safe
384
+ html_content_with_state = html_template.replace('</head>', js_injection_script + '\n</head>', 1)
385
 
386
+ # --- Embed HTML Component (ONLY if HTML loading and preparation succeeded) ---
387
  components.html(
388
  html_content_with_state,
389
  height=750, # Adjust height as needed
390
  scrolling=False
391
  )
392
 
393
+ # --- Error Handling ---
394
  except FileNotFoundError:
395
+ st.error(f"CRITICAL ERROR: Could not find the file '{html_file_path}'.")
396
  st.warning(f"Please make sure `{html_file_path}` is in the same directory as `app.py` and that the `{SAVE_DIR}` directory exists.")
397
  except Exception as e:
398
+ st.error(f"An critical error occurred during HTML preparation or component rendering: {e}")
399
+ st.exception(e) # Show full traceback for debugging
400
+ # Do NOT attempt to render components.html if html_content_with_state is not defined