broadfield-dev commited on
Commit
ebd9ab7
·
verified ·
1 Parent(s): 63c0586

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +374 -139
app.py CHANGED
@@ -16,11 +16,14 @@ from build_logic import (
16
  parse_markdown as build_logic_parse_markdown,
17
  delete_space_file as build_logic_delete_space_file,
18
  get_space_runtime_status,
19
- apply_staged_changes,
20
  duplicate_space as build_logic_duplicate_space,
21
  list_user_spaces as build_logic_list_user_spaces,
22
  build_logic_set_space_privacy,
23
- build_logic_delete_space
 
 
 
24
  )
25
 
26
  from model_logic import (
@@ -77,8 +80,11 @@ Available commands:
77
  - `DELETE_FILE path/to/file.ext`: Deletes a specific file from the current space.
78
  - `SET_PRIVATE <true|false>`: Sets the privacy for the current space.
79
  - `DELETE_SPACE`: Deletes the entire current space. THIS IS PERMANENT AND REQUIRES CAUTION. Only use this if the user explicitly and clearly asks to delete the space.
 
 
 
80
 
81
- You can issue multiple file updates and action commands in a single response. The system will process all of them into a single change plan. Note that the `DUPLICATE_SPACE` command, if present, will override any other file or space actions in the same response.
82
 
83
  **Current Space Context:**
84
  You will be provided with the current state of the files in the Space the user is interacting with. Use this information to understand the current project structure and content before proposing changes or actions. This context will appear after the user's message, starting with "## Current Space Context:". Do NOT include this context in your response. Only generate your response based on the user's request and the formatting rules above.
@@ -88,7 +94,7 @@ If no code or actions are requested, respond conversationally and help the user
88
 
89
  def escape_html_for_markdown(text):
90
  if not isinstance(text, str): return ""
91
- return text.replace("&", "&").replace("<", "<").replace(">", ">")
92
 
93
  def _infer_lang_from_filename(filename):
94
  if not filename: return "plaintext"
@@ -264,7 +270,6 @@ def generate_and_stage_changes(ai_response_content, current_files_state, hf_owne
264
  ai_proposed_files_dict = {f["path"]: f for f in ai_proposed_files_list}
265
 
266
  action_pattern = re.compile(r"### HF_ACTION:\s*(?P<command_line>[^\n]+)", re.MULTILINE)
267
- # Collect all potential actions first
268
  potential_actions = []
269
  for match in action_pattern.finditer(ai_response_content):
270
  try:
@@ -276,11 +281,9 @@ def generate_and_stage_changes(ai_response_content, current_files_state, hf_owne
276
  print(f"Error parsing HF_ACTION line '{match.group('command_line').strip()}': {e}")
277
 
278
 
279
- # Check for exclusive actions (like DUPLICATE_SPACE)
280
  duplicate_action = next((act for act in potential_actions if act["command"] == "DUPLICATE_SPACE"), None)
281
 
282
  if duplicate_action:
283
- # If DUPLICATE_SPACE is present, only stage that action
284
  cmd_parts = duplicate_action["args"]
285
  if cmd_parts:
286
  target_repo_id = cmd_parts[0]
@@ -289,7 +292,6 @@ def generate_and_stage_changes(ai_response_content, current_files_state, hf_owne
289
  try: private_str = cmd_parts[cmd_parts.index('--private') + 1].lower()
290
  except IndexError: print("Warning: DUPLICATE_SPACE --private requires an argument.")
291
  else: private = private_str == 'true'
292
- # Source repo is the currently loaded one
293
  source_repo_id = f"{hf_owner_name}/{hf_repo_name}" if hf_owner_name and hf_repo_name else None
294
 
295
  if source_repo_id:
@@ -302,29 +304,24 @@ def generate_and_stage_changes(ai_response_content, current_files_state, hf_owne
302
  print(f"Staged exclusive DUPLICATE_SPACE action from {source_repo_id} to {target_repo_id}")
303
  else:
304
  print(f"Warning: Cannot stage DUPLICATE_SPACE action, no Space currently loaded.")
305
- # Maybe add an error message to the summary?
306
  changeset.append({"type": "Error", "message": "Cannot duplicate space, no Space currently loaded."})
307
-
308
  else:
309
  print(f"Warning: DUPLICATE_SPACE action requires a target repo_id.")
310
  changeset.append({"type": "Error", "message": "DUPLICATE_SPACE action requires a target repo_id (<new_owner>/<new_repo_name>)."})
311
 
312
- # If duplication is staged (or failed to stage due to syntax/missing source), skip other actions
313
  if changeset:
314
  md_summary = ["### 📋 Proposed Changes Plan (Exclusive Action)\n"]
315
  if changeset[0]["type"] == "DUPLICATE_SPACE":
316
  change = changeset[0]
317
  md_summary.append(f"- **📂 Duplicate Space:** Duplicate `{change.get('source_repo_id', '...')}` to `{change.get('target_repo_id', '...')}` (Private: {change.get('private', False)})")
318
  md_summary.append("\n**Warning:** This will overwrite the target space if it exists. No other file or space actions proposed by the AI in this turn will be applied.")
319
- else: # Handle the staging error case
320
  md_summary.append(f"- **Error staging DUPLICATE_SPACE:** {changeset[0].get('message', 'Unknown error')}")
321
  md_summary.append("\nNo changes were staged due to the error in the DUPLICATE_SPACE command.")
322
- changeset = [] # Clear changeset if the duplicate action itself was malformed/blocked
323
 
324
  return changeset, "\n".join(md_summary)
325
 
326
-
327
- # If no exclusive action, process other actions and file changes
328
  for action in potential_actions:
329
  command, args = action["command"], action["args"]
330
 
@@ -355,6 +352,70 @@ def generate_and_stage_changes(ai_response_content, current_files_state, hf_owne
355
  elif command == "DELETE_SPACE":
356
  changeset.append({"type": "DELETE_SPACE", "owner": hf_owner_name, "space_name": hf_repo_name})
357
  print(f"Staged DELETE_SPACE action for {hf_owner_name}/{hf_repo_name}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  # Note: DUPLICATE_SPACE is handled before this loop
359
 
360
 
@@ -399,6 +460,18 @@ def generate_and_stage_changes(ai_response_content, current_files_state, hf_owne
399
  md_summary.append(f"- **🔒 Set Privacy:** Set `{change.get('repo_id', '...')}` to `private={change.get('private', False)}`")
400
  elif change["type"] == "DELETE_SPACE":
401
  md_summary.append(f"- **💥 DELETE ENTIRE SPACE:** `{change.get('owner', '...')}/{change.get('space_name', '...')}` **(DESTRUCTIVE ACTION)**")
 
 
 
 
 
 
 
 
 
 
 
 
402
  md_summary.append("")
403
 
404
  if file_changes:
@@ -525,17 +598,22 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
525
  global parsed_code_blocks_state_cache
526
 
527
  _status = "Applying changes..."
 
528
  yield _status, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="*Applying changes...*")
 
 
529
 
530
  if not changeset:
531
  return "No changes to apply.", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="No changes were staged.")
532
 
533
  # Check if the first action is an exclusive action like DUPLICATE_SPACE
534
- first_action_type = changeset[0].get('type') if changeset else None
535
- is_exclusive_action = first_action_type == 'DUPLICATE_SPACE'
 
 
536
 
537
- if is_exclusive_action:
538
- change = changeset[0]
539
  if change.get("source_repo_id") and change.get("target_repo_id"):
540
  status_message = build_logic_duplicate_space(
541
  hf_api_key=hf_api_key,
@@ -545,21 +623,27 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
545
  )
546
  # After duplication, attempt to load the *new* space
547
  _status_reload = f"{status_message} | Attempting to load the new Space [{change['target_repo_id']}]..."
548
- yield _status_reload, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="*Loading new Space...*")
549
 
550
  # Extract new owner and space name from target_repo_id
551
- new_owner, new_space_name = change["target_repo_id"].split('/', 1) if '/' in change["target_repo_id"] else (None, change["target_repo_id"])
552
- if not new_owner: # Try to auto-detect owner if target was just a name
 
 
 
553
  token, token_err = build_logic_get_api_token(hf_api_key)
554
- if token_err: new_owner = None # Cannot auto-detect
 
 
555
  else:
556
  try: user_info = build_logic_whoami(token=token); new_owner = user_info.get('name')
557
- except: new_owner = None
 
 
558
 
559
- # Trigger the load logic for the new space
 
560
  if new_owner and new_space_name:
561
- # Call the load handler directly or replicate its logic
562
- # Replicating is safer for yield chains
563
  sdk, file_list, err_list = get_space_repository_info(hf_api_key, new_space_name, new_owner)
564
  if err_list:
565
  reload_error = f"Error reloading file list after duplication: {err_list}"
@@ -577,29 +661,42 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
577
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(new_owner, new_space_name)
578
  final_overall_status = status_message + (f" | Reload Status: {reload_error}" if reload_error else f" | Reload Status: New Space [{new_owner}/{new_space_name}] state refreshed.")
579
 
580
- # Update UI fields to reflect the newly loaded space
581
  owner_update = gr.update(value=new_owner)
582
  space_update = gr.update(value=new_space_name)
583
  file_browser_update = gr.update(visible=True, choices=sorted([f["filename"] for f in parsed_code_blocks_state_cache if not f.get("is_structure_block")] or []), value=None)
584
 
585
- # Update iframe preview for the new space
586
  if new_owner and new_space_name:
587
- sub_owner = re.sub(r'[^a-z0-9\-]+', '-', new_owner.lower()).strip('-') or 'owner'
588
- sub_repo = re.sub(r'[^a-z0-9\-]+', '-', new_space_name.lower()).strip('-') or 'space'
589
- iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if sdk == 'static' else '.hf.space'}"
590
- iframe_update = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="500px"></iframe>', visible=True)
591
  else:
592
  iframe_update = gr.update(value=None, visible=False)
593
 
 
 
 
 
 
 
 
 
 
 
 
594
 
595
  else:
596
  reload_error = "Cannot load new Space state: Owner or Space Name missing after duplication."
597
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(None, None) # Clear previews if new space couldn't be loaded
598
  final_overall_status = status_message + f" | Reload Status: {reload_error}"
599
- owner_update = gr.update() # Keep old value
600
- space_update = gr.update() # Keep old value
601
- file_browser_update = gr.update(visible=False, choices=[], value=None) # Clear file browser
602
- iframe_update = gr.update(value=None, visible=False) # Hide iframe
 
 
 
 
603
 
604
 
605
  else:
@@ -607,27 +704,136 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
607
  final_overall_status = "Duplicate Action Error: Invalid duplicate changeset format."
608
  # No state change, just report error
609
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_name, space_name)
610
- owner_update = gr.update()
611
- space_update = gr.update()
612
- file_browser_update = gr.update()
613
- iframe_update = gr.update()
614
-
615
-
616
- cleared_changeset = []
617
- # Return updates including potentially new space info and file list
618
- yield (
619
- final_overall_status,
620
- gr.update(value=_formatted), gr.update(value=_detected), _download,
621
- gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), # Hide confirm UI
622
- cleared_changeset, gr.update(value="*No changes proposed.*"), # Clear changeset state and display
623
- owner_update, space_update, file_browser_update, iframe_update # Update space/owner fields, file browser, iframe
624
- )
625
-
626
- else: # Not an exclusive action, proceed with standard apply_staged_changes
627
- status_message = apply_staged_changes(hf_api_key, owner_name, space_name, changeset)
628
-
629
- _status_reload = f"{status_message} | Reloading Space state..."
630
- yield _status_reload, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="*Reloading Space state...*")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
 
632
  refreshed_file_list = []
633
  reload_error = None
@@ -649,9 +855,7 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
649
  loaded_files.append({"filename": file_path, "code": code, "language": lang, "is_binary": is_binary, "is_structure_block": False})
650
  parsed_code_blocks_state_cache = loaded_files
651
 
652
- # Update file browser dropdown with refreshed list
653
  file_browser_update = gr.update(visible=True, choices=sorted(refreshed_file_list or []), value=None)
654
- # Update iframe preview for the current space (might not have changed URL, but status may update)
655
  if owner_name and space_name:
656
  sub_owner = re.sub(r'[^a-z0-9\-]+', '-', owner_name.lower()).strip('-') or 'owner'
657
  sub_repo = re.sub(r'[^a-z0-9\-]+', '-', space_name.lower()).strip('-') or 'space'
@@ -660,28 +864,29 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
660
  else:
661
  iframe_update = gr.update(value=None, visible=False)
662
 
 
 
 
 
663
  else:
664
  reload_error = "Cannot reload Space state: Owner or Space Name missing."
665
- # Clear UI elements related to files if reload fails
666
  file_browser_update = gr.update(visible=False, choices=[], value=None)
667
  iframe_update = gr.update(value=None, visible=False)
 
668
 
669
 
670
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_name, space_name)
671
 
