Update app.py
Browse files
app.py
CHANGED
@@ -24,7 +24,7 @@ from build_logic import (
|
|
24 |
build_logic_create_pull_request,
|
25 |
build_logic_add_comment,
|
26 |
build_logic_like_space,
|
27 |
-
build_logic_create_space
|
28 |
)
|
29 |
|
30 |
from model_logic import (
|
@@ -95,10 +95,10 @@ If no code or actions are requested, respond conversationally and help the user
|
|
95 |
|
96 |
def escape_html_for_markdown(text):
|
97 |
if not isinstance(text, str): return ""
|
98 |
-
return text.replace("&", "&").replace("<", "
|
99 |
|
100 |
def _infer_lang_from_filename(filename):
|
101 |
-
if not filename: return "
|
102 |
if '.' in filename:
|
103 |
ext = filename.split('.')[-1].lower()
|
104 |
mapping = {
|
@@ -110,17 +110,17 @@ def _infer_lang_from_filename(filename):
|
|
110 |
'c': 'c', 'h': 'c', 'cpp': 'cpp', 'hpp': 'cpp', 'cs': 'csharp', 'java': 'java',
|
111 |
'rb': 'ruby', 'php': 'php', 'go': 'go', 'rs': 'rust', 'swift': 'swift', 'kt': 'kotlin', 'kts': 'kotlin',
|
112 |
'sql': 'sql', 'dockerfile': 'docker', 'tf': 'terraform', 'hcl': 'terraform',
|
113 |
-
'txt': '
|
114 |
-
'csv': '
|
115 |
-
'.env': '
|
116 |
'makefile': 'makefile',
|
117 |
}
|
118 |
-
return mapping.get(ext, "
|
119 |
base_filename = os.path.basename(filename)
|
120 |
if base_filename == 'Dockerfile': return 'docker'
|
121 |
if base_filename == 'Makefile': return 'makefile'
|
122 |
-
if base_filename.startswith('.'): return '
|
123 |
-
return "
|
124 |
|
125 |
def _clean_filename(filename_line_content):
|
126 |
text = filename_line_content.strip()
|
@@ -141,7 +141,7 @@ def _parse_and_update_state_cache(latest_bot_message_content, current_files_stat
|
|
141 |
|
142 |
structure_match = structure_pattern.search(content)
|
143 |
if structure_match:
|
144 |
-
structure_block_state = {"filename": "File Structure (from AI)", "language": structure_match.group("struct_lang") or "
|
145 |
|
146 |
current_message_proposed_filenames = []
|
147 |
for match in file_pattern.finditer(content):
|
@@ -200,7 +200,7 @@ def _export_selected_logic(selected_filenames, space_line_name_for_md, parsed_bl
|
|
200 |
if block.get('is_binary') or content.startswith(("[Binary file", "[Error loading content:", "[Binary or Skipped file]")):
|
201 |
output_lines.append(content)
|
202 |
else:
|
203 |
-
lang = block.get('language', '
|
204 |
output_lines.extend([f"{bbb}{lang}", content, bbb])
|
205 |
output_lines.append("")
|
206 |
exported_content_count += 1
|
@@ -257,7 +257,7 @@ def _generate_ui_outputs_from_cache(owner, space_name):
|
|
257 |
if block.get('is_binary') or content.startswith(("[Binary file", "[Error loading content:", "[Binary or Skipped file]")):
|
258 |
preview_md_lines.append(f"\n`{escape_html_for_markdown(content.strip())}`\n")
|
259 |
else:
|
260 |
-
lang = block.get('language', '
|
261 |
preview_md_lines.append(f"\n{bbb}{lang}\n{content.strip()}\n{bbb}\n")
|
262 |
preview_md_val = "\n".join(preview_md_lines)
|
263 |
|
@@ -265,7 +265,7 @@ def _generate_ui_outputs_from_cache(owner, space_name):
|
|
265 |
|
266 |
def generate_and_stage_changes(ai_response_content, current_files_state, hf_owner_name, hf_repo_name):
|
267 |
changeset = []
|
268 |
-
current_files_dict = {f["filename"]: f
|
269 |
ai_parsed_md = build_logic_parse_markdown(ai_response_content)
|
270 |
ai_proposed_files_list = ai_parsed_md.get("files", [])
|
271 |
ai_proposed_files_dict = {f["path"]: f for f in ai_proposed_files_list}
|
@@ -411,7 +411,6 @@ def generate_and_stage_changes(ai_response_content, current_files_state, hf_owne
|
|
411 |
print(f"Warning: Cannot stage LIKE_SPACE action, no Space currently loaded.")
|
412 |
changeset.append({"type": "Error", "message": "Cannot like space, no Space currently loaded."})
|
413 |
|
414 |
-
|
415 |
for file_info in ai_proposed_files_list:
|
416 |
filename = file_info["path"]
|
417 |
proposed_content = file_info["content"]
|
@@ -593,21 +592,12 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
|
|
593 |
global parsed_code_blocks_state_cache
|
594 |
|
595 |
_status = "Applying changes..."
|
596 |
-
# Yield initial status, hide confirm UI, clear summary
|
597 |
yield _status, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="*Applying changes...*")
|
598 |
-
#
|
599 |
-
yield
|
600 |
-
|
601 |
|
602 |
if not changeset:
|
603 |
-
|
604 |
-
yield (
|
605 |
-
"No changes to apply.", # status_output
|
606 |
-
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="No changes were staged."), # Hide confirm UI, clear summary
|
607 |
-
None, None, None, None, None, None, None, None, None, None, None # 15 None values
|
608 |
-
)
|
609 |
-
return
|
610 |
-
|
611 |
|
612 |
first_action = changeset[0] if changeset else None
|
613 |
is_exclusive_duplicate = first_action and first_action.get('type') == 'DUPLICATE_SPACE'
|
@@ -624,9 +614,7 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
|
|
624 |
private=change.get("private", False)
|
625 |
)
|
626 |
_status_reload = f"{status_message} | Attempting to load the new Space [{change['target_repo_id']}]..."
|
627 |
-
|
628 |
-
yield gr.update(value=_status_reload), 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(), gr.update()
|
629 |
-
|
630 |
|
631 |
target_repo_id = change['target_repo_id']
|
632 |
new_owner, new_space_name = target_repo_id.split('/', 1) if '/' in target_repo_id else (None, target_repo_id)
|
@@ -675,20 +663,18 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
|
|
675 |
|
676 |
runtime_status_md = handle_refresh_space_status(hf_api_key, new_owner, new_space_name)
|
677 |
|
678 |
-
# Final yield after loading the new space
|
679 |
yield (
|
680 |
-
gr.update(value=final_overall_status), gr.update(value=_formatted), gr.update(value=_detected), _download,
|
681 |
-
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
|
682 |
-
[], gr.update(value="*No changes proposed.*"),
|
683 |
-
owner_update, space_update, file_browser_update, iframe_update,
|
684 |
-
gr.update(value=runtime_status_md)
|
685 |
)
|
686 |
|
687 |
else:
|
688 |
reload_error = "Cannot load new Space state: Owner or Space Name missing after duplication."
|
689 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(None, None)
|
690 |
final_overall_status = status_message + f" | Reload Status: {reload_error}"
|
691 |
-
# Keep old UI fields, clear file browser/iframe
|
692 |
yield (
|
693 |
gr.update(value=final_overall_status), gr.update(value=_formatted), gr.update(value=_detected), _download,
|
694 |
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
|
@@ -697,7 +683,6 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
|
|
697 |
gr.update(value="*Runtime status unavailable for new space.*")
|
698 |
)
|
699 |
|
700 |
-
|
701 |
else:
|
702 |
final_overall_status = "Duplicate Action Error: Invalid duplicate changeset format."
|
703 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_name, space_name)
|
@@ -718,13 +703,6 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
|
|
718 |
if not delete_repo_id_target or delete_repo_id_target != current_repo_id:
|
719 |
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."
|
720 |
print(f"Blocked DELETE_SPACE action via confirm: requested '{delete_repo_id_target}', current '{current_repo_id}'.")
|
721 |
-
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_name, space_name)
|
722 |
-
owner_update = gr.update()
|
723 |
-
space_update = gr.update()
|
724 |
-
file_browser_update = gr.update()
|
725 |
-
iframe_update = gr.update()
|
726 |
-
runtime_status_update = handle_refresh_space_status(hf_api_key, owner_name, space_name) # Refresh status after failed delete attempt
|
727 |
-
|
728 |
else:
|
729 |
status_message = build_logic_delete_space(hf_api_key, delete_owner, delete_space)
|
730 |
final_overall_status = status_message
|
@@ -742,7 +720,7 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
|
|
742 |
space_update = gr.update()
|
743 |
file_browser_update = gr.update()
|
744 |
iframe_update = gr.update()
|
745 |
-
runtime_status_update = handle_refresh_space_status(hf_api_key, owner_name, space_name)
|
746 |
|
747 |
|
748 |
cleared_changeset = []
|
@@ -763,8 +741,10 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
|
|
763 |
if change['type'] == 'CREATE_SPACE':
|
764 |
repo_id_to_create = change.get('repo_id')
|
765 |
if repo_id_to_create:
|
766 |
-
msg = build_logic_create_space(hf_api_key, repo_id_to_create.split('/')[1], repo_id_to_create.split('/')[0] if '/' in repo_id_to_create else None, change.get('sdk', 'gradio'), "", change.get('private', False))
|
767 |
action_status_messages.append(f"CREATE_SPACE: {msg}")
|
|
|
|
|
768 |
else:
|
769 |
action_status_messages.append("CREATE_SPACE Error: Target repo_id not specified.")
|
770 |
elif change['type'] == 'SET_PRIVACY':
|
@@ -825,7 +805,7 @@ def handle_confirm_changes(hf_api_key, owner_name, space_name, changeset):
|
|
825 |
if not final_overall_status: final_overall_status = "No operations were applied."
|
826 |
|
827 |
_status_reload = f"{final_overall_status} | Reloading Space state..."
|
828 |
-
yield gr.update(value=_status_reload)
|
829 |
|
830 |
refreshed_file_list = []
|
831 |
reload_error = None
|
@@ -900,18 +880,15 @@ def update_models_dropdown(provider_select):
|
|
900 |
selected_value = default_model if default_model in models else (models[0] if models else None)
|
901 |
return gr.update(choices=models, value=selected_value)
|
902 |
|
903 |
-
def
|
904 |
_status = "Detecting user from token..."
|
905 |
-
# Yield initial state
|
906 |
-
|
907 |
-
yield (gr.update(value=_status), gr.update(value=""), gr.update(value=""), gr.update(value="*Listing spaces...*"), gr.update(visible=False, choices=[], value=None), gr.update(value=None, visible=False), gr.update(value="*Load or create a space to see its definition.*"), gr.update(value="*A preview of the latest file versions will appear here.*"), gr.update(value="*Manual build status...*"), gr.update(value="*Select a file...*"), gr.update(value="*Runtime/Repo status will appear here.*"), [], gr.update(value="*No changes proposed.*"), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value=""), gr.update(value=""), gr.update(value=False))
|
908 |
-
|
909 |
|
910 |
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
911 |
if token_err:
|
912 |
_status = f"Detection Error: {token_err}"
|
913 |
-
|
914 |
-
yield (gr.update(value=_status), gr.update(), gr.update(), gr.update(value=f"*Could not list spaces due to token error: {token_err}*"), 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(), gr.update())
|
915 |
return
|
916 |
|
917 |
try:
|
@@ -921,6 +898,7 @@ def handle_detect_user_and_list_spaces(hf_api_key_ui):
|
|
921 |
_status = f"User detected: {owner_name}"
|
922 |
owner_update = gr.update(value=owner_name)
|
923 |
|
|
|
924 |
list_spaces_md, list_err = build_logic_list_user_spaces(hf_api_key=token, owner=owner_name)
|
925 |
if list_err:
|
926 |
list_spaces_update = gr.update(value=f"List Spaces Error: {list_err}")
|
@@ -928,17 +906,14 @@ def handle_detect_user_and_list_spaces(hf_api_key_ui):
|
|
928 |
else:
|
929 |
list_spaces_update = gr.update(value=list_spaces_md)
|
930 |
|
931 |
-
|
932 |
-
yield (gr.update(value=_status), owner_update, gr.update(value=""), list_spaces_update, gr.update(visible=False, choices=[], value=None), gr.update(value=None, visible=False), gr.update(value="*Load or create a space to see its definition.*"), gr.update(value="*A preview of the latest file versions will appear here.*"), gr.update(value="*Manual build status...*"), gr.update(value="*Select a file...*"), gr.update(value="*Runtime/Repo status will appear here.*"), [], gr.update(value="*No changes proposed.*"), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value=""), gr.update(value=""), gr.update(value=False))
|
933 |
-
|
934 |
|
935 |
except Exception as e:
|
936 |
_status = f"Detection Error: Could not detect user from token: {e}"
|
937 |
print(f"Error in handle_detect_user: {e}")
|
938 |
import traceback
|
939 |
traceback.print_exc()
|
940 |
-
|
941 |
-
yield (gr.update(value=_status), gr.update(), gr.update(), gr.update(value=f"*Could not list spaces due to user detection error: {e}*"), 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(), gr.update())
|
942 |
|
943 |
|
944 |
def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
|
@@ -949,64 +924,55 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
|
|
949 |
_changeset_clear = []
|
950 |
_changeset_summary_clear = "*No changes proposed.*"
|
951 |
_confirm_ui_hidden = gr.update(visible=False)
|
952 |
-
|
953 |
_target_owner_clear, _target_space_clear, _target_private_clear = gr.update(value=""), gr.update(value=""), gr.update(value=False)
|
954 |
|
955 |
|
956 |
-
# outputs list: [formatted_space_output_display, detected_files_preview, status_output, file_browser_dropdown, owner_name_input, space_name_input, space_iframe_display, download_button, build_status_display, edit_status_display, space_runtime_status_display, changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button, list_spaces_display, target_owner_input, target_space_name_input, target_private_checkbox] (20 items)
|
957 |
yield (
|
958 |
gr.update(value=_formatted_md_val), gr.update(value=_detected_preview_val), gr.update(value=_status_val), _file_browser_update,
|
959 |
gr.update(value=ui_owner_name), gr.update(value=ui_space_name),
|
960 |
_iframe_html_update, _download_btn_update, gr.update(value=_build_status_clear),
|
961 |
gr.update(value=_edit_status_clear), gr.update(value=_runtime_status_clear),
|
962 |
_changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
|
963 |
-
|
964 |
)
|
965 |
|
966 |
owner_to_use = ui_owner_name
|
967 |
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
968 |
if token_err:
|
969 |
_status_val = f"Load Error: {token_err}"
|
970 |
-
|
971 |
-
yield (gr.update(), gr.update(), gr.update(value=_status_val), 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(), gr.update(), gr.update(), gr.update())
|
972 |
return
|
973 |
if not owner_to_use:
|
974 |
try:
|
975 |
user_info = build_logic_whoami(token=token)
|
976 |
owner_to_use = user_info.get('name')
|
977 |
if not owner_to_use: raise Exception("Could not find user name from token.")
|
978 |
-
|
979 |
-
yield (gr.update(), gr.update(), gr.update(value=f"Loading Space: {owner_to_use}/{ui_space_name} (Auto-detected owner)..."), gr.update(), gr.update(value=owner_to_use), 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(), gr.update())
|
980 |
except Exception as e:
|
981 |
_status_val = f"Load Error: Error auto-detecting owner: {e}"
|
982 |
-
|
983 |
-
yield (gr.update(), gr.update(), gr.update(value=_status_val), 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(), gr.update(), gr.update(), gr.update())
|
984 |
return
|
985 |
|
986 |
if not owner_to_use or not ui_space_name:
|
987 |
_status_val = "Load Error: Owner and Space Name are required."
|
988 |
-
|
989 |
-
yield (gr.update(), gr.update(), gr.update(value=_status_val), 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(), gr.update(), gr.update(), gr.update())
|
990 |
return
|
991 |
|
992 |
sdk, file_list, err = get_space_repository_info(hf_api_key_ui, ui_space_name, owner_to_use)
|
993 |
|
994 |
-
|
995 |
-
yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(value=owner_to_use), gr.update(value=ui_space_name), 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())
|
996 |
-
|
997 |
|
998 |
if err:
|
999 |
_status_val = f"Load Error: {err}"
|
1000 |
parsed_code_blocks_state_cache = []
|
1001 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
1002 |
-
# Yield final state on error
|
1003 |
yield (
|
1004 |
gr.update(value=_formatted), gr.update(value=_detected), gr.update(value=_status_val),
|
1005 |
-
gr.update(visible=False, choices=[], value=None),
|
1006 |
-
gr.update(), gr.update(),
|
1007 |
-
gr.update(value=None, visible=False),
|
1008 |
-
_download, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
|
1009 |
-
gr.update(value="*List of spaces will appear here.*"), gr.update(value=""), gr.update(value=""), gr.update(value=False) # List Spaces and Duplicate fields (from initial clear state)
|
1010 |
)
|
1011 |
return
|
1012 |
|
@@ -1035,16 +1001,12 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
|
|
1035 |
|
1036 |
runtime_status_md = handle_refresh_space_status(hf_api_key_ui, owner_to_use, ui_space_name)
|
1037 |
|
1038 |
-
# Final yield after success
|
1039 |
yield (
|
1040 |
-
gr.update(value=_formatted), gr.update(value=_detected), gr.update(value=_status_val),
|
1041 |
-
file_browser_update,
|
1042 |
-
gr.update(), gr.update(),
|
1043 |
-
iframe_update,
|
1044 |
-
_download,
|
1045 |
-
gr.update(), gr.update(), gr.update(value=runtime_status_md), # Build/Edit status (from initial clear state), Runtime status
|
1046 |
-
gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), # Changeset, Confirm UI (from initial clear state)
|
1047 |
-
gr.update(value="*List of spaces will appear here.*"), gr.update(value=""), gr.update(value=""), gr.update(value=False) # List Spaces and Duplicate fields (from initial clear state)
|
1048 |
)
|
1049 |
|
1050 |
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):
|
@@ -1055,14 +1017,14 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
|
|
1055 |
_changeset_summary_clear = "*Manual build initiated, changes plan cleared.*"
|
1056 |
_confirm_ui_hidden = gr.update(visible=False)
|
1057 |
|
1058 |
-
# Outputs list: [build_status_display, space_iframe_display, file_browser_dropdown, owner_name_input, space_name_input, changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button, formatted_space_output_display, detected_files_preview, download_button, space_runtime_status_display] (14 items)
|
1059 |
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),
|
1060 |
_changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
|
1061 |
-
gr.update(), gr.update(), gr.update(), gr.update())
|
|
|
1062 |
|
1063 |
if not ui_space_name_part or "/" in ui_space_name_part:
|
1064 |
_build_status = f"Build Error: Invalid Space Name '{ui_space_name_part}'."
|
1065 |
-
yield gr.update(value=_build_status),
|
1066 |
return
|
1067 |
|
1068 |
parsed_content = build_logic_parse_markdown(formatted_markdown_content)
|
@@ -1076,13 +1038,13 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
|
|
1076 |
|
1077 |
if not manual_changeset:
|
1078 |
_build_status = "Build Error: No target space specified or no files parsed from markdown."
|
1079 |
-
yield gr.update(value=_build_status),
|
1080 |
return
|
1081 |
|
1082 |
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
1083 |
if token_err:
|
1084 |
_build_status = f"Build Error: API Token Error: {token_err}"
|
1085 |
-
yield gr.update(value=_build_status),
|
1086 |
return
|
1087 |
api = HfApi(token=token)
|
1088 |
repo_id_target = f"{ui_owner_name_part}/{ui_space_name_part}"
|
@@ -1094,10 +1056,10 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
|
|
1094 |
except HfHubHTTPError as e_http:
|
1095 |
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.")
|
1096 |
if e_http.response and e_http.response.status_code in (401, 403):
|
1097 |
-
_build_status = f"Build Error: {build_status_messages[-1]}. Cannot proceed with file upload."; yield gr.update(value=_build_status)
|
1098 |
except Exception as e:
|
1099 |
build_status_messages.append(f"CREATE_SPACE Error: {e}")
|
1100 |
-
_build_status = f"Build Error: {build_status_messages[-1]}. Cannot proceed with file upload."; yield gr.update(value=_build_status)
|
1101 |
|
1102 |
|
1103 |
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]
|
@@ -1163,11 +1125,11 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
|
|
1163 |
|
1164 |
def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path):
|
1165 |
if not selected_file_path:
|
1166 |
-
return "", "Select a file.", "", gr.update(language="
|
1167 |
|
1168 |
content, err = get_space_file_content(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path)
|
1169 |
if err:
|
1170 |
-
return "", f"Load Error: {err}", "", gr.update(language="
|
1171 |
|
1172 |
lang = _infer_lang_from_filename(selected_file_path)
|
1173 |
commit_msg = f"Update {selected_file_path}"
|
@@ -1210,9 +1172,9 @@ def handle_commit_file_changes(hf_api_key_ui, ui_space_name_part, ui_owner_name_
|
|
1210 |
|
1211 |
def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path):
|
1212 |
if not ui_owner_name_part or not ui_space_name_part:
|
1213 |
-
return "Delete Error: Cannot delete file. Please load a space first.", gr.update(), "", "", "
|
1214 |
if not file_to_delete_path:
|
1215 |
-
return "Delete Error: No file selected to delete.", gr.update(), "", "", "
|
1216 |
|
1217 |
status_msg = build_logic_delete_space_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path)
|
1218 |
|
@@ -1226,7 +1188,7 @@ def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, fi
|
|
1226 |
parsed_code_blocks_state_cache = [b for b in parsed_code_blocks_state_cache if b["filename"] != file_to_delete_path]
|
1227 |
file_content_editor_update = gr.update(value="")
|
1228 |
commit_message_update = gr.update(value="")
|
1229 |
-
editor_lang_update = gr.update(language="
|
1230 |
file_list, _ = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part)
|
1231 |
file_browser_update = gr.update(choices=sorted(file_list or []), value=None)
|
1232 |
|
@@ -1324,8 +1286,9 @@ def handle_manual_duplicate_space(hf_api_key_ui, source_owner, source_space_name
|
|
1324 |
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()
|
1325 |
if "/" in target_space_name:
|
1326 |
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()
|
1327 |
-
|
1328 |
global parsed_code_blocks_state_cache
|
|
|
|
|
1329 |
source_repo_id = f"{source_owner}/{source_space_name}"
|
1330 |
target_repo_id = f"{target_owner}/{target_space_name}"
|
1331 |
|
@@ -1337,7 +1300,7 @@ def handle_manual_duplicate_space(hf_api_key_ui, source_owner, source_space_name
|
|
1337 |
status_msg = f"Duplication Result: {result_message}"
|
1338 |
|
1339 |
_status_reload = f"{status_msg} | Attempting to load the new Space [{target_repo_id}]..."
|
1340 |
-
yield gr.update(value=_status_reload)
|
1341 |
|
1342 |
new_owner = target_owner
|
1343 |
new_space_name = target_space_name
|
@@ -1346,7 +1309,6 @@ def handle_manual_duplicate_space(hf_api_key_ui, source_owner, source_space_name
|
|
1346 |
|
1347 |
if err_list:
|
1348 |
reload_error = f"Error reloading file list after duplication: {err_list}"
|
1349 |
-
|
1350 |
parsed_code_blocks_state_cache = []
|
1351 |
_file_browser_update = gr.update(visible=False, choices=[], value=None)
|
1352 |
_iframe_html_update = gr.update(value=None, visible=False)
|
@@ -1383,4 +1345,224 @@ def handle_manual_duplicate_space(hf_api_key_ui, source_owner, source_space_name
|
|
1383 |
owner_update, space_update,
|
1384 |
gr.update(value=_formatted), gr.update(value=_detected), _download,
|
1385 |
_file_browser_update, _iframe_html_update, gr.update(value=runtime_status_md)
|
1386 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
build_logic_create_pull_request,
|
25 |
build_logic_add_comment,
|
26 |
build_logic_like_space,
|
27 |
+
build_logic_create_space
|
28 |
)
|
29 |
|
30 |
from model_logic import (
|
|
|
95 |
|
96 |
def escape_html_for_markdown(text):
|
97 |
if not isinstance(text, str): return ""
|
98 |
+
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
99 |
|
100 |
def _infer_lang_from_filename(filename):
|
101 |
+
if not filename: return "plaintext"
|
102 |
if '.' in filename:
|
103 |
ext = filename.split('.')[-1].lower()
|
104 |
mapping = {
|
|
|
110 |
'c': 'c', 'h': 'c', 'cpp': 'cpp', 'hpp': 'cpp', 'cs': 'csharp', 'java': 'java',
|
111 |
'rb': 'ruby', 'php': 'php', 'go': 'go', 'rs': 'rust', 'swift': 'swift', 'kt': 'kotlin', 'kts': 'kotlin',
|
112 |
'sql': 'sql', 'dockerfile': 'docker', 'tf': 'terraform', 'hcl': 'terraform',
|
113 |
+
'txt': 'plaintext', 'log': 'plaintext', 'ini': 'ini', 'conf': 'plaintext', 'cfg': 'plaintext',
|
114 |
+
'csv': 'plaintext', 'tsv': 'tsv', 'err': 'plaintext',
|
115 |
+
'.env': 'plaintext', '.gitignore': 'plaintext', '.npmrc': 'plaintext', '.gitattributes': 'plaintext',
|
116 |
'makefile': 'makefile',
|
117 |
}
|
118 |
+
return mapping.get(ext, "plaintext")
|
119 |
base_filename = os.path.basename(filename)
|
120 |
if base_filename == 'Dockerfile': return 'docker'
|
121 |
if base_filename == 'Makefile': return 'makefile'
|
122 |
+
if base_filename.startswith('.'): return 'plaintext'
|
123 |
+
return "plaintext"
|
124 |
|
125 |
def _clean_filename(filename_line_content):
|
126 |
text = filename_line_content.strip()
|
|
|
141 |
|
142 |
structure_match = structure_pattern.search(content)
|
143 |
if structure_match:
|
144 |
+
structure_block_state = {"filename": "File Structure (from AI)", "language": structure_match.group("struct_lang") or "plaintext", "code": structure_match.group("structure_code").strip(), "is_binary": False, "is_structure_block": True}
|
145 |
|
146 |
current_message_proposed_filenames = []
|
147 |
for match in file_pattern.finditer(content):
|
|
|
200 |
if block.get('is_binary') or content.startswith(("[Binary file", "[Error loading content:", "[Binary or Skipped file]")):
|
201 |
output_lines.append(content)
|
202 |
else:
|
203 |
+
lang = block.get('language', 'plaintext') or 'plaintext'
|
204 |
output_lines.extend([f"{bbb}{lang}", content, bbb])
|
205 |
output_lines.append("")
|
206 |
exported_content_count += 1
|
|
|
257 |
if block.get('is_binary') or content.startswith(("[Binary file", "[Error loading content:", "[Binary or Skipped file]")):
|
258 |
preview_md_lines.append(f"\n`{escape_html_for_markdown(content.strip())}`\n")
|
259 |
else:
|
260 |
+
lang = block.get('language', 'plaintext') or 'plaintext'
|
261 |
preview_md_lines.append(f"\n{bbb}{lang}\n{content.strip()}\n{bbb}\n")
|
262 |
preview_md_val = "\n".join(preview_md_lines)
|
263 |
|
|
|
265 |
|
266 |
def generate_and_stage_changes(ai_response_content, current_files_state, hf_owner_name, hf_repo_name):
|
267 |
changeset = []
|
268 |
+
current_files_dict = {f["filename"]: f for f in current_files_state if not f.get("is_structure_block")}
|
269 |
ai_parsed_md = build_logic_parse_markdown(ai_response_content)
|
270 |
ai_proposed_files_list = ai_parsed_md.get("files", [])
|
271 |
ai_proposed_files_dict = {f["path"]: f for f in ai_proposed_files_list}
|
|
|
411 |
print(f"Warning: Cannot stage LIKE_SPACE action, no Space currently loaded.")
|
412 |
changeset.append({"type": "Error", "message": "Cannot like space, no Space currently loaded."})
|
413 |
|
|
|
414 |
for file_info in ai_proposed_files_list:
|
415 |
filename = file_info["path"]
|
416 |
proposed_content = file_info["content"]
|
|
|
592 |
global parsed_code_blocks_state_cache
|
593 |
|
594 |
_status = "Applying changes..."
|
|
|
595 |
yield _status, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="*Applying changes...*")
|
596 |
+
# Dummy yields for components that might be updated later in the generator
|
597 |
+
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()
|
|
|
598 |
|
599 |
if not changeset:
|
600 |
+
return "No changes to apply.", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="No changes were staged.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
601 |
|
602 |
first_action = changeset[0] if changeset else None
|
603 |
is_exclusive_duplicate = first_action and first_action.get('type') == 'DUPLICATE_SPACE'
|
|
|
614 |
private=change.get("private", False)
|
615 |
)
|
616 |
_status_reload = f"{status_message} | Attempting to load the new Space [{change['target_repo_id']}]..."
|
617 |
+
yield gr.update(value=_status_reload)
|
|
|
|
|
618 |
|
619 |
target_repo_id = change['target_repo_id']
|
620 |
new_owner, new_space_name = target_repo_id.split('/', 1) if '/' in target_repo_id else (None, target_repo_id)
|
|
|
663 |
|
664 |
runtime_status_md = handle_refresh_space_status(hf_api_key, new_owner, new_space_name)
|
665 |
|
|
|
666 |
yield (
|
667 |
+
gr.update(value=final_overall_status), gr.update(value=_formatted), gr.update(value=_detected), _download,
|
668 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
|
669 |
+
[], gr.update(value="*No changes proposed.*"),
|
670 |
+
owner_update, space_update, file_browser_update, iframe_update,
|
671 |
+
gr.update(value=runtime_status_md)
|
672 |
)
|
673 |
|
674 |
else:
|
675 |
reload_error = "Cannot load new Space state: Owner or Space Name missing after duplication."
|
676 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(None, None)
|
677 |
final_overall_status = status_message + f" | Reload Status: {reload_error}"
|
|
|
678 |
yield (
|
679 |
gr.update(value=final_overall_status), gr.update(value=_formatted), gr.update(value=_detected), _download,
|
680 |
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
|
|
|
683 |
gr.update(value="*Runtime status unavailable for new space.*")
|
684 |
)
|
685 |
|
|
|
686 |
else:
|
687 |
final_overall_status = "Duplicate Action Error: Invalid duplicate changeset format."
|
688 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_name, space_name)
|
|
|
703 |
if not delete_repo_id_target or delete_repo_id_target != current_repo_id:
|
704 |
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."
|
705 |
print(f"Blocked DELETE_SPACE action via confirm: requested '{delete_repo_id_target}', current '{current_repo_id}'.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
706 |
else:
|
707 |
status_message = build_logic_delete_space(hf_api_key, delete_owner, delete_space)
|
708 |
final_overall_status = status_message
|
|
|
720 |
space_update = gr.update()
|
721 |
file_browser_update = gr.update()
|
722 |
iframe_update = gr.update()
|
723 |
+
runtime_status_update = handle_refresh_space_status(hf_api_key, owner_name, space_name)
|
724 |
|
725 |
|
726 |
cleared_changeset = []
|
|
|
741 |
if change['type'] == 'CREATE_SPACE':
|
742 |
repo_id_to_create = change.get('repo_id')
|
743 |
if repo_id_to_create:
|
744 |
+
msg = build_logic_create_space(hf_api_key, repo_id_to_create.split('/')[1], repo_id_to_create.split('/')[0] if '/' in repo_id_to_create else None, change.get('sdk', 'gradio'), "", change.get('private', False)) # Note: Manual create requires markdown, passing empty string
|
745 |
action_status_messages.append(f"CREATE_SPACE: {msg}")
|
746 |
+
# If creation was successful, potentially update the UI fields? No, AI might create a *different* space.
|
747 |
+
# Just report success/failure. The AI can propose loading it next.
|
748 |
else:
|
749 |
action_status_messages.append("CREATE_SPACE Error: Target repo_id not specified.")
|
750 |
elif change['type'] == 'SET_PRIVACY':
|
|
|
805 |
if not final_overall_status: final_overall_status = "No operations were applied."
|
806 |
|
807 |
_status_reload = f"{final_overall_status} | Reloading Space state..."
|
808 |
+
yield gr.update(value=_status_reload)
|
809 |
|
810 |
refreshed_file_list = []
|
811 |
reload_error = None
|
|
|
880 |
selected_value = default_model if default_model in models else (models[0] if models else None)
|
881 |
return gr.update(choices=models, value=selected_value)
|
882 |
|
883 |
+
def handle_detect_user(hf_api_key_ui, owner_name_ui):
|
884 |
_status = "Detecting user from token..."
|
885 |
+
# Yield initial state to show loading status and clear potentially outdated owner/spaces list
|
886 |
+
yield gr.update(value=_status), gr.update(value=""), gr.update(value="*Listing spaces...*")
|
|
|
|
|
887 |
|
888 |
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
889 |
if token_err:
|
890 |
_status = f"Detection Error: {token_err}"
|
891 |
+
yield gr.update(value=_status), gr.update(), gr.update(value="*Could not list spaces due to token error.*")
|
|
|
892 |
return
|
893 |
|
894 |
try:
|
|
|
898 |
_status = f"User detected: {owner_name}"
|
899 |
owner_update = gr.update(value=owner_name)
|
900 |
|
901 |
+
# Immediately trigger listing spaces using the detected owner
|
902 |
list_spaces_md, list_err = build_logic_list_user_spaces(hf_api_key=token, owner=owner_name)
|
903 |
if list_err:
|
904 |
list_spaces_update = gr.update(value=f"List Spaces Error: {list_err}")
|
|
|
906 |
else:
|
907 |
list_spaces_update = gr.update(value=list_spaces_md)
|
908 |
|
909 |
+
yield gr.update(value=_status), owner_update, list_spaces_update
|
|
|
|
|
910 |
|
911 |
except Exception as e:
|
912 |
_status = f"Detection Error: Could not detect user from token: {e}"
|
913 |
print(f"Error in handle_detect_user: {e}")
|
914 |
import traceback
|
915 |
traceback.print_exc()
|
916 |
+
yield gr.update(value=_status), gr.update(), gr.update(value="*Could not list spaces due to user detection error.*")
|
|
|
917 |
|
918 |
|
919 |
def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
|
|
|
924 |
_changeset_clear = []
|
925 |
_changeset_summary_clear = "*No changes proposed.*"
|
926 |
_confirm_ui_hidden = gr.update(visible=False)
|
927 |
+
_list_spaces_display_clear = gr.update() # Don't clear list spaces display on load, it's controlled by its own button
|
928 |
_target_owner_clear, _target_space_clear, _target_private_clear = gr.update(value=""), gr.update(value=""), gr.update(value=False)
|
929 |
|
930 |
|
|
|
931 |
yield (
|
932 |
gr.update(value=_formatted_md_val), gr.update(value=_detected_preview_val), gr.update(value=_status_val), _file_browser_update,
|
933 |
gr.update(value=ui_owner_name), gr.update(value=ui_space_name),
|
934 |
_iframe_html_update, _download_btn_update, gr.update(value=_build_status_clear),
|
935 |
gr.update(value=_edit_status_clear), gr.update(value=_runtime_status_clear),
|
936 |
_changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
|
937 |
+
_list_spaces_display_clear, _target_owner_clear, _target_space_clear, _target_private_clear
|
938 |
)
|
939 |
|
940 |
owner_to_use = ui_owner_name
|
941 |
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
942 |
if token_err:
|
943 |
_status_val = f"Load Error: {token_err}"
|
944 |
+
yield gr.update(value=_status_val),
|
|
|
945 |
return
|
946 |
if not owner_to_use:
|
947 |
try:
|
948 |
user_info = build_logic_whoami(token=token)
|
949 |
owner_to_use = user_info.get('name')
|
950 |
if not owner_to_use: raise Exception("Could not find user name from token.")
|
951 |
+
yield gr.update(value=owner_to_use), gr.update(value=f"Loading Space: {owner_to_use}/{ui_space_name} (Auto-detected owner)...")
|
|
|
952 |
except Exception as e:
|
953 |
_status_val = f"Load Error: Error auto-detecting owner: {e}"
|
954 |
+
yield gr.update(value=_status_val),
|
|
|
955 |
return
|
956 |
|
957 |
if not owner_to_use or not ui_space_name:
|
958 |
_status_val = "Load Error: Owner and Space Name are required."
|
959 |
+
yield gr.update(value=_status_val),
|
|
|
960 |
return
|
961 |
|
962 |
sdk, file_list, err = get_space_repository_info(hf_api_key_ui, ui_space_name, owner_to_use)
|
963 |
|
964 |
+
yield gr.update(value=owner_to_use), gr.update(value=ui_space_name),
|
|
|
|
|
965 |
|
966 |
if err:
|
967 |
_status_val = f"Load Error: {err}"
|
968 |
parsed_code_blocks_state_cache = []
|
969 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
|
|
970 |
yield (
|
971 |
gr.update(value=_formatted), gr.update(value=_detected), gr.update(value=_status_val),
|
972 |
+
gr.update(visible=False, choices=[], value=None),
|
973 |
+
gr.update(), gr.update(),
|
974 |
+
gr.update(value=None, visible=False),
|
975 |
+
_download, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
|
|
|
976 |
)
|
977 |
return
|
978 |
|
|
|
1001 |
|
1002 |
runtime_status_md = handle_refresh_space_status(hf_api_key_ui, owner_to_use, ui_space_name)
|
1003 |
|
|
|
1004 |
yield (
|
1005 |
+
gr.update(value=_formatted), gr.update(value=_detected), gr.update(value=_status_val),
|
1006 |
+
file_browser_update,
|
1007 |
+
gr.update(), gr.update(),
|
1008 |
+
iframe_update,
|
1009 |
+
_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(), gr.update()
|
|
|
|
|
|
|
1010 |
)
|
1011 |
|
1012 |
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):
|
|
|
1017 |
_changeset_summary_clear = "*Manual build initiated, changes plan cleared.*"
|
1018 |
_confirm_ui_hidden = gr.update(visible=False)
|
1019 |
|
|
|
1020 |
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),
|
1021 |
_changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
|
1022 |
+
gr.update(), gr.update(), gr.update(), gr.update())
|
1023 |
+
|
1024 |
|
1025 |
if not ui_space_name_part or "/" in ui_space_name_part:
|
1026 |
_build_status = f"Build Error: Invalid Space Name '{ui_space_name_part}'."
|
1027 |
+
yield gr.update(value=_build_status),
|
1028 |
return
|
1029 |
|
1030 |
parsed_content = build_logic_parse_markdown(formatted_markdown_content)
|
|
|
1038 |
|
1039 |
if not manual_changeset:
|
1040 |
_build_status = "Build Error: No target space specified or no files parsed from markdown."
|
1041 |
+
yield gr.update(value=_build_status),
|
1042 |
return
|
1043 |
|
1044 |
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
1045 |
if token_err:
|
1046 |
_build_status = f"Build Error: API Token Error: {token_err}"
|
1047 |
+
yield gr.update(value=_build_status),
|
1048 |
return
|
1049 |
api = HfApi(token=token)
|
1050 |
repo_id_target = f"{ui_owner_name_part}/{ui_space_name_part}"
|
|
|
1056 |
except HfHubHTTPError as e_http:
|
1057 |
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.")
|
1058 |
if e_http.response and e_http.response.status_code in (401, 403):
|
1059 |
+
_build_status = f"Build Error: {build_status_messages[-1]}. Cannot proceed with file upload."; yield gr.update(value=_build_status); return
|
1060 |
except Exception as e:
|
1061 |
build_status_messages.append(f"CREATE_SPACE Error: {e}")
|
1062 |
+
_build_status = f"Build Error: {build_status_messages[-1]}. Cannot proceed with file upload."; yield gr.update(value=_build_status); return
|
1063 |
|
1064 |
|
1065 |
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]
|
|
|
1125 |
|
1126 |
def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path):
|
1127 |
if not selected_file_path:
|
1128 |
+
return "", "Select a file.", "", gr.update(language="plaintext")
|
1129 |
|
1130 |
content, err = get_space_file_content(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path)
|
1131 |
if err:
|
1132 |
+
return "", f"Load Error: {err}", "", gr.update(language="plaintext")
|
1133 |
|
1134 |
lang = _infer_lang_from_filename(selected_file_path)
|
1135 |
commit_msg = f"Update {selected_file_path}"
|
|
|
1172 |
|
1173 |
def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path):
|
1174 |
if not ui_owner_name_part or not ui_space_name_part:
|
1175 |
+
return "Delete Error: Cannot delete file. Please load a space first.", gr.update(), "", "", "plaintext", gr.update(), gr.update(), gr.update()
|
1176 |
if not file_to_delete_path:
|
1177 |
+
return "Delete Error: No file selected to delete.", gr.update(), "", "", "plaintext", gr.update(), gr.update(), gr.update()
|
1178 |
|
1179 |
status_msg = build_logic_delete_space_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path)
|
1180 |
|
|
|
1188 |
parsed_code_blocks_state_cache = [b for b in parsed_code_blocks_state_cache if b["filename"] != file_to_delete_path]
|
1189 |
file_content_editor_update = gr.update(value="")
|
1190 |
commit_message_update = gr.update(value="")
|
1191 |
+
editor_lang_update = gr.update(language="plaintext")
|
1192 |
file_list, _ = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part)
|
1193 |
file_browser_update = gr.update(choices=sorted(file_list or []), value=None)
|
1194 |
|
|
|
1286 |
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()
|
1287 |
if "/" in target_space_name:
|
1288 |
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()
|
|
|
1289 |
global parsed_code_blocks_state_cache
|
1290 |
+
|
1291 |
+
|
1292 |
source_repo_id = f"{source_owner}/{source_space_name}"
|
1293 |
target_repo_id = f"{target_owner}/{target_space_name}"
|
1294 |
|
|
|
1300 |
status_msg = f"Duplication Result: {result_message}"
|
1301 |
|
1302 |
_status_reload = f"{status_msg} | Attempting to load the new Space [{target_repo_id}]..."
|
1303 |
+
yield gr.update(value=_status_reload)
|
1304 |
|
1305 |
new_owner = target_owner
|
1306 |
new_space_name = target_space_name
|
|
|
1309 |
|
1310 |
if err_list:
|
1311 |
reload_error = f"Error reloading file list after duplication: {err_list}"
|
|
|
1312 |
parsed_code_blocks_state_cache = []
|
1313 |
_file_browser_update = gr.update(visible=False, choices=[], value=None)
|
1314 |
_iframe_html_update = gr.update(value=None, visible=False)
|
|
|
1345 |
owner_update, space_update,
|
1346 |
gr.update(value=_formatted), gr.update(value=_detected), _download,
|
1347 |
_file_browser_update, _iframe_html_update, gr.update(value=runtime_status_md)
|
1348 |
+
)
|
1349 |
+
|
1350 |
+
|
1351 |
+
custom_theme = gr.themes.Base(primary_hue="teal", secondary_hue="purple", neutral_hue="zinc", text_size="sm", spacing_size="md", radius_size="sm", font=["System UI", "sans-serif"])
|
1352 |
+
custom_css = """
|
1353 |
+
body { background: linear-gradient(to bottom right, #2c3e50, #34495e); color: #ecf0f1; }
|
1354 |
+
.gradio-container { background: transparent !important; }
|
1355 |
+
.gr-box, .gr-panel, .gr-pill { background-color: rgba(44, 62, 80, 0.8) !important; border-color: rgba(189, 195, 199, 0.2) !important; }
|
1356 |
+
.gr-textbox, .gr-dropdown, .gr-button, .gr-code, .gr-chat-message { border-color: rgba(189, 195, 199, 0.3) !important; background-color: rgba(52, 73, 94, 0.9) !important; color: #ecf0f1 !important; }
|
1357 |
+
.gr-button.gr-button-primary { background-color: #1abc9c !important; color: white !important; border-color: #16a085 !important; }
|
1358 |
+
.gr-button.gr-button-secondary { background-color: #9b59b6 !important; color: white !important; border-color: #8e44ad !important; }
|
1359 |
+
.gr-button.gr-button-stop { background-color: #e74c3c !important; color: white !important; border-color: #c0392b !important; }
|
1360 |
+
.gr-markdown { background-color: rgba(44, 62, 80, 0.7) !important; padding: 10px; border-radius: 5px; overflow-x: auto; }
|
1361 |
+
.gr-markdown h1, .gr-markdown h2, .gr-markdown h3, .gr-markdown h4, .gr-markdown h5, .gr-markdown h6 { color: #ecf0f1 !important; border-bottom-color: rgba(189, 195, 199, 0.3) !important; }
|
1362 |
+
.gr-markdown pre code { background-color: rgba(52, 73, 94, 0.95) !important; border-color: rgba(189, 195, 199, 0.3) !important; }
|
1363 |
+
.gr-chatbot { background-color: rgba(44, 62, 80, 0.7) !important; border-color: rgba(189, 195, 199, 0.2) !important; }
|
1364 |
+
.gr-chatbot .message { background-color: rgba(52, 73, 94, 0.9) !important; color: #ecf0f1 !important; border-color: rgba(189, 195, 199, 0.3) !important; }
|
1365 |
+
.gr-chatbot .message.user { background-color: rgba(46, 204, 113, 0.9) !important; color: black !important; }
|
1366 |
+
.gradio-container .gr-accordion { border-color: rgba(189, 195, 199, 0.3) !important; }
|
1367 |
+
.gradio-container .gr-accordion.closed { background-color: rgba(52, 73, 94, 0.9) !important; }
|
1368 |
+
.gradio-container .gr-accordion.open { background-color: rgba(44, 62, 80, 0.8) !important; }
|
1369 |
+
|
1370 |
+
"""
|
1371 |
+
|
1372 |
+
with gr.Blocks(theme=custom_theme, css=custom_css) as demo:
|
1373 |
+
changeset_state = gr.State([])
|
1374 |
+
|
1375 |
+
gr.Markdown("# π€ AI-Powered Hugging Face Space Commander")
|
1376 |
+
gr.Markdown("Use an AI assistant to create, modify, build, and manage your Hugging Face Spaces directly from this interface.")
|
1377 |
+
gr.Markdown("## β This will cause changes to your huggingface spaces if you give it your Huggingface Key")
|
1378 |
+
gr.Markdown("β Under Development - Use with Caution")
|
1379 |
+
|
1380 |
+
|
1381 |
+
with gr.Sidebar():
|
1382 |
+
with gr.Column(scale=1):
|
1383 |
+
with gr.Accordion("βοΈ Configuration", open=True):
|
1384 |
+
hf_api_key_input = gr.Textbox(label="Hugging Face Token", type="password", placeholder="hf_... (uses env var HF_TOKEN if empty)")
|
1385 |
+
owner_name_input = gr.Textbox(label="HF Owner Name", placeholder="e.g., your-username")
|
1386 |
+
space_name_input = gr.Textbox(label="HF Space Name", value="")
|
1387 |
+
load_space_button = gr.Button("π Load Existing Space", variant="secondary")
|
1388 |
+
detect_user_button = gr.Button("π€ Detect User from Token", variant="secondary")
|
1389 |
+
|
1390 |
+
|
1391 |
+
with gr.Accordion("π€ AI Model Settings", open=True):
|
1392 |
+
available_providers = get_available_providers()
|
1393 |
+
default_provider = 'Groq'
|
1394 |
+
if default_provider not in available_providers:
|
1395 |
+
default_provider = available_providers[0] if available_providers else None
|
1396 |
+
elif len(available_providers) < 3:
|
1397 |
+
default_provider = available_providers[0] if available_providers else None
|
1398 |
+
|
1399 |
+
initial_models = get_models_for_provider(default_provider) if default_provider else []
|
1400 |
+
initial_model = get_default_model_for_provider(default_provider) if default_provider else None
|
1401 |
+
if initial_model not in initial_models:
|
1402 |
+
initial_model = initial_models[0] if initial_models else None
|
1403 |
+
|
1404 |
+
provider_select = gr.Dropdown(
|
1405 |
+
label="AI Provider",
|
1406 |
+
choices=available_providers,
|
1407 |
+
value=default_provider,
|
1408 |
+
allow_custom_value=False
|
1409 |
+
)
|
1410 |
+
model_select = gr.Dropdown(
|
1411 |
+
label="AI Model",
|
1412 |
+
choices=initial_models,
|
1413 |
+
value=initial_model,
|
1414 |
+
allow_custom_value=False
|
1415 |
+
)
|
1416 |
+
provider_api_key_input = gr.Textbox(label="Model Provider API Key (Optional)", type="password", placeholder="sk_... (overrides backend settings)")
|
1417 |
+
system_prompt_input = gr.Textbox(label="System Prompt", lines=10, value=DEFAULT_SYSTEM_PROMPT, elem_id="system-prompt")
|
1418 |
+
|
1419 |
+
with gr.Accordion("ποΈ Space Management", open=True):
|
1420 |
+
gr.Markdown("### Manual Actions")
|
1421 |
+
with gr.Group():
|
1422 |
+
gr.Markdown("Duplicate Current Space To:")
|
1423 |
+
target_owner_input = gr.Textbox(label="Target Owner Name", placeholder="e.g., new-username")
|
1424 |
+
target_space_name_input = gr.Textbox(label="Target Space Name", placeholder="e.g., new-space")
|
1425 |
+
target_private_checkbox = gr.Checkbox(label="Make Target Private", value=False)
|
1426 |
+
duplicate_space_button = gr.Button("π Duplicate Space", variant="secondary")
|
1427 |
+
|
1428 |
+
gr.Markdown("---")
|
1429 |
+
gr.Markdown("### List Spaces")
|
1430 |
+
list_spaces_button = gr.Button("π List My Spaces", variant="secondary")
|
1431 |
+
list_spaces_display = gr.Markdown("*List of spaces will appear here.*")
|
1432 |
+
|
1433 |
+
|
1434 |
+
with gr.Column(scale=2):
|
1435 |
+
gr.Markdown("## π¬ AI Assistant Chat")
|
1436 |
+
chatbot_display = gr.Chatbot(label="AI Chat", height=500, bubble_full_width=False, avatar_images=(None))
|
1437 |
+
with gr.Row():
|
1438 |
+
chat_message_input = gr.Textbox(show_label=False, placeholder="Your Message...", scale=7)
|
1439 |
+
send_chat_button = gr.Button("Send", variant="primary", scale=1)
|
1440 |
+
status_output = gr.Textbox(label="Last Action Status", interactive=False, value="Ready.")
|
1441 |
+
|
1442 |
+
with gr.Accordion("π Proposed Changes (Pending Confirmation)", open=False, visible=False) as confirm_accordion:
|
1443 |
+
changeset_display = gr.Markdown("*No changes proposed.*")
|
1444 |
+
with gr.Row():
|
1445 |
+
confirm_button = gr.Button("β
Confirm & Apply Changes", variant="primary", visible=False)
|
1446 |
+
cancel_button = gr.Button("β Cancel", variant="stop", visible=False)
|
1447 |
+
|
1448 |
+
with gr.Tabs():
|
1449 |
+
with gr.TabItem("π Generated Markdown & Build"):
|
1450 |
+
with gr.Row():
|
1451 |
+
with gr.Column(scale=2):
|
1452 |
+
formatted_space_output_display = gr.Textbox(label="Current Space Definition (Generated Markdown)", lines=20, interactive=True, value="*Load or create a space to see its definition.*")
|
1453 |
+
download_button = gr.DownloadButton(label="Download .md", interactive=False)
|
1454 |
+
with gr.Column(scale=1):
|
1455 |
+
gr.Markdown("### Manual Build & Status")
|
1456 |
+
space_sdk_select = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio", interactive=True)
|
1457 |
+
space_private_checkbox = gr.Checkbox(label="Make Space Private", value=False, interactive=True)
|
1458 |
+
build_space_button = gr.Button("π Build / Update Space from Markdown", variant="primary")
|
1459 |
+
build_status_display = gr.Textbox(label="Manual Build/Update Status", interactive=False, value="*Manual build status...*")
|
1460 |
+
gr.Markdown("---")
|
1461 |
+
refresh_status_button = gr.Button("π Refresh Runtime/Repo Status")
|
1462 |
+
space_runtime_status_display = gr.Markdown("*Runtime/Repo status will appear here.*")
|
1463 |
+
|
1464 |
+
with gr.TabItem("π Files Preview"):
|
1465 |
+
detected_files_preview = gr.Markdown(value="*A preview of the latest file versions will appear here.*")
|
1466 |
+
|
1467 |
+
with gr.TabItem("βοΈ Live File Editor & Preview"):
|
1468 |
+
with gr.Row():
|
1469 |
+
with gr.Column(scale=1):
|
1470 |
+
gr.Markdown("### Live Editor")
|
1471 |
+
file_browser_dropdown = gr.Dropdown(label="Select File in Space", choices=[], interactive=True)
|
1472 |
+
file_content_editor = gr.Code(label="File Content Editor", language="python", lines=15, interactive=True, value="")
|
1473 |
+
commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Updated app.py", interactive=True, value="")
|
1474 |
+
with gr.Row():
|
1475 |
+
update_file_button = gr.Button("Commit Changes", variant="primary", interactive=True)
|
1476 |
+
delete_file_button = gr.Button("ποΈ Delete Selected File", variant="stop", interactive=True)
|
1477 |
+
edit_status_display = gr.Textbox(label="File Edit/Delete Status", interactive=False, value="")
|
1478 |
+
with gr.Column(scale=1):
|
1479 |
+
gr.Markdown("### Live Space Preview")
|
1480 |
+
space_iframe_display = gr.HTML(value="", visible=True)
|
1481 |
+
|
1482 |
+
|
1483 |
+
provider_select.change(update_models_dropdown, inputs=provider_select, outputs=model_select)
|
1484 |
+
|
1485 |
+
chat_inputs = [
|
1486 |
+
chat_message_input, chatbot_display, hf_api_key_input,
|
1487 |
+
provider_api_key_input, provider_select, model_select, system_prompt_input,
|
1488 |
+
owner_name_input, space_name_input
|
1489 |
+
]
|
1490 |
+
chat_outputs = [
|
1491 |
+
chat_message_input, chatbot_display, status_output,
|
1492 |
+
detected_files_preview, formatted_space_output_display, download_button,
|
1493 |
+
changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button
|
1494 |
+
]
|
1495 |
+
send_chat_button.click(handle_chat_submit, inputs=chat_inputs, outputs=chat_outputs)
|
1496 |
+
chat_message_input.submit(handle_chat_submit, inputs=chat_inputs, outputs=chat_outputs)
|
1497 |
+
|
1498 |
+
confirm_inputs = [hf_api_key_input, owner_name_input, space_name_input, changeset_state]
|
1499 |
+
confirm_outputs = [
|
1500 |
+
status_output, formatted_space_output_display, detected_files_preview, download_button,
|
1501 |
+
confirm_accordion, confirm_button, cancel_button, changeset_state, changeset_display,
|
1502 |
+
owner_name_input, space_name_input, file_browser_dropdown, space_iframe_display, space_runtime_status_display
|
1503 |
+
]
|
1504 |
+
confirm_button.click(handle_confirm_changes, inputs=confirm_inputs, outputs=confirm_outputs)
|
1505 |
+
|
1506 |
+
cancel_outputs = [
|
1507 |
+
status_output, changeset_state, changeset_display,
|
1508 |
+
confirm_accordion, confirm_button, cancel_button
|
1509 |
+
]
|
1510 |
+
cancel_button.click(handle_cancel_changes, inputs=None, outputs=cancel_outputs)
|
1511 |
+
|
1512 |
+
load_space_outputs = [
|
1513 |
+
formatted_space_output_display, detected_files_preview, status_output,
|
1514 |
+
file_browser_dropdown, owner_name_input, space_name_input,
|
1515 |
+
space_iframe_display, download_button, build_status_display,
|
1516 |
+
edit_status_display, space_runtime_status_display,
|
1517 |
+
changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button,
|
1518 |
+
list_spaces_display, target_owner_input, target_space_name_input, target_private_checkbox
|
1519 |
+
]
|
1520 |
+
load_space_button.click(
|
1521 |
+
fn=handle_load_existing_space,
|
1522 |
+
inputs=[hf_api_key_input, owner_name_input, space_name_input],
|
1523 |
+
outputs=load_space_outputs
|
1524 |
+
)
|
1525 |
+
|
1526 |
+
detect_user_outputs = [status_output, owner_name_input, list_spaces_display]
|
1527 |
+
detect_user_button.click(fn=handle_detect_user, inputs=[hf_api_key_input, owner_name_input], outputs=detect_user_outputs)
|
1528 |
+
|
1529 |
+
build_outputs = [
|
1530 |
+
build_status_display, space_iframe_display, file_browser_dropdown,
|
1531 |
+
owner_name_input, space_name_input,
|
1532 |
+
changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button,
|
1533 |
+
formatted_space_output_display, detected_files_preview, download_button, space_runtime_status_display
|
1534 |
+
]
|
1535 |
+
build_inputs = [
|
1536 |
+
hf_api_key_input, space_name_input, owner_name_input, space_sdk_select,
|
1537 |
+
space_private_checkbox, formatted_space_output_display
|
1538 |
+
]
|
1539 |
+
build_space_button.click(fn=handle_build_space_button, inputs=build_inputs, outputs=build_outputs)
|
1540 |
+
|
1541 |
+
file_edit_load_outputs = [file_content_editor, edit_status_display, commit_message_input, file_content_editor]
|
1542 |
+
file_browser_dropdown.change(fn=handle_load_file_for_editing, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown], outputs=file_edit_load_outputs)
|
1543 |
+
|
1544 |
+
commit_file_outputs = [edit_status_display, file_browser_dropdown, formatted_space_output_display, detected_files_preview, download_button]
|
1545 |
+
update_file_button.click(fn=handle_commit_file_changes, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown, file_content_editor, commit_message_input], outputs=commit_file_outputs)
|
1546 |
+
|
1547 |
+
delete_file_outputs = [
|
1548 |
+
edit_status_display, file_browser_dropdown,
|
1549 |
+
file_content_editor, commit_message_input, file_content_editor,
|
1550 |
+
formatted_space_output_display, detected_files_preview, download_button
|
1551 |
+
]
|
1552 |
+
delete_file_button.click(fn=handle_delete_file, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown], outputs=delete_file_outputs)
|
1553 |
+
|
1554 |
+
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])
|
1555 |
+
|
1556 |
+
manual_duplicate_inputs = [hf_api_key_input, owner_name_input, space_name_input, target_owner_input, target_space_name_input, target_private_checkbox]
|
1557 |
+
manual_duplicate_outputs = [
|
1558 |
+
status_output, owner_name_input, space_name_input,
|
1559 |
+
formatted_space_output_display, detected_files_preview, download_button,
|
1560 |
+
file_browser_dropdown, space_iframe_display, space_runtime_status_display
|
1561 |
+
]
|
1562 |
+
duplicate_space_button.click(fn=handle_manual_duplicate_space, inputs=manual_duplicate_inputs, outputs=manual_duplicate_outputs)
|
1563 |
+
|
1564 |
+
list_spaces_button.click(fn=handle_list_spaces, inputs=[hf_api_key_input, owner_name_input], outputs=[list_spaces_display])
|
1565 |
+
|
1566 |
+
|
1567 |
+
if __name__ == "__main__":
|
1568 |
+
demo.launch(debug=False, mcp_server=True)
|