broadfield-dev commited on
Commit
0ef5c69
·
verified ·
1 Parent(s): a9bc6a8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -31
app.py CHANGED
@@ -35,6 +35,7 @@ print("model_logic.py loaded successfully.")
35
 
36
 
37
  bbb = chr(96) * 3
 
38
  parsed_code_blocks_state_cache = []
39
  BOT_ROLE_NAME = "assistant"
40
  # Removed GROQ_API_ENDPOINT as it's now in model_logic
@@ -133,6 +134,7 @@ def _parse_chat_stream_logic(latest_bot_message_content, existing_files_state=No
133
  Parses a single bot message content string to find file blocks and updates the state.
134
  Assumes existing_files_state is the current state *before* this message.
135
  """
 
136
  latest_blocks_dict = {}
137
  if existing_files_state:
138
  # Copy existing blocks, except for potential structure blocks that might be overwritten
@@ -192,6 +194,7 @@ def _parse_chat_stream_logic(latest_bot_message_content, existing_files_state=No
192
 
193
  def _export_selected_logic(selected_filenames, space_line_name_for_md, parsed_blocks_for_export):
194
  # This function remains largely the same, using the provided parsed_blocks_for_export
 
195
  results = {"output_str": "", "error_message": None, "download_filepath": None}
196
  # Filter out structure blocks for file listing/export content
197
  exportable_blocks_content = [b for b in parsed_blocks_for_export if not b.get("is_structure_block") and not b.get("is_binary") and not (b.get("code", "").startswith("[Error loading content:") or b.get("code", "").startswith("[Binary or Skipped file]"))]
@@ -276,14 +279,11 @@ def _convert_gr_history_to_api_messages(system_prompt, gr_history, current_user_
276
  if current_user_message: messages.append({"role": "user", "content": current_user_message})
277
  return messages
278
 
279
- # This function is no longer needed as we process the *latest* message content directly from the stream handler
280
- # def get_latest_bot_message_as_json(gr_history):
281
- # ...
282
-
283
 
284
  def _generate_ui_outputs_from_cache(owner, space_name):
285
- # This function remains largely the same, generating UI previews and the export MD
286
  global parsed_code_blocks_state_cache
 
287
  preview_md_val = "*No files in cache to display.*"
288
  formatted_md_val = f"# Space: {owner}/{space_name}\n## File Structure\n{bbb}\n📁 Root\n{bbb}\n\n*No files in cache.*" if owner or space_name else "*Load or define a Space to see its Markdown structure.*"
289
  download_file = None
@@ -321,6 +321,7 @@ def _generate_ui_outputs_from_cache(owner, space_name):
321
 
322
  # --- Refactored Chat Submit Handler ---
323
  def handle_chat_submit(user_message, chat_history, api_key_input, provider_select, model_select, system_prompt, hf_owner_name, hf_repo_name, _current_formatted_markdown):
 
324
  global parsed_code_blocks_state_cache
325
  _chat_msg_in = ""
326
  _chat_hist = list(chat_history)
@@ -346,7 +347,7 @@ def handle_chat_submit(user_message, chat_history, api_key_input, provider_selec
346
 
347
  for f_info in parsed_from_md.get("files", []):
348
  # Only add if it has a path and isn't the structure block representation placeholder
349
- if f_info.get("path") and f_info["path"] != "File Structure (original)":
350
  # Check if it's a binary representation string
351
  is_binary_repr = isinstance(f_info.get("content"), str) and (f_info["content"].startswith("[Binary file") or f_info["content"].startswith("[Error loading content:") or f_info["content"].startswith("[Binary or Skipped file]"))
352
  # Check if a block with this filename already exists in new_cache_state and replace it
@@ -434,14 +435,14 @@ def handle_chat_submit(user_message, chat_history, api_key_input, provider_selec
434
  # Generate stream from model_logic
435
  for chunk in generate_stream(provider_select, model_select, api_key_override, api_msgs):
436
  if chunk is None: continue # Skip None chunks if any
437
- if chunk.startswith("Error: ") or chunk.startswith("API HTTP Error"):
438
  # If an error chunk is received, treat it as the final output and stop
439
  full_bot_response_content = chunk
440
  error_during_stream = chunk
441
  break # Stop processing stream
442
  else:
443
  # Accumulate response and update the last message in chat_hist
444
- full_bot_response_content += chunk
445
  _chat_hist[-1] = (user_message, full_bot_response_content)
446
  _status = f"Streaming from {model_select}..."
447
  # Yield update immediately after receiving chunk
@@ -450,7 +451,7 @@ def handle_chat_submit(user_message, chat_history, api_key_input, provider_selec
450
  # After the stream finishes or breaks
451
  if error_during_stream:
452
  _status = error_during_stream # Set status to the error message
453
- elif full_bot_response_content:
454
  _status = f"Streaming complete. Processing files from {model_select} response..."
455
 
456
  # Pass the *current state* (updated from markdown at the start)
@@ -472,9 +473,12 @@ def handle_chat_submit(user_message, chat_history, api_key_input, provider_selec
472
  _formatted_output_update, _detected_files_update, _download_btn_update = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
473
  _status = "Processing complete. Previews updated."
474
  else:
475
- # Handle cases where the stream finished but yielded no content (e.g., filter)
476
- _status = "AI response complete, but returned no content."
477
- # Keep existing previews/markdown if no content was generated to parse
 
 
 
478
 
479
  except Exception as e:
480
  # Catch any errors that occurred *before* or *during* the stream setup/iteration
@@ -487,6 +491,8 @@ def handle_chat_submit(user_message, chat_history, api_key_input, provider_selec
487
  _chat_hist.append((user_message, error_msg)) # Append as a new user/bot turn if structure unexpected
488
  _status = error_msg
489
  # Previews and markdown might not be affected by a generation error, keep existing state
 
 
490
 
491
  # Final yield to update UI after all processing
492
  yield (_chat_msg_in, _chat_hist, _status, _detected_files_update, _formatted_output_update, _download_btn_update)
@@ -499,14 +505,21 @@ def update_models_dropdown(provider_select):
499
  return gr.update(choices=[], value=None)
500
  models = get_models_for_provider(provider_select)
501
  default_model = get_default_model_for_provider(provider_select)
502
- return gr.update(choices=models, value=default_model if default_model in models else (models[0] if models else None))
 
 
 
 
 
 
503
 
 
504
 
505
- # --- Existing handlers for Load, Build, Edit, Delete, Status (Mostly unchanged) ---
506
- # Ensure they correctly use hf_api_key_ui and potentially owner_name_input/space_name_input
507
 
508
  def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
509
- # ... (rest of this function is largely the same, but needs to update global cache)
510
  global parsed_code_blocks_state_cache
511
  _formatted_md_val, _detected_preview_val, _status_val = "*Loading files...*", "*Loading files...*", f"Loading Space: {ui_owner_name}/{ui_space_name}..."
512
  _file_browser_update, _iframe_html_update, _download_btn_update = gr.update(visible=False, choices=[], value=None), gr.update(value=None, visible=False), gr.update(interactive=False, value=None)
@@ -621,14 +634,17 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
621
 
622
 
623
  def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, space_sdk_ui, formatted_markdown_content):
 
 
624
  # ... (this function calls build_logic_create_space and refreshes file list)
625
  _build_status, _iframe_html, _file_browser_update = "Starting space build process...", gr.update(value=None, visible=False), gr.update(visible=False, choices=[], value=None)
626
- yield _build_status, _iframe_html, _file_browser_update # Yield initial status
627
- if not ui_space_name_part or "/" in ui_space_name_part: _build_status = f"Build Error: HF Space Name '{ui_space_name_part}' must be repo name only (no '/')."; yield _build_status, _iframe_html, _file_browser_update; return
 
628
  final_owner_for_build = ui_owner_name_part
629
  if not final_owner_for_build:
630
  token_for_whoami, token_err = build_logic_get_api_token(hf_api_key_ui)
631
- if token_err: _build_status = f"Build Error: {token_err}"; yield _build_status, _iframe_html, _file_browser_update; return
632
  if token_for_whoami:
633
  try:
634
  user_info = build_logic_whoami(token=token_for_whoami)
@@ -637,19 +653,18 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
637
  except Exception as e: _build_status += f"\n(Warning: Could not auto-detect owner for build: {e}. Please specify.)"
638
  else: _build_status += "\n(Warning: Owner not specified and no token to auto-detect for build. Please specify owner or provide a token.)"
639
 
640
- if not final_owner_for_build: _build_status = "Build Error: HF Owner Name could not be determined. Please specify it."; yield _build_status, _iframe_html, _file_browser_update; return
641
 
642
  # Before building, parse the markdown to ensure the cache reflects exactly what's being built
643
  # This prevents inconsistencies if the user manually edited the markdown output
644
  try:
645
  parsed_from_md_for_build = build_logic_parse_markdown(formatted_markdown_content)
646
  # Replace the global cache state with the state derived from the markdown being built
647
- global parsed_code_blocks_state_cache
648
  parsed_code_blocks_state_cache = []
649
  if parsed_from_md_for_build.get("owner_md"): # Update UI owner/space name if present in MD
650
- ui_owner_name_part = parsed_from_md_for_build["owner_md"]
651
  if parsed_from_md_for_build.get("repo_name_md"):
652
- ui_space_name_part = parsed_from_md_for_build["repo_name_md"]
653
 
654
  # Rebuild cache from parsed markdown files + structure block
655
  structure_block_md = next((f for f in parsed_from_md_for_build.get("files", []) if f.get("path") == "File Structure (original)"), None)
@@ -663,7 +678,7 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
663
  })
664
 
665
  for f_info in parsed_from_md_for_build.get("files", []):
666
- if f_info.get("path") and f_info["path"] != "File Structure (original)":
667
  is_binary_repr = isinstance(f_info.get("content"), str) and (f_info["content"].startswith("[Binary file") or f_info["content"].startswith("[Error loading content:") or f_info["content"].startswith("[Binary or Skipped file]"))
668
  parsed_code_blocks_state_cache.append({
669
  "filename": f_info["path"],
@@ -676,12 +691,17 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
676
 
677
  except Exception as e:
678
  _build_status = f"Build Error: Failed to parse Markdown structure before building: {e}";
679
- yield _build_status, _iframe_html, _file_browser_update; return # Stop build on parse error
 
680
 
681
 
682
  result_message = build_logic_create_space(hf_api_key_ui, ui_space_name_part, final_owner_for_build, space_sdk_ui, formatted_markdown_content)
683
  _build_status = f"Build Process: {result_message}"
684
 
 
 
 
 
685
  if "Successfully" in result_message:
686
  # Use potentially updated owner/space name from markdown parsing
687
  sub_owner = re.sub(r'[^a-z0-9\-]+', '-', ui_owner_name_part.lower()).strip('-') or 'owner'
@@ -695,17 +715,17 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
695
  if err_list: _build_status += f"\nFile list refresh error after build: {err_list}"; _file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value="Error refreshing files")
696
  else: _file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value=None if file_list else "No files found")
697
 
698
- # Update UI with owner/space names extracted from markdown if present
699
- owner_name_output = gr.update(value=ui_owner_name_part)
700
- space_name_output = gr.update(value=ui_space_name_part)
701
-
702
-
703
  yield _build_status, _iframe_html, _file_browser_update, owner_name_output, space_name_output
704
 
705
 
706
  # File editing handlers are okay, just need to ensure they update the cache properly after commit/delete
707
  def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path):
708
- # ... (rest of this function is the same)
 
 
 
 
709
  _file_content_val, _edit_status_val, _commit_msg_val, _lang_update = "", "Error: No file selected.", gr.update(value=""), gr.update(language="python") # Reset values
710
  if not selected_file_path or selected_file_path in ["No files found", "Error loading files", "Error refreshing files"]:
711
  yield _file_content_val, "Select a file from the dropdown.", _commit_msg_val, _lang_update # Clear editor and status
@@ -745,6 +765,7 @@ def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_nam
745
  yield _file_content_val, _edit_status_val, _commit_msg_val, _lang_update
746
 
747
  def handle_commit_file_changes(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_edit_path, edited_content, commit_message):
 
748
  global parsed_code_blocks_state_cache
749
  _edit_status_val = "Processing commit..."
750
  # Initialize updates for components that might change
@@ -817,6 +838,7 @@ def handle_commit_file_changes(hf_api_key_ui, ui_space_name_part, ui_owner_name_
817
 
818
 
819
  def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path):
 
820
  global parsed_code_blocks_state_cache
821
  _edit_status_val = "Processing deletion..."
822
  # Initialize updates for components that might change/clear
@@ -885,6 +907,8 @@ def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, fi
885
  _file_browser_value_update = file_to_delete_path # Keep the file selected visually
886
 
887
  # Markdown and preview are not changed if deletion failed, keep current updates as gr.update()
 
 
888
 
889
 
890
  yield (_edit_status_val, _file_browser_choices_update, _file_browser_value_update, _file_content_editor_update, _commit_msg_update, _lang_update, _formatted_md_out, _detected_preview_out, _download_btn_out)
@@ -892,6 +916,7 @@ def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, fi
892
 
893
  # Space status handler is okay
894
  def handle_refresh_space_status(hf_api_key_ui, ui_owner_name, ui_space_name):
 
895
  # ... (rest of this function is the same)
896
  yield "*Fetching space status...*" # Initial feedback
897
  owner_to_use = ui_owner_name
@@ -1098,5 +1123,6 @@ with gr.Blocks(theme=custom_theme, css=custom_css) as demo:
1098
  # Refresh status handler is okay
1099
  refresh_status_button.click(fn=handle_refresh_space_status, inputs=[hf_api_key_input, owner_name_input, space_name_input], outputs=[space_runtime_status_display])
1100
 
 
1101
  if __name__ == "__main__":
1102
  demo.launch(debug=True, share=False)
 
35
 
36
 
37
  bbb = chr(96) * 3
38
+ # Declare the global variable at the module level where it's initialized
39
  parsed_code_blocks_state_cache = []
40
  BOT_ROLE_NAME = "assistant"
41
  # Removed GROQ_API_ENDPOINT as it's now in model_logic
 
134
  Parses a single bot message content string to find file blocks and updates the state.
135
  Assumes existing_files_state is the current state *before* this message.
136
  """
137
+ # This function takes state as an argument and returns new state, it doesn't need 'global'
138
  latest_blocks_dict = {}
139
  if existing_files_state:
140
  # Copy existing blocks, except for potential structure blocks that might be overwritten
 
194
 
195
  def _export_selected_logic(selected_filenames, space_line_name_for_md, parsed_blocks_for_export):
196
  # This function remains largely the same, using the provided parsed_blocks_for_export
197
+ # It takes state as an argument and doesn't need 'global'
198
  results = {"output_str": "", "error_message": None, "download_filepath": None}
199
  # Filter out structure blocks for file listing/export content
200
  exportable_blocks_content = [b for b in parsed_blocks_for_export if not b.get("is_structure_block") and not b.get("is_binary") and not (b.get("code", "").startswith("[Error loading content:") or b.get("code", "").startswith("[Binary or Skipped file]"))]
 
279
  if current_user_message: messages.append({"role": "user", "content": current_user_message})
280
  return messages
281
 
 
 
 
 
282
 
283
  def _generate_ui_outputs_from_cache(owner, space_name):
284
+ # Declare global at the top
285
  global parsed_code_blocks_state_cache
286
+ # This function remains largely the same, generating UI previews and the export MD
287
  preview_md_val = "*No files in cache to display.*"
288
  formatted_md_val = f"# Space: {owner}/{space_name}\n## File Structure\n{bbb}\n📁 Root\n{bbb}\n\n*No files in cache.*" if owner or space_name else "*Load or define a Space to see its Markdown structure.*"
289
  download_file = None
 
321
 
322
  # --- Refactored Chat Submit Handler ---
323
  def handle_chat_submit(user_message, chat_history, api_key_input, provider_select, model_select, system_prompt, hf_owner_name, hf_repo_name, _current_formatted_markdown):
324
+ # Declare global at the top
325
  global parsed_code_blocks_state_cache
326
  _chat_msg_in = ""
327
  _chat_hist = list(chat_history)
 
347
 
348
  for f_info in parsed_from_md.get("files", []):
349
  # Only add if it has a path and isn't the structure block representation placeholder
350
+ if f_info.get("path") and f_info["path"] != "File Structure (original)"):
351
  # Check if it's a binary representation string
