Update app.py
Browse files
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 |
-
|
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("<", "
|
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:
|
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 = []
|
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 |
-
|
535 |
-
|
|
|
|
|
536 |
|
537 |
-
if
|
538 |
-
change =
|
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
|
549 |
|
550 |
# Extract new owner and space name from target_repo_id
|
551 |
-
|
552 |
-
|
|
|
|
|
|
|
553 |
token, token_err = build_logic_get_api_token(hf_api_key)
|
554 |
-
if token_err:
|
|
|
|
|
555 |
else:
|
556 |
try: user_info = build_logic_whoami(token=token); new_owner = user_info.get('name')
|
557 |
-
except
|
|
|
|
|
558 |
|
559 |
-
|
|
|
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 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
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 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
673 |
|
674 |
cleared_changeset = []
|
675 |
|
676 |
-
# Return updated UI elements and hide confirmation UI
|
677 |
yield (
|
678 |
-
|
679 |
-
gr.update(value=_formatted),
|
680 |
-
gr.update(
|
681 |
-
|
682 |
-
gr.update(
|
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()
|
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),
|
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)...")
|
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()
|
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()
|
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),
|
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 |
-
|
837 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
980 |
-
|
981 |
-
|
982 |
-
|
983 |
-
|
984 |
-
|
985 |
-
|
986 |
-
|
987 |
-
|
988 |
-
|
989 |
-
|
990 |
-
|
991 |
-
|
992 |
-
|
993 |
-
|
994 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
995 |
if repo_info_err:
|
996 |
-
|
|
|
|
|
|
|
997 |
else:
|
998 |
-
|
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 |
-
|
|
|
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 |
-
|
|
|
|
|
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
|
1057 |
|
1058 |
-
|
1059 |
-
|
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 |
-
|
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
|
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 |
-
|
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
|
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
|
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("&", "&").replace("<", "<").replace(">", ">")
|
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 |
|