672
- final_overall_status = status_message + (f" | Reload Status: {reload_error}" if reload_error else " | Reload Status: Space state refreshed.")
673
 
674
  cleared_changeset = []
675
 
676
- # Return updated UI elements and hide confirmation UI
677
  yield (
678
- final_overall_status,
679
- gr.update(value=_formatted),
680
- gr.update(value=_detected),
681
- _download,
682
- gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), # Hide confirm UI
683
- cleared_changeset, gr.update(value="*No changes proposed.*"), # Clear changeset state and display
684
- gr.update(), gr.update(), file_browser_update, iframe_update # Keep owner/space, update file browser and iframe
685
  )
686
 
687
 
@@ -708,35 +913,34 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
708
  global parsed_code_blocks_state_cache
709
  _formatted_md_val, _detected_preview_val, _status_val = "*Loading files...*", "*Loading files...*", f"Loading Space: {ui_owner_name}/{ui_space_name}..."
710
  _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)
711
- _build_status_clear, _edit_status_clear, _runtime_status_clear = "*Manual build status...*", "*Select a file...*", "*Runtime status...*"
712
  _changeset_clear = []
713
  _changeset_summary_clear = "*No changes proposed.*"
714
  _confirm_ui_hidden = gr.update(visible=False)
715
  _list_spaces_display_clear = "*List of spaces will appear here.*"
716
 
717
 
718
- # Initial yield to show loading state
719
  yield (
720
  gr.update(value=_formatted_md_val), gr.update(value=_detected_preview_val), gr.update(value=_status_val), _file_browser_update,
721
  gr.update(value=ui_owner_name), gr.update(value=ui_space_name),
722
  _iframe_html_update, _download_btn_update, gr.update(value=_build_status_clear),
723
  gr.update(value=_edit_status_clear), gr.update(value=_runtime_status_clear),
724
  _changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
725
- gr.update(), gr.update(), gr.update() # For list spaces updates
726
  )
727
 
728
  owner_to_use = ui_owner_name
729
  token, token_err = build_logic_get_api_token(hf_api_key_ui)
730
  if token_err:
731
  _status_val = f"Load Error: {token_err}"
732
- yield gr.update(value=_status_val), # Only update status on early error
733
  return
734
  if not owner_to_use:
735
  try:
736
  user_info = build_logic_whoami(token=token)
737
  owner_to_use = user_info.get('name')
738
  if not owner_to_use: raise Exception("Could not find user name from token.")
739
- yield gr.update(value=owner_to_use), gr.update(value=f"Loading Space: {owner_to_use}/{ui_space_name} (Auto-detected owner)...") # Update owner and status
740
  except Exception as e:
741
  _status_val = f"Load Error: Error auto-detecting owner: {e}"
742
  yield gr.update(value=_status_val),
@@ -749,7 +953,6 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
749
 
750
  sdk, file_list, err = get_space_repository_info(hf_api_key_ui, ui_space_name, owner_to_use)
751
 
752
- # Always update owner/space inputs even on error
753
  yield gr.update(value=owner_to_use), gr.update(value=ui_space_name),
754
 
755
  if err:
@@ -761,7 +964,7 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
761
  gr.update(visible=False, choices=[], value=None),
762
  gr.update(), gr.update(),
763
  gr.update(value=None, visible=False),
764
- _download, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
765
  )
766
  return
767
 
@@ -788,17 +991,14 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
788
  else:
789
  iframe_update = gr.update(value=None, visible=False)
790
 
791
- # Also fetch and display runtime status after loading
792
  runtime_status_md = handle_refresh_space_status(hf_api_key_ui, owner_to_use, ui_space_name)
793
 
794
-
795
- # Final yield after success
796
  yield (
797
  gr.update(value=_formatted), gr.update(value=_detected), gr.update(value=_status_val),
798
  file_browser_update,
799
  gr.update(), gr.update(),
800
  iframe_update,
801
- _download, gr.update(), gr.update(), gr.update(value=runtime_status_md), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
802
  )
803
 
804
  def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, space_sdk_ui, is_private_ui, formatted_markdown_content):
@@ -809,14 +1009,14 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
809
  _changeset_summary_clear = "*Manual build initiated, changes plan cleared.*"
810
  _confirm_ui_hidden = gr.update(visible=False)
811
 
812
- yield (_build_status, _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part),
813
  _changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
814
- gr.update(), gr.update(), gr.update())
815
 
816
 
817
  if not ui_space_name_part or "/" in ui_space_name_part:
818
  _build_status = f"Build Error: Invalid Space Name '{ui_space_name_part}'."