352
  is_binary_repr = isinstance(f_info.get("content"), str) and (f_info["content"].startswith("[Binary file") or f_info["content"].startswith("[Error loading content:") or f_info["content"].startswith("[Binary or Skipped file]"))
353
  # Check if a block with this filename already exists in new_cache_state and replace it
 
435
  # Generate stream from model_logic
436
  for chunk in generate_stream(provider_select, model_select, api_key_override, api_msgs):
437
  if chunk is None: continue # Skip None chunks if any
438
+ if isinstance(chunk, str) and (chunk.startswith("Error: ") or chunk.startswith("API HTTP Error")):
439
  # If an error chunk is received, treat it as the final output and stop
440
  full_bot_response_content = chunk
441
  error_during_stream = chunk
442
  break # Stop processing stream
443
  else:
444
  # Accumulate response and update the last message in chat_hist
445
+ full_bot_response_content += str(chunk) # Ensure chunk is string
446
  _chat_hist[-1] = (user_message, full_bot_response_content)
447
  _status = f"Streaming from {model_select}..."
448
  # Yield update immediately after receiving chunk
 
451
  # After the stream finishes or breaks
452
  if error_during_stream:
453
  _status = error_during_stream # Set status to the error message
454
+ elif full_bot_response_content and not full_bot_response_content.startswith("Error: "): # Only parse if it's not an error message
455
  _status = f"Streaming complete. Processing files from {model_select} response..."