819
- yield gr.update(value=_build_status), # Only update status
820
  return
821
 
822
  parsed_content = build_logic_parse_markdown(formatted_markdown_content)
@@ -833,8 +1033,42 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
833
  yield gr.update(value=_build_status),
834
  return
835
 
836
- result_message = apply_staged_changes(hf_api_key_ui, ui_owner_name_part, ui_space_name_part, manual_changeset)
837
- _build_status = f"Manual Build/Update Result: {result_message}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
838
 
839
  owner_to_use = ui_owner_name_part
840
  space_to_use = ui_space_name_part
@@ -846,7 +1080,7 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
846
  gr.update(value=_build_status), _iframe_html, _file_browser_update,
847
  gr.update(value=owner_to_use), gr.update(value=space_to_use),
848
  _changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
849
- gr.update(value=_formatted_md), gr.update(value=_detected_preview_val), _download_btn_update
850
  )
851
 
852
  sdk_built, file_list, err_list = get_space_repository_info(hf_api_key_ui, space_to_use, owner_to_use)
@@ -876,12 +1110,8 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
876
  else:
877
  _iframe_html_update = gr.update(value=None, visible=False)
878
 
879
- _formatted_md, _detected_preview, _download = _generate_ui_outputs_from_cache(owner_to_use, space_to_use)
880
-
881
- # Also fetch and display runtime status after build
882
  runtime_status_md = handle_refresh_space_status(hf_api_key_ui, owner_to_use, space_to_use)
883
 
884
-
885
  yield (
886
  gr.update(value=_build_status), _iframe_html_update, _file_browser_update,
887
  gr.update(value=owner_to_use), gr.update(value=space_to_use),
@@ -976,30 +1206,44 @@ def handle_refresh_space_status(hf_api_key_ui, ui_owner_name, ui_space_name):
976
  return "**Status Error:** Owner and Space Name must be provided to get status."
977
 
978
  status_details, err = get_space_runtime_status(hf_api_key_ui, ui_space_name, ui_owner_name)
979
- if err: return f"**Status Error:** {err}"
980
- if not status_details: return "*Could not retrieve status details.*"
981
-
982
- md = f"### Status for {ui_owner_name}/{ui_space_name}\n"
983
- md += f"- **Stage:** `{status_details.get('stage', 'N/A')}`\n"
984
- md += f"- **Status:** `{status_details.get('status', 'N/A')}`\n"
985
- md += f"- **Hardware:** `{status_details.get('hardware', 'N/A')}`\n"
986
- requested_hw = status_details.get('requested_hardware')
987
- if requested_hw: md += f"- **Requested Hardware:** `{requested_hw}`\n"
988
- error_msg = status_details.get('error_message')
989
- if error_msg: md += f"- **Error:** `{escape_html_for_markdown(error_msg)}`\n"
990
- log_link = status_details.get('full_log_link')
991
- if log_link and log_link != "#": md += f"- [View Full Logs]({log_link})\n"
992
-
993
- # Get general repo info as well
994
- sdk, file_list, repo_info_err = get_space_repository_info(hf_api_key_ui, ui_space_name, ui_owner_name)
 
 
 
 
 
 
 
 
 
 
 
 
995
  if repo_info_err:
996
- md += f"- **Repo Info Error:** {repo_info_err}\n"
 
 
 
997
  else:
998
- md += f"- **SDK:** `{sdk or 'N/A'}`\n"
999
- md += f"- **File Count:** `{len(file_list) if file_list is not None else 'N/A'}`\n"
1000
- # Add more repo info if needed from get_space_repository_info result
1001
 
1002
- return md
 
1003
 
1004
  def handle_list_spaces(hf_api_key_ui, ui_owner_name):
1005
  token, token_err = build_logic_get_api_token(hf_api_key_ui)
@@ -1033,38 +1277,36 @@ def handle_list_spaces(hf_api_key_ui, ui_owner_name):
1033
  return md
1034
 
1035
  def handle_manual_duplicate_space(hf_api_key_ui, source_owner, source_space_name, target_owner, target_space_name, target_private):
1036
- global parsed_code_blocks_state_cache
1037
  if not source_owner or not source_space_name:
1038
- return "Duplicate Error: Please load a Space first to duplicate.", gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1039
  if not target_owner or not target_space_name:
1040
- return "Duplicate Error: Target Owner and Target Space Name are required.", gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1041
  if "/" in target_space_name:
1042
- return "Duplicate Error: Target Space Name should not contain '/'. Use Target Owner field for the owner part.", gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1043
 
1044
 
1045
  source_repo_id = f"{source_owner}/{source_space_name}"
1046
  target_repo_id = f"{target_owner}/{target_space_name}"
1047
 
1048
  status_msg = f"Attempting to duplicate `{source_repo_id}` to `{target_repo_id}`..."
1049
- yield status_msg, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update() # Update status immediately
 
 
1050
 
1051
  result_message = build_logic_duplicate_space(hf_api_key_ui, source_repo_id, target_repo_id, target_private)
1052
  status_msg = f"Duplication Result: {result_message}"
1053
 
1054
- # Attempt to load the new space after duplication attempt
1055
  _status_reload = f"{status_msg} | Attempting to load the new Space [{target_repo_id}]..."
1056
- yield _status_reload, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1057
 
1058
- # Replicate load logic for the new space
1059
- # Note: This replicates parts of handle_load_existing_space, could refactor common parts
1060
- new_owner = target_owner # Use the provided target owner
1061
- new_space_name = target_space_name # Use the provided target space name
1062
 
1063
  sdk, file_list, err_list = get_space_repository_info(hf_api_key_ui, new_space_name, new_owner)
1064
 
1065
  if err_list:
1066
  reload_error = f"Error reloading file list after duplication: {err_list}"
1067
-
1068
  parsed_code_blocks_state_cache = []
1069
  _file_browser_update = gr.update(visible=False, choices=[], value=None)
1070
  _iframe_html_update = gr.update(value=None, visible=False)
@@ -1076,6 +1318,7 @@ def handle_manual_duplicate_space(hf_api_key_ui, source_owner, source_space_name
1076
  is_binary = lang == "binary" or (err_get is not None)
1077
  code = f"[Error loading content: {err_get}]" if err_get else (content or "")
1078
  loaded_files.append({"filename": file_path, "code": code, "language": lang, "is_binary": is_binary, "is_structure_block": False})
 
1079
  parsed_code_blocks_state_cache = loaded_files
1080
 
1081
  _file_browser_update = gr.update(visible=True, choices=sorted([f["filename"] for f in parsed_code_blocks_state_cache if not f.get("is_structure_block")] or []), value=None)
@@ -1087,22 +1330,19 @@ def handle_manual_duplicate_space(hf_api_key_ui, source_owner, source_space_name
1087
  else:
1088
  _iframe_html_update = gr.update(value=None, visible=False)
1089
 
1090
-
1091
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(new_owner, new_space_name)
1092
  final_overall_status = status_msg + (f" | Reload Status: {reload_error}" if reload_error else f" | Reload Status: New Space [{new_owner}/{new_space_name}] state refreshed.")
1093
 
1094
- # Fetch runtime status for the new space
1095
  runtime_status_md = handle_refresh_space_status(hf_api_key_ui, new_owner, new_space_name)
1096
 
1097
- # Update UI fields to reflect the newly loaded space
1098
  owner_update = gr.update(value=new_owner)
1099
  space_update = gr.update(value=new_space_name)
1100
 
1101
 
1102
- return (
1103
- final_overall_status,
1104
  owner_update, space_update,
1105
- _formatted, _detected, _download,
1106
  _file_browser_update, _iframe_html_update, gr.update(value=runtime_status_md)
1107
  )
1108
 
@@ -1252,12 +1492,11 @@ with gr.Blocks(theme=custom_theme, css=custom_css) as demo:
1252
  send_chat_button.click(handle_chat_submit, inputs=chat_inputs, outputs=chat_outputs)
1253
  chat_message_input.submit(handle_chat_submit, inputs=chat_inputs, outputs=chat_outputs)
1254
 
1255
- # Note: handle_confirm_changes yields multiple sets of outputs for intermediate steps
1256
  confirm_inputs = [hf_api_key_input, owner_name_input, space_name_input, changeset_state]
1257
  confirm_outputs = [
1258
  status_output, formatted_space_output_display, detected_files_preview, download_button,
1259
  confirm_accordion, confirm_button, cancel_button, changeset_state, changeset_display,
1260
- owner_name_input, space_name_input, file_browser_dropdown, space_iframe_display # Added outputs for DUPLICATE_SPACE load
1261
  ]
1262
  confirm_button.click(handle_confirm_changes, inputs=confirm_inputs, outputs=confirm_outputs)
1263
 
@@ -1267,14 +1506,13 @@ with gr.Blocks(theme=custom_theme, css=custom_css) as demo:
1267
  ]
1268
  cancel_button.click(handle_cancel_changes, inputs=None, outputs=cancel_outputs)
1269
 
1270
- # Note: handle_load_existing_space yields multiple sets of outputs for intermediate steps
1271
  load_space_outputs = [
1272
  formatted_space_output_display, detected_files_preview, status_output,
1273
  file_browser_dropdown, owner_name_input, space_name_input,
1274
  space_iframe_display, download_button, build_status_display,
1275
  edit_status_display, space_runtime_status_display,
1276
  changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button,
1277
- target_owner_input, target_space_name_input, target_private_checkbox # Added outputs for List Spaces
1278
  ]
1279
  load_space_button.click(
1280
  fn=handle_load_existing_space,
@@ -1282,12 +1520,11 @@ with gr.Blocks(theme=custom_theme, css=custom_css) as demo:
1282
  outputs=load_space_outputs
1283
  )
1284
 
1285
- # Note: handle_build_space_button yields multiple sets of outputs for intermediate steps
1286
  build_outputs = [
1287
  build_status_display, space_iframe_display, file_browser_dropdown,
1288
  owner_name_input, space_name_input,
1289
  changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button,
1290
- formatted_space_output_display, detected_files_preview, download_button, space_runtime_status_display # Added runtime status output
1291
  ]
1292
  build_inputs = [
1293
  hf_api_key_input, space_name_input, owner_name_input, space_sdk_select,
@@ -1310,16 +1547,14 @@ with gr.Blocks(theme=custom_theme, css=custom_css) as demo:
1310
 
1311
  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])
1312
 
1313
- # Manual Duplicate Space Button logic - Note it yields
1314
  manual_duplicate_inputs = [hf_api_key_input, owner_name_input, space_name_input, target_owner_input, target_space_name_input, target_private_checkbox]
1315
  manual_duplicate_outputs = [
1316
  status_output, owner_name_input, space_name_input,
1317
  formatted_space_output_display, detected_files_preview, download_button,
1318
- file_browser_dropdown, space_iframe_display, space_runtime_status_display # Outputs needed after loading the new space
1319
  ]
1320
  duplicate_space_button.click(fn=handle_manual_duplicate_space, inputs=manual_duplicate_inputs, outputs=manual_duplicate_outputs)
1321
 
1322
- # List Spaces Button logic
1323
  list_spaces_button.click(fn=handle_list_spaces, inputs=[hf_api_key_input, owner_name_input], outputs=[list_spaces_display])
1324
 
1325
 
 
16
  parse_markdown as build_logic_parse_markdown,
17
  delete_space_file as build_logic_delete_space_file,
18
  get_space_runtime_status,
19
+ apply_staged_file_changes, # Renamed to indicate it only handles files
20
  duplicate_space as build_logic_duplicate_space,
21
  list_user_spaces as build_logic_list_user_spaces,
22
  build_logic_set_space_privacy,
23
+ build_logic_delete_space,
24
+ build_logic_create_pull_request,
25
+ build_logic_add_comment,
26
+ build_logic_like_space
27
  )