456
 
457
  # Pass the *current state* (updated from markdown at the start)
 
473
  _formatted_output_update, _detected_files_update, _download_btn_update = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
474
  _status = "Processing complete. Previews updated."
475
  else:
476
+ # Handle cases where the stream finished but yielded no content (e.g., filter) or only an error was yielded
477
+ if not error_during_stream:
478
+ _status = "AI response complete, but returned no content."
479
+ # Keep existing previews/markdown if no content was generated to parse or if it was an error message
480
+ _formatted_output_update, _detected_files_update, _download_btn_update = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
481
+
482
 
483
  except Exception as e:
484
  # Catch any errors that occurred *before* or *during* the stream setup/iteration
 
491
  _chat_hist.append((user_message, error_msg)) # Append as a new user/bot turn if structure unexpected
492
  _status = error_msg
493
  # Previews and markdown might not be affected by a generation error, keep existing state
494
+ _formatted_output_update, _detected_files_update, _download_btn_update = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
495
+
496
 
497
  # Final yield to update UI after all processing
498
  yield (_chat_msg_in, _chat_hist, _status, _detected_files_update, _formatted_output_update, _download_btn_update)
 
505
  return gr.update(choices=[], value=None)
506
  models = get_models_for_provider(provider_select)
507
  default_model = get_default_model_for_provider(provider_select)
508
+ # Ensure default is in choices, or pick first, or None
509
+ if default_model and default_model in models:
510
+ selected_value = default_model
511
+ elif models:
512
+ selected_value = models[0]
513
+ else:
514
+ selected_value = None
515
 
516
+ return gr.update(choices=models, value=selected_value)
517
 
518
+
519
+ # --- Existing handlers for Load, Build, Edit, Delete, Status (Mostly unchanged, just global placement) ---
520
 
521
  def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
522
+ # Declare global at the top
523
  global parsed_code_blocks_state_cache
524
  _formatted_md_val, _detected_preview_val, _status_val = "*Loading files...*", "*Loading files...*", f"Loading Space: {ui_owner_name}/{ui_space_name}..."
525
  _file_browser_update, _iframe_html_update, _download_btn_update = gr.update(visible=False, choices=[], value=None), gr.update(value=None, visible=False), gr.update(interactive=False, value=None)
 
634
 
635
 
636
  def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, space_sdk_ui, formatted_markdown_content):
637
+ # Declare global at the top
638
+ global parsed_code_blocks_state_cache
639
  # ... (this function calls build_logic_create_space and refreshes file list)
640
  _build_status, _iframe_html, _file_browser_update = "Starting space build process...", gr.update(value=None, visible=False), gr.update(visible=False, choices=[], value=None)