28
 
29
  from model_logic import (
 
80
  - `DELETE_FILE path/to/file.ext`: Deletes a specific file from the current space.
81
  - `SET_PRIVATE <true|false>`: Sets the privacy for the current space.
82
  - `DELETE_SPACE`: Deletes the entire current space. THIS IS PERMANENT AND REQUIRES CAUTION. Only use this if the user explicitly and clearly asks to delete the space.
83
+ - `CREATE_PR <target_repo_id> --title "..." --body "..."`: Creates a Pull Request from the current space's main branch to the target repository (e.g., a model or dataset repo) main branch. Requires `--title` and `--body` arguments (quoted strings).
84
+ - `ADD_COMMENT "Your comment text"`: Adds a comment to the currently loaded space. Requires a quoted string comment text.
85
+ - `LIKE_SPACE`: Likes the currently loaded space.
86
 
87
+ You can issue multiple file updates and action commands in a single response. The system will process all of them into a single change plan. Note that the `DUPLICATE_SPACE` command, if present, will override any other file or space actions in the same response. Actions other than file changes are executed sequentially before any file changes are committed.
88
 
89
  **Current Space Context:**
90
  You will be provided with the current state of the files in the Space the user is interacting with. Use this information to understand the current project structure and content before proposing changes or actions. This context will appear after the user's message, starting with "## Current Space Context:". Do NOT include this context in your response. Only generate your response based on the user's request and the formatting rules above.
 
94
 
95
  def escape_html_for_markdown(text):
96
  if not isinstance(text, str): return ""
97
+ return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
98
 
99
  def _infer_lang_from_filename(filename):
100
  if not filename: return "plaintext"
 
270
  ai_proposed_files_dict = {f["path"]: f for f in ai_proposed_files_list}
271
 
272
  action_pattern = re.compile(r"### HF_ACTION:\s*(?P<command_line>[^\n]+)", re.MULTILINE)
 
273
  potential_actions = []
274
  for match in action_pattern.finditer(ai_response_content):
275
  try:
 
281
  print(f"Error parsing HF_ACTION line '{match.group('command_line').strip()}': {e}")
282
 
283
 
 
284
  duplicate_action = next((act for act in potential_actions if act["command"] == "DUPLICATE_SPACE"), None)
285
 
286
  if duplicate_action:
 
287
  cmd_parts = duplicate_action["args"]
288
  if cmd_parts:
289
  target_repo_id = cmd_parts[0]
 
292
  try: private_str = cmd_parts[cmd_parts.index('--private') + 1].lower()
293
  except IndexError: print("Warning: DUPLICATE_SPACE --private requires an argument.")
294
  else: private = private_str == 'true'
 
295
  source_repo_id = f"{hf_owner_name}/{hf_repo_name}" if hf_owner_name and hf_repo_name else None
296
 
297
  if source_repo_id:
 
304
  print(f"Staged exclusive DUPLICATE_SPACE action from {source_repo_id} to {target_repo_id}")
305
  else:
306
  print(f"Warning: Cannot stage DUPLICATE_SPACE action, no Space currently loaded.")
 
307
  changeset.append({"type": "Error", "message": "Cannot duplicate space, no Space currently loaded."})
 
308
  else:
309
  print(f"Warning: DUPLICATE_SPACE action requires a target repo_id.")
310
  changeset.append({"type": "Error", "message": "DUPLICATE_SPACE action requires a target repo_id (<new_owner>/<new_repo_name>)."})
311
 
 
312
  if changeset:
313
  md_summary = ["### 📋 Proposed Changes Plan (Exclusive Action)\n"]
314
  if changeset[0]["type"] == "DUPLICATE_SPACE":
315
  change = changeset[0]
316
  md_summary.append(f"- **📂 Duplicate Space:** Duplicate `{change.get('source_repo_id', '...')}` to `{change.get('target_repo_id', '...')}` (Private: {change.get('private', False)})")
317
  md_summary.append("\n**Warning:** This will overwrite the target space if it exists. No other file or space actions proposed by the AI in this turn will be applied.")
318
+ else:
319
  md_summary.append(f"- **Error staging DUPLICATE_SPACE:** {changeset[0].get('message', 'Unknown error')}")
320
  md_summary.append("\nNo changes were staged due to the error in the DUPLICATE_SPACE command.")
321
+ changeset = []
322
 
323
  return changeset, "\n".join(md_summary)
324
 
 
 
325
  for action in potential_actions:
326
  command, args = action["command"], action["args"]
327
 
 
352
  elif command == "DELETE_SPACE":
353
  changeset.append({"type": "DELETE_SPACE", "owner": hf_owner_name, "space_name": hf_repo_name})
354
  print(f"Staged DELETE_SPACE action for {hf_owner_name}/{hf_repo_name}")
355
+
356
+ elif command == "CREATE_PR" and args:
357
+ # Expecting: CREATE_PR <target_repo_id> --title "..." --body "..."
358
+ target_repo_id = None
359
+ title = ""
360
+ body = ""
361
+ try:
362
+ target_repo_id = args[0]
363
+ arg_string = shlex.join(args[1:]) # Rejoin remaining args as a string to parse options
364
+
365
+ title_match = re.search(r'--title\s+"([^"]*)"', arg_string)
366
+ if title_match: title = title_match.group(1)
367
+ else: print("Warning: CREATE_PR missing --title argument.")
368
+
369
+ body_match = re.search(r'--body\s+"([^"]*)"', arg_string)
370
+ if body_match: body = body_match.group(1)
371
+ else: print("Warning: CREATE_PR missing --body argument.")
372
+
373
+ if target_repo_id and title: # Require target and title
374
+ changeset.append({"type": "CREATE_PR", "source_repo_id": f"{hf_owner_name}/{hf_repo_name}", "target_repo_id": target_repo_id, "title": title, "body": body})
375
+ print(f"Staged CREATE_PR action to {target_repo_id}")
376
+ else:
377
+ print("Warning: CREATE_PR requires target_repo_id and --title.")
378
+ changeset.append({"type": "Error", "message": "CREATE_PR action requires target_repo_id and --title arguments."})
379
+ except Exception as e:
380
+ print(f"Error parsing CREATE_PR arguments: {e}")
381
+ changeset.append({"type": "Error", "message": f"Error parsing CREATE_PR arguments: {e}"})
382
+
383
+
384
+ elif command == "ADD_COMMENT" and args:
385
+ # Expecting: ADD_COMMENT "Your comment text"
386
+ comment_text = None
387
+ try:
388
+ # Attempt to parse the rest of the line as a single quoted string
389
+ # Rejoin args and then try to find the quoted string
390
+ comment_string = shlex.join(args)
391
+ comment_match = re.match(r'^"([^"]*)"$', comment_string) # Look for the whole string being a quoted value
392
+ if comment_match: comment_text = comment_match.group(1)
393
+ else:
394
+ # Fallback: just use the raw args joined by space if not a perfect quoted string
395
+ comment_text = " ".join(args)
396
+ print(f"Warning: ADD_COMMENT argument was not a single quoted string, using raw args: '{comment_text}'")
397
+
398
+ if comment_text:
399
+ changeset.append({"type": "ADD_COMMENT", "repo_id": f"{hf_owner_name}/{hf_repo_name}", "comment": comment_text})
400
+ print(f"Staged ADD_COMMENT action on {hf_owner_name}/{hf_repo_name}")
401
+ else:
402
+ print("Warning: ADD_COMMENT requires comment text.")
403
+ changeset.append({"type": "Error", "message": "ADD_COMMENT action requires comment text (in quotes)."})
404
+
405
+ except Exception as e:
406
+ print(f"Error parsing ADD_COMMENT arguments: {e}")
407
+ changeset.append({"type": "Error", "message": f"Error parsing ADD_COMMENT arguments: {e}"})
408
+
409
+
410
+ elif command == "LIKE_SPACE":
411
+ # No args expected
412
+ if hf_owner_name and hf_repo_name:
413
+ changeset.append({"type": "LIKE_SPACE", "repo_id": f"{hf_owner_name}/{hf_repo_name}"})
414
+ print(f"Staged LIKE_SPACE action on {hf_owner_name}/{hf_repo_name}")
415
+ else:
416
+ print(f"Warning: Cannot stage LIKE_SPACE action, no Space currently loaded.")
417
+ changeset.append({"type": "Error", "message": "Cannot like space, no Space currently loaded."})
418
+
419
  # Note: DUPLICATE_SPACE is handled before this loop
420
 
421
 
 
460
  md_summary.append(f"- **🔒 Set Privacy:** Set `{change.get('repo_id', '...')}` to `private={change.get('private', False)}`")
461
  elif change["type"] == "DELETE_SPACE":
462
  md_summary.append(f"- **💥 DELETE ENTIRE SPACE:** `{change.get('owner', '...')}/{change.get('space_name', '...')}` **(DESTRUCTIVE ACTION)**")
463
+ elif change["type"] == "CREATE_PR":
464
+ md_summary.append(f"- **🤝 Create Pull Request:** From `{change.get('source_repo_id', '...')}` to `{change.get('target_repo_id', '...')}`")
465
+ md_summary.append(f" - Title: `{change.get('title', '...')}`")
466
+ if change.get('body'): md_summary.append(f" - Body: `{change.get('body', '...')}`")
467
+ elif change["type"] == "ADD_COMMENT":
468
+ md_summary.append(f"- **💬 Add Comment:** On `{change.get('repo_id', '...')}`")
469
+ md_summary.append(f" - Comment: `{change.get('comment', '...')[:100]}{'...' if len(change.get('comment', '')) > 100 else ''}`") # Truncate long comments
470
+ elif change["type"] == "LIKE_SPACE":
471
+ md_summary.append(f"- **👍 Like Space:** `{change.get('repo_id', '...')}`")
472
+ elif change["type"] == "Error":
473
+ md_summary.append(f"- **❌ Staging Error:** {change.get('message', 'Unknown error')}")
474
+
475
  md_summary.append("")
476
 
477
  if file_changes:
 
598
  global parsed_code_blocks_state_cache
599
 
600
  _status = "Applying changes..."
601
+ # Yield initial status, hide confirm UI, clear summary
602
  yield _status, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="*Applying changes...*")
603
+ # Include outputs for potential space load/update
604
+ yield gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update() # Dummy yields to match structure
605
 
606
  if not changeset:
607
  return "No changes to apply.", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="No changes were staged.")
608
 
609
  # Check if the first action is an exclusive action like DUPLICATE_SPACE
610
+ first_action = changeset[0] if changeset else None
611
+ is_exclusive_duplicate = first_action and first_action.get('type') == 'DUPLICATE_SPACE'
612
+ is_exclusive_delete = first_action and first_action.get('type') == 'DELETE_SPACE'
613
+
614
 
615
+ if is_exclusive_duplicate:
616
+ change = first_action
617
  if change.get("source_repo_id") and change.get("target_repo_id"):
618
  status_message = build_logic_duplicate_space(
619
  hf_api_key=hf_api_key,
 
623
  )
624
  # After duplication, attempt to load the *new* space
625
  _status_reload = f"{status_message} | Attempting to load the new Space [{change['target_repo_id']}]..."
626
+ yield gr.update(value=_status_reload) # Update status
627
 
628
  # Extract new owner and space name from target_repo_id
629
+ target_repo_id = change['target_repo_id']
630
+ new_owner, new_space_name = target_repo_id.split('/', 1) if '/' in target_repo_id else (None, target_repo_id)
631
+
632
+ # If owner was not explicitly provided in target_repo_id, try to get it from the token
633
+ if not new_owner:
634
  token, token_err = build_logic_get_api_token(hf_api_key)
635
+ if token_err:
636
+ print(f"Error getting token for new owner determination: {token_err}")
637
+ new_owner = None # Cannot auto-detect
638
  else:
639
  try: user_info = build_logic_whoami(token=token); new_owner = user_info.get('name')
640
+ except Exception as e:
641
+ print(f"Error auto-detecting owner from token for new space: {e}")
642
+ new_owner = None
643
 
644
+
645
+ # Trigger the load logic for the new space if owner and name are available
646
  if new_owner and new_space_name:
 
 
647
  sdk, file_list, err_list = get_space_repository_info(hf_api_key, new_space_name, new_owner)
648
  if err_list:
649
  reload_error = f"Error reloading file list after duplication: {err_list}"
 
661
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(new_owner, new_space_name)
662
  final_overall_status = status_message + (f" | Reload Status: {reload_error}" if reload_error else f" | Reload Status: New Space [{new_owner}/{new_space_name}] state refreshed.")
663
 
 
664
  owner_update = gr.update(value=new_owner)
665
  space_update = gr.update(value=new_space_name)
666
  file_browser_update = gr.update(visible=True, choices=sorted([f["filename"] for f in parsed_code_blocks_state_cache if not f.get("is_structure_block")] or []), value=None)
667
 
 
668
  if new_owner and new_space_name:
669
+ sub_owner = re.sub(r'[^a-z0-9\-]+', '-', new_owner.lower()).strip('-') or 'owner'
670
+ sub_repo = re.sub(r'[^a-z0-9\-]+', '-', new_space_name.lower()).strip('-') or 'space'
671
+ iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if sdk == 'static' else '.hf.space'}"
672
+ iframe_update = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="500px"></iframe>', visible=True)
673
  else:
674
  iframe_update = gr.update(value=None, visible=False)
675
 
676
+ # Fetch runtime status for the new space
677
+ runtime_status_md = handle_refresh_space_status(hf_api_key, new_owner, new_space_name)
678
+
679
+ # Final yield after loading the new space
680
+ yield (
681
+ gr.update(value=final_overall_status), gr.update(value=_formatted), gr.update(value=_detected), _download, # Status, markdown, preview, download
682
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), # Hide confirm UI
683
+ [], gr.update(value="*No changes proposed.*"), # Clear changeset state and display
684
+ owner_update, space_update, file_browser_update, iframe_update, # Update space/owner fields, file browser, iframe
685
+ gr.update(value=runtime_status_md) # Update runtime status
686
+ )
687
 
688
  else:
689
  reload_error = "Cannot load new Space state: Owner or Space Name missing after duplication."
690
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(None, None) # Clear previews if new space couldn't be loaded
691
  final_overall_status = status_message + f" | Reload Status: {reload_error}"