641
+ # Include outputs for owner/space name textboxes in the initial yield
642
+ yield _build_status, _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part) # Yield initial status
643
+ if not ui_space_name_part or "/" in ui_space_name_part: _build_status = f"Build Error: HF Space Name '{ui_space_name_part}' must be repo name only (no '/')."; yield _build_status, _iframe_html, _file_browser_update, gr.update(), gr.update(); return
644
  final_owner_for_build = ui_owner_name_part
645
  if not final_owner_for_build:
646
  token_for_whoami, token_err = build_logic_get_api_token(hf_api_key_ui)
647
+ if token_err: _build_status = f"Build Error: {token_err}"; yield _build_status, _iframe_html, _file_browser_update, gr.update(), gr.update(); return
648
  if token_for_whoami:
649
  try:
650
  user_info = build_logic_whoami(token=token_for_whoami)
 
653
  except Exception as e: _build_status += f"\n(Warning: Could not auto-detect owner for build: {e}. Please specify.)"
654
  else: _build_status += "\n(Warning: Owner not specified and no token to auto-detect for build. Please specify owner or provide a token.)"
655
 
656
+ if not final_owner_for_build: _build_status = "Build Error: HF Owner Name could not be determined. Please specify it."; yield _build_status, _iframe_html, _file_browser_update, gr.update(), gr.update(); return
657
 