692
+ # Keep old UI fields, clear file browser/iframe
693
+ yield (
694
+ gr.update(value=final_overall_status), gr.update(value=_formatted), gr.update(value=_detected), _download,
695
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
696
+ [], gr.update(value="*No changes proposed.*"),
697
+ gr.update(), gr.update(), gr.update(visible=False, choices=[], value=None), gr.update(value=None, visible=False),
698
+ gr.update(value="*Runtime status unavailable for new space.*") # Clear runtime status
699
+ )
700
 
701
 
702
  else:
 
704
  final_overall_status = "Duplicate Action Error: Invalid duplicate changeset format."
705
  # No state change, just report error
706
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_name, space_name)
707
+ yield (
708
+ gr.update(value=final_overall_status), gr.update(value=_formatted), gr.update(value=_detected), _download,
709
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
710
+ [], gr.update(value="*No changes proposed.*"),
711
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
712
+ )
713
+
714
+ elif is_exclusive_delete:
715
+ change = first_action
716
+ # Confirm DELETE_SPACE is only for the currently loaded space before calling the backend
717
+ delete_owner = change.get('owner') or owner_name
718
+ delete_space = change.get('space_name') or space_name
719
+ delete_repo_id_target = f"{delete_owner}/{delete_space}" if delete_owner and delete_space else None
720
+ current_repo_id = f"{owner_name}/{space_name}" if owner_name and space_name else None
721
+
722
+ if not delete_repo_id_target or delete_repo_id_target != current_repo_id:
723
+ final_overall_status = f"DELETE_SPACE Error: Action blocked. Cannot delete '{delete_repo_id_target}'. Only deletion of the currently loaded space ('{current_repo_id}') is permitted via AI action."
724
+ print(f"Blocked DELETE_SPACE action via confirm: requested '{delete_repo_id_target}', current '{current_repo_id}'.")
725
+ else:
726
+ status_message = build_logic_delete_space(hf_api_key, delete_owner, delete_space)
727
+ final_overall_status = status_message
728
+ # Clear the UI completely after successful deletion
729
+ if "Successfully" in status_message:
730
+ global parsed_code_blocks_state_cache
731
+ parsed_code_blocks_state_cache = []
732
+ _formatted, _detected, _download = _generate_ui_outputs_from_cache(None, None) # Clear previews
733
+ owner_update = gr.update(value="")
734
+ space_update = gr.update(value="")
735
+ file_browser_update = gr.update(visible=False, choices=[], value=None)
736
+ iframe_update = gr.update(value=None, visible=False)
737
+ runtime_status_update = gr.update(value="*Space deleted, runtime status unavailable.*")
738
+ else:
739
+ # If deletion failed, keep the current state and update status
740
+ _formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_name, space_name)
741
+ owner_update = gr.update()
742
+ space_update = gr.update()
743
+ file_browser_update = gr.update()
744
+ iframe_update = gr.update()
745
+ runtime_status_update = gr.update() # Keep existing or trigger refresh?
746
+ # Let's trigger a status refresh after failed delete attempt
747
+ runtime_status_update = handle_refresh_space_status(hf_api_key, owner_name, space_name)
748
+
749
+
750
+ cleared_changeset = []
751
+ yield (
752
+ gr.update(value=final_overall_status), gr.update(value=_formatted), gr.update(value=_detected), _download,
753
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
754
+ cleared_changeset, gr.update(value="*No changes proposed.*"),
755
+ owner_update, space_update, file_browser_update, iframe_update, runtime_status_update
756
+ )
757
+
758
+
759
+ else: # Not an exclusive action, process other actions and file changes sequentially
760
+
761
+ action_status_messages = []
762
+ file_change_operations = []
763
+
764
+ # Process non-file actions first
765
+ for change in changeset:
766
+ try:
767
+ if change['type'] == 'CREATE_SPACE':
768
+ repo_id_to_create = change.get('repo_id')
769
+ if repo_id_to_create:
770
+ msg = build_logic_create_space(hf_api_key, repo_id_to_create, change.get('sdk', 'gradio'), change.get('private', False))
771
+ action_status_messages.append(f"CREATE_SPACE: {msg}")
772
+ else:
773
+ action_status_messages.append("CREATE_SPACE Error: Target repo_id not specified.")
774
+ elif change['type'] == 'SET_PRIVACY':
775
+ repo_id_to_set_privacy = change.get('repo_id') or f"{owner_name}/{space_name}"
776
+ if repo_id_to_set_privacy and owner_name and space_name: # Ensure current space is loaded
777
+ msg = build_logic_set_space_privacy(hf_api_key, repo_id_to_set_privacy, change['private'])
778
+ action_status_messages.append(f"SET_PRIVACY: {msg}")
779
+ else:
780
+ action_status_messages.append("SET_PRIVACY Error: Cannot set privacy, no Space currently loaded.")
781
+ elif change['type'] == 'CREATE_PR':
782
+ # Ensure source is the currently loaded space
783
+ source_repo = f"{owner_name}/{space_name}" if owner_name and space_name else None
784
+ if source_repo and change.get('target_repo_id') and change.get('title'):
785
+ msg = build_logic_create_pull_request(hf_api_key, source_repo, change['target_repo_id'], change['title'], change.get('body', ''))
786
+ action_status_messages.append(f"CREATE_PR: {msg}")
787
+ else:
788
+ action_status_messages.append("CREATE_PR Error: Source/Target repo, title, or body missing.")
789
+ elif change['type'] == 'ADD_COMMENT':
790
+ repo_id_to_comment = change.get('repo_id') or f"{owner_name}/{space_name}"
791
+ if repo_id_to_comment and owner_name and space_name and change.get('comment'):
792
+ msg = build_logic_add_comment(hf_api_key, repo_id_to_comment, change['comment'])
793
+ action_status_messages.append(f"ADD_COMMENT: {msg}")
794
+ else:
795
+ action_status_messages.append("ADD_COMMENT Error: Cannot add comment, no Space loaded or comment text missing.")
796
+ elif change['type'] == 'LIKE_SPACE':
797
+ repo_id_to_like = change.get('repo_id') or f"{owner_name}/{space_name}"
798
+ if repo_id_to_like and owner_name and space_name:
799
+ msg = build_logic_like_space(hf_api_key, repo_id_to_like)
800
+ action_status_messages.append(f"LIKE_SPACE: {msg}")
801
+ else:
802
+ action_status_messages.append("LIKE_SPACE Error: Cannot like space, no Space currently loaded.")
803
+ elif change['type'] == 'Error':
804
+ action_status_messages.append(f"Staging Error: {change.get('message', 'Unknown error')}")
805
+
806
+ # Collect file changes for the final commit
807
+ elif change['type'] in ['CREATE_FILE', 'UPDATE_FILE', 'DELETE_FILE']:
808
+ file_change_operations.append(change)
809
+
810
+ except Exception as e:
811
+ action_status_messages.append(f"Error processing action {change.get('type', 'Unknown')}: {e}")
812
+ print(f"Error processing action {change.get('type', 'Unknown')}: {e}")
813
+ import traceback
814
+ traceback.print_exc()
815
+
816
+
817
+ # Apply file changes if any were staged
818
+ file_commit_status = ""
819
+ if file_change_operations:
820
+ if owner_name and space_name:
821
+ file_commit_status = apply_staged_file_changes(hf_api_key, owner_name, space_name, file_change_operations)
822
+ else:
823
+ file_commit_status = "File Commit Error: Cannot commit file changes, no Space currently loaded."
824
+ action_status_messages.append(file_commit_status)
825
+
826
+
827
+ # Combine all status messages
828
+ final_overall_status = " | ".join(action_status_messages)
829
+ if file_commit_status and file_commit_status != "No file changes (create/update/delete) to commit.":
830
+ if final_overall_status: final_overall_status += " | "
831
+ final_overall_status += file_commit_status
832
+ if not final_overall_status: final_overall_status = "No operations were applied."
833
+
834
+ # After applying changes, reload the space state to reflect the actual state on the Hub
835
+ _status_reload = f"{final_overall_status} | Reloading Space state..."
836
+ yield gr.update(value=_status_reload) # Update status
837
 
838
  refreshed_file_list = []
839
  reload_error = None
 
855
  loaded_files.append({"filename": file_path, "code": code, "language": lang, "is_binary": is_binary, "is_structure_block": False})
856
  parsed_code_blocks_state_cache = loaded_files
857
 
 
858
  file_browser_update = gr.update(visible=True, choices=sorted(refreshed_file_list or []), value=None)
 
859
  if owner_name and space_name:
860
  sub_owner = re.sub(r'[^a-z0-9\-]+', '-', owner_name.lower()).strip('-') or 'owner'
861
  sub_repo = re.sub(r'[^a-z0-9\-]+', '-', space_name.lower()).strip('-') or 'space'
 
864
  else:
865
  iframe_update = gr.update(value=None, visible=False)
866
 
867
+ # Refresh runtime status as well
868
+ runtime_status_update = handle_refresh_space_status(hf_api_key, owner_name, space_name)
869
+
870
+
871
  else:
872
  reload_error = "Cannot reload Space state: Owner or Space Name missing."
 
873
  file_browser_update = gr.update(visible=False, choices=[], value=None)
874
  iframe_update = gr.update(value=None, visible=False)
875
+ runtime_status_update = gr.update(value="*Runtime status unavailable after reload failure.*")
876
 
877
 
878
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_name, space_name)
879
 
880
+ final_overall_status_with_reload = final_overall_status + (f" | Reload Status: {reload_error}" if reload_error else " | Reload Status: Space state refreshed.")
881
 
882
  cleared_changeset = []
883
 
 
884
  yield (
885
+ gr.update(value=final_overall_status_with_reload),
886
+ gr.update(value=_formatted), gr.update(value=_detected), _download,
887
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
888
+ cleared_changeset, gr.update(value="*No changes proposed.*"),
889
+ gr.update(), gr.update(), file_browser_update, iframe_update, runtime_status_update
 
 
890
  )
891
 
892
 
 
913
  global parsed_code_blocks_state_cache
914
  _formatted_md_val, _detected_preview_val, _status_val = "*Loading files...*", "*Loading files...*", f"Loading Space: {ui_owner_name}/{ui_space_name}..."
915
  _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)
916
+ _build_status_clear, _edit_status_clear, _runtime_status_clear = "*Manual build status...*", "*Select a file...*", "*Runtime/Repo status will appear here.*"
917
  _changeset_clear = []
918
  _changeset_summary_clear = "*No changes proposed.*"
919
  _confirm_ui_hidden = gr.update(visible=False)
920
  _list_spaces_display_clear = "*List of spaces will appear here.*"
921
 
922
 
 
923
  yield (
924
  gr.update(value=_formatted_md_val), gr.update(value=_detected_preview_val), gr.update(value=_status_val), _file_browser_update,
925
  gr.update(value=ui_owner_name), gr.update(value=ui_space_name),
926
  _iframe_html_update, _download_btn_update, gr.update(value=_build_status_clear),
927
  gr.update(value=_edit_status_clear), gr.update(value=_runtime_status_clear),
928
  _changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
929
+ gr.update(value=_list_spaces_display_clear)
930
  )
931
 
932
  owner_to_use = ui_owner_name
933
  token, token_err = build_logic_get_api_token(hf_api_key_ui)
934
  if token_err:
935
  _status_val = f"Load Error: {token_err}"
936
+ yield gr.update(value=_status_val),
937
  return
938
  if not owner_to_use:
939
  try:
940
  user_info = build_logic_whoami(token=token)
941
  owner_to_use = user_info.get('name')
942
  if not owner_to_use: raise Exception("Could not find user name from token.")
943
+ yield gr.update(value=owner_to_use), gr.update(value=f"Loading Space: {owner_to_use}/{ui_space_name} (Auto-detected owner)...")
944
  except Exception as e:
945
  _status_val = f"Load Error: Error auto-detecting owner: {e}"
946
  yield gr.update(value=_status_val),
 
953
 
954
  sdk, file_list, err = get_space_repository_info(hf_api_key_ui, ui_space_name, owner_to_use)
955
 
 
956
  yield gr.update(value=owner_to_use), gr.update(value=ui_space_name),
957
 
958
  if err:
 
964
  gr.update(visible=False, choices=[], value=None),
965
  gr.update(), gr.update(),
966
  gr.update(value=None, visible=False),
967
+ _download, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
968
  )
969
  return
970
 
 
991
  else:
992
  iframe_update = gr.update(value=None, visible=False)
993
 
 
994
  runtime_status_md = handle_refresh_space_status(hf_api_key_ui, owner_to_use, ui_space_name)
995
 
 
 
996
  yield (
997
  gr.update(value=_formatted), gr.update(value=_detected), gr.update(value=_status_val),
998
  file_browser_update,
999
  gr.update(), gr.update(),
1000
  iframe_update,
1001
+ _download, gr.update(), gr.update(), gr.update(value=runtime_status_md), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1002
  )
1003
 
1004
  def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, space_sdk_ui, is_private_ui, formatted_markdown_content):
 
1009
  _changeset_summary_clear = "*Manual build initiated, changes plan cleared.*"
1010
  _confirm_ui_hidden = gr.update(visible=False)
1011
 
1012
+ yield (gr.update(value=_build_status), _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part),
1013
  _changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
1014
+ gr.update(), gr.update(), gr.update(), gr.update())
1015
 
1016
 
1017
  if not ui_space_name_part or "/" in ui_space_name_part:
1018
  _build_status = f"Build Error: Invalid Space Name '{ui_space_name_part}'."
1019
+ yield gr.update(value=_build_status),
1020
  return
1021
 
1022
  parsed_content = build_logic_parse_markdown(formatted_markdown_content)
 
1033
  yield gr.update(value=_build_status),
1034
  return
1035
 
1036
+ # Use apply_staged_file_changes and CREATE_SPACE action via confirm handler logic
1037
+ # Or simulate the parts needed here directly? Direct call might be simpler for manual build.
1038
+ # Let's use apply_staged_file_changes directly, but need to handle the CREATE_SPACE part separately first.
1039
+ token, token_err = build_logic_get_api_token(hf_api_key_ui)
1040
+ if token_err:
1041
+ _build_status = f"Build Error: API Token Error: {token_err}"
1042
+ yield gr.update(value=_build_status),
1043
+ return
1044
+ api = HfApi(token=token)
1045
+ repo_id_target = f"{ui_owner_name_part}/{ui_space_name_part}"
1046
+
1047
+ build_status_messages = []
1048
+ try:
1049
+ # Attempt to create/ensure space exists first
1050
+ api.create_repo(repo_id=repo_id_target, repo_type="space", space_sdk=space_sdk_select, private=is_private_ui, exist_ok=True)
1051
+ build_status_messages.append(f"CREATE_SPACE: Successfully created or ensured space [{repo_id_target}](https://huggingface.co/spaces/{repo_id_target}) exists.")
1052
+ except HfHubHTTPError as e_http:
1053
+ build_status_messages.append(f"CREATE_SPACE HTTP Error ({e_http.response.status_code if e_http.response else 'N/A'}): {e_http.response.text if e_http.response else str(e_http)}. Check logs.")
1054
+ if e_http.response and e_http.response.status_code in (401, 403):
1055
+ _build_status = f"Build Error: {build_status_messages[-1]}. Cannot proceed with file upload."; yield gr.update(value=_build_status), return
1056
+ except Exception as e:
1057
+ build_status_messages.append(f"CREATE_SPACE Error: {e}")
1058
+ _build_status = f"Build Error: {build_status_messages[-1]}. Cannot proceed with file upload."; yield gr.update(value=_build_status), return
1059
+
1060
+
1061
+ # Now apply file changes from the markdown content
1062
+ file_changes_only_changeset = [{"type": "CREATE_FILE", "path": f["path"], "content": f["content"], "lang": _infer_lang_from_filename(f["path"])} for f in proposed_files_list]
1063
+ if file_changes_only_changeset:
1064
+ file_commit_status = apply_staged_file_changes(hf_api_key_ui, ui_owner_name_part, ui_space_name_part, file_changes_only_changeset)
1065
+ build_status_messages.append(file_commit_status)
1066
+ else:
1067
+ build_status_messages.append("No files parsed from markdown to upload/update.")
1068
+
1069
+
1070
+ _build_status = " | ".join(build_status_messages)
1071
+
1072
 
1073
  owner_to_use = ui_owner_name_part
1074
  space_to_use = ui_space_name_part
 
1080
  gr.update(value=_build_status), _iframe_html, _file_browser_update,
1081
  gr.update(value=owner_to_use), gr.update(value=space_to_use),
1082
  _changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
1083
+ gr.update(value=_formatted_md), gr.update(value=_detected_preview_val), _download_btn_update, gr.update() # Keep runtime status
1084
  )
1085
 
1086
  sdk_built, file_list, err_list = get_space_repository_info(hf_api_key_ui, space_to_use, owner_to_use)
 
1110
  else:
1111
  _iframe_html_update = gr.update(value=None, visible=False)
1112
 
 
 
 
1113
  runtime_status_md = handle_refresh_space_status(hf_api_key_ui, owner_to_use, space_to_use)