658
  # Before building, parse the markdown to ensure the cache reflects exactly what's being built
659
  # This prevents inconsistencies if the user manually edited the markdown output
660
  try:
661
  parsed_from_md_for_build = build_logic_parse_markdown(formatted_markdown_content)
662
  # Replace the global cache state with the state derived from the markdown being built
 
663
  parsed_code_blocks_state_cache = []
664
  if parsed_from_md_for_build.get("owner_md"): # Update UI owner/space name if present in MD
665
+ ui_owner_name_part = parsed_from_md_for_build["owner_md"] # Use this updated value later
666
  if parsed_from_md_for_build.get("repo_name_md"):
667
+ ui_space_name_part = parsed_from_md_for_build["repo_name_md"] # Use this updated value later
668
 
669
  # Rebuild cache from parsed markdown files + structure block
670
  structure_block_md = next((f for f in parsed_from_md_for_build.get("files", []) if f.get("path") == "File Structure (original)"), None)
 
678
  })
679
 
680
  for f_info in parsed_from_md_for_build.get("files", []):
681
+ if f_info.get("path") and f_info["path"] != "File Structure (original)"):
682
  is_binary_repr = isinstance(f_info.get("content"), str) and (f_info["content"].startswith("[Binary file") or f_info["content"].startswith("[Error loading content:") or f_info["content"].startswith("[Binary or Skipped file]"))