1114
 
 
1115
  yield (
1116
  gr.update(value=_build_status), _iframe_html_update, _file_browser_update,
1117
  gr.update(value=owner_to_use), gr.update(value=space_to_use),
 
1206
  return "**Status Error:** Owner and Space Name must be provided to get status."
1207
 
1208
  status_details, err = get_space_runtime_status(hf_api_key_ui, ui_space_name, ui_owner_name)
1209
+ repo_info = None
1210
+ repo_info_err = None
1211
+ if not err: # Only fetch repo info if runtime status succeeded (implies space exists)
1212
+ sdk, file_list, repo_info_err = get_space_repository_info(hf_api_key_ui, ui_space_name, ui_owner_name)
1213
+ if not repo_info_err:
1214
+ repo_info = {"sdk": sdk, "file_count": len(file_list) if file_list is not None else 'N/A'}
1215
+
1216
+
1217
+ md_lines = []
1218
+ md_lines.append(f"### Status for {ui_owner_name}/{ui_space_name}\n")
1219
+
1220
+ if err:
1221
+ md_lines.append(f"- **Runtime Status Error:** {err}\n")
1222
+ elif not status_details:
1223
+ md_lines.append("*Could not retrieve runtime status details.*\n")
1224
+ else:
1225
+ md_lines.append(f"- **Stage:** `{status_details.get('stage', 'N/A')}`\n")
1226
+ md_lines.append(f"- **Status:** `{status_details.get('status', 'N/A')}`\n")
1227
+ md_lines.append(f"- **Hardware:** `{status_details.get('hardware', 'N/A')}`\n")
1228
+ requested_hw = status_details.get('requested_hardware')
1229
+ if requested_hw: md_lines.append(f"- **Requested Hardware:** `{requested_hw}`\n")
1230
+ error_msg = status_details.get('error_message')
1231
+ if error_msg: md_lines.append(f"- **Error:** `{escape_html_for_markdown(error_msg)}`\n")
1232
+ log_link = status_details.get('full_log_link')
1233
+ if log_link and log_link != "#": md_lines.append(f"- [View Full Logs]({log_link})\n")
1234
+
1235
+ md_lines.append("---")
1236
+ md_lines.append("### Repository Info\n")
1237
  if repo_info_err:
1238
+ md_lines.append(f"- **Repo Info Error:** {repo_info_err}\n")
1239
+ elif repo_info:
1240
+ md_lines.append(f"- **SDK:** `{repo_info.get('sdk', 'N/A')}`\n")
1241
+ md_lines.append(f"- **File Count:** `{repo_info.get('file_count', 'N/A')}`\n")
1242
  else:
1243
+ md_lines.append("*Could not retrieve repository info.*")
 
 
1244
 
1245
+
1246
+ return "\n".join(md_lines)
1247
 
1248
  def handle_list_spaces(hf_api_key_ui, ui_owner_name):
1249
  token, token_err = build_logic_get_api_token(hf_api_key_ui)
 
1277
  return md
1278
 
1279
  def handle_manual_duplicate_space(hf_api_key_ui, source_owner, source_space_name, target_owner, target_space_name, target_private):
 
1280
  if not source_owner or not source_space_name:
1281
+ return "Duplicate Error: Please load a Space first to duplicate.", gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1282
  if not target_owner or not target_space_name:
1283
+ return "Duplicate Error: Target Owner and Target Space Name are required.", gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1284
  if "/" in target_space_name:
1285
+ return "Duplicate Error: Target Space Name should not contain '/'. Use Target Owner field for the owner part.", gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1286
 
1287
 
1288
  source_repo_id = f"{source_owner}/{source_space_name}"
1289
  target_repo_id = f"{target_owner}/{target_space_name}"
1290
 
1291
  status_msg = f"Attempting to duplicate `{source_repo_id}` to `{target_repo_id}`..."
1292
+ # Yield initial status
1293
+ yield status_msg, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
1294
+
1295
 
1296
  result_message = build_logic_duplicate_space(hf_api_key_ui, source_repo_id, target_repo_id, target_private)
1297
  status_msg = f"Duplication Result: {result_message}"
1298
 
 
1299
  _status_reload = f"{status_msg} | Attempting to load the new Space [{target_repo_id}]..."
1300
+ yield gr.update(value=_status_reload) # Update status
1301
 
1302
+ new_owner = target_owner
1303
+ new_space_name = target_space_name
 
 
1304
 
1305
  sdk, file_list, err_list = get_space_repository_info(hf_api_key_ui, new_space_name, new_owner)
1306
 
1307
  if err_list:
1308
  reload_error = f"Error reloading file list after duplication: {err_list}"
1309
+ global parsed_code_blocks_state_cache
1310
  parsed_code_blocks_state_cache = []
1311
  _file_browser_update = gr.update(visible=False, choices=[], value=None)
1312
  _iframe_html_update = gr.update(value=None, visible=False)
 
1318
  is_binary = lang == "binary" or (err_get is not None)
1319
  code = f"[Error loading content: {err_get}]" if err_get else (content or "")
1320
  loaded_files.append({"filename": file_path, "code": code, "language": lang, "is_binary": is_binary, "is_structure_block": False})
1321
+ global parsed_code_blocks_state_cache
1322
  parsed_code_blocks_state_cache = loaded_files
1323
 
1324
  _file_browser_update = gr.update(visible=True, choices=sorted([f["filename"] for f in parsed_code_blocks_state_cache if not f.get("is_structure_block")] or []), value=None)
 
1330
  else:
1331
  _iframe_html_update = gr.update(value=None, visible=False)
1332
 
 
1333
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(new_owner, new_space_name)
1334
  final_overall_status = status_msg + (f" | Reload Status: {reload_error}" if reload_error else f" | Reload Status: New Space [{new_owner}/{new_space_name}] state refreshed.")
1335
 
 
1336
  runtime_status_md = handle_refresh_space_status(hf_api_key_ui, new_owner, new_space_name)
1337
 
 
1338
  owner_update = gr.update(value=new_owner)
1339
  space_update = gr.update(value=new_space_name)
1340
 
1341
 
1342
+ yield (
1343
+ gr.update(value=final_overall_status),
1344
  owner_update, space_update,
1345
+ gr.update(value=_formatted), gr.update(value=_detected), _download,
1346
  _file_browser_update, _iframe_html_update, gr.update(value=runtime_status_md)
1347
  )
1348
 
 
1492
  send_chat_button.click(handle_chat_submit, inputs=chat_inputs, outputs=chat_outputs)
1493
  chat_message_input.submit(handle_chat_submit, inputs=chat_inputs, outputs=chat_outputs)
1494
 
 
1495
  confirm_inputs = [hf_api_key_input, owner_name_input, space_name_input, changeset_state]
1496
  confirm_outputs = [
1497
  status_output, formatted_space_output_display, detected_files_preview, download_button,
1498
  confirm_accordion, confirm_button, cancel_button, changeset_state, changeset_display,
1499
+ owner_name_input, space_name_input, file_browser_dropdown, space_iframe_display, space_runtime_status_display # Added outputs for potential state changes
1500
  ]
1501
  confirm_button.click(handle_confirm_changes, inputs=confirm_inputs, outputs=confirm_outputs)
1502
 
 
1506
  ]
1507
  cancel_button.click(handle_cancel_changes, inputs=None, outputs=cancel_outputs)
1508
 
 
1509
  load_space_outputs = [
1510
  formatted_space_output_display, detected_files_preview, status_output,
1511
  file_browser_dropdown, owner_name_input, space_name_input,
1512
  space_iframe_display, download_button, build_status_display,
1513
  edit_status_display, space_runtime_status_display,
1514
  changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button,
1515
+ list_spaces_display # Added outputs for List Spaces
1516
  ]
1517
  load_space_button.click(
1518
  fn=handle_load_existing_space,
 
1520
  outputs=load_space_outputs
1521
  )
1522
 
 
1523
  build_outputs = [
1524
  build_status_display, space_iframe_display, file_browser_dropdown,
1525
  owner_name_input, space_name_input,
1526
  changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button,
1527
+ formatted_space_output_display, detected_files_preview, download_button, space_runtime_status_display
1528
  ]
1529
  build_inputs = [
1530
  hf_api_key_input, space_name_input, owner_name_input, space_sdk_select,
 
1547
 
1548
  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])
1549
 
 
1550
  manual_duplicate_inputs = [hf_api_key_input, owner_name_input, space_name_input, target_owner_input, target_space_name_input, target_private_checkbox]
1551
  manual_duplicate_outputs = [
1552
  status_output, owner_name_input, space_name_input,
1553
  formatted_space_output_display, detected_files_preview, download_button,
1554
+ file_browser_dropdown, space_iframe_display, space_runtime_status_display
1555
  ]
1556
  duplicate_space_button.click(fn=handle_manual_duplicate_space, inputs=manual_duplicate_inputs, outputs=manual_duplicate_outputs)
1557
 
 
1558
  list_spaces_button.click(fn=handle_list_spaces, inputs=[hf_api_key_input, owner_name_input], outputs=[list_spaces_display])
1559
 
1560