683
  parsed_code_blocks_state_cache.append({
684
  "filename": f_info["path"],
 
691
 
692
  except Exception as e:
693
  _build_status = f"Build Error: Failed to parse Markdown structure before building: {e}";
694
+ # Yield error status, including keeping current owner/space name in textboxes
695
+ yield _build_status, _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part); return # Stop build on parse error
696
 
697
 
698
  result_message = build_logic_create_space(hf_api_key_ui, ui_space_name_part, final_owner_for_build, space_sdk_ui, formatted_markdown_content)
699
  _build_status = f"Build Process: {result_message}"
700
 
701
+ # Update UI with owner/space names extracted from markdown if present
702
+ owner_name_output = gr.update(value=ui_owner_name_part)
703
+ space_name_output = gr.update(value=ui_space_name_part)
704
+
705
  if "Successfully" in result_message:
706
  # Use potentially updated owner/space name from markdown parsing
707
  sub_owner = re.sub(r'[^a-z0-9\-]+', '-', ui_owner_name_part.lower()).strip('-') or 'owner'
 
715
  if err_list: _build_status += f"\nFile list refresh error after build: {err_list}"; _file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value="Error refreshing files")
716
  else: _file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value=None if file_list else "No files found")
717
 
718
+ # Final yield including potential updates to owner/space name textboxes
 
 
 
 
719
  yield _build_status, _iframe_html, _file_browser_update, owner_name_output, space_name_output
720
 
721
 
722
  # File editing handlers are okay, just need to ensure they update the cache properly after commit/delete
723
  def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path):
724
+ # Declare global at the top (even though it's not modified here, it's good practice if the function *might* interact with it in the future, or just for consistency)
725
+ # In this specific function, `global` isn't strictly needed as it only *reads* indirectly via _generate_ui_outputs_from_cache which handles its own global
726
+ # Keeping it here for consistency as per the error symptoms observed in similar functions.
727
+ global parsed_code_blocks_state_cache # Added global here
728
+
729
  _file_content_val, _edit_status_val, _commit_msg_val, _lang_update = "", "Error: No file selected.", gr.update(value=""), gr.update(language="python") # Reset values
730
  if not selected_file_path or selected_file_path in ["No files found", "Error loading files", "Error refreshing files"]:
731
  yield _file_content_val, "Select a file from the dropdown.", _commit_msg_val, _lang_update # Clear editor and status
 
765
  yield _file_content_val, _edit_status_val, _commit_msg_val, _lang_update
766
 
767
  def handle_commit_file_changes(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_edit_path, edited_content, commit_message):
768
+ # Declare global at the top
769
  global parsed_code_blocks_state_cache
770
  _edit_status_val = "Processing commit..."
771
  # Initialize updates for components that might change
 
838
 
839
 
840
  def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path):
841
+ # Declare global at the top
842
  global parsed_code_blocks_state_cache
843
  _edit_status_val = "Processing deletion..."
844
  # Initialize updates for components that might change/clear
 
907
  _file_browser_value_update = file_to_delete_path # Keep the file selected visually
908
 
909
  # Markdown and preview are not changed if deletion failed, keep current updates as gr.update()
910
+ # Regenerate previews to show they are unchanged
911
+ _formatted_md_out, _detected_preview_out, _download_btn_out = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name_part)
912
 
913
 
914
  yield (_edit_status_val, _file_browser_choices_update, _file_browser_value_update, _file_content_editor_update, _commit_msg_update, _lang_update, _formatted_md_out, _detected_preview_out, _download_btn_out)
 
916
 
917
  # Space status handler is okay
918
  def handle_refresh_space_status(hf_api_key_ui, ui_owner_name, ui_space_name):
919
+ # This function doesn't modify the global cache, so no global declaration needed.
920
  # ... (rest of this function is the same)
921
  yield "*Fetching space status...*" # Initial feedback
922
  owner_to_use = ui_owner_name
 
1123
  # Refresh status handler is okay
1124
  refresh_status_button.click(fn=handle_refresh_space_status, inputs=[hf_api_key_input, owner_name_input, space_name_input], outputs=[space_runtime_status_display])
1125
 
1126
+
1127
  if __name__ == "__main__":
1128
  demo.launch(debug=True, share=False)