broadfield-dev commited on
Commit
02f7aad
Β·
verified Β·
1 Parent(s): 2bdd8a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +286 -104
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("<", "<").replace(">", ">")
99
 
100
  def _infer_lang_from_filename(filename):
101
- if not filename: return "text"
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': 'text', 'log': 'text', 'ini': 'ini', 'conf': 'text', 'cfg': 'text',
114
- 'csv': 'text', 'tsv': 'text', 'err': 'text',
115
- '.env': 'text', '.gitignore': 'text', '.npmrc': 'text', '.gitattributes': 'text',
116
  'makefile': 'makefile',
117
  }
118
- return mapping.get(ext, "text")
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 'text'
123
- return "text"
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 "text", "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,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', 'text') or 'text'
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', 'text') or 'text'
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.copy() 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,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
- # Yield None for the other 15 outputs that are part of this generator's outputs list
599
- yield None, None, None, None, None, None, None, None, None, None, None, None, None, None, None # 15 None values
600
-
601
 
602
  if not changeset:
603
- # This case should be prevented by hiding the button, but safety first
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
- # Yield status update only
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, # Status, markdown, preview, download
681
- gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), # Hide confirm UI
682
- [], gr.update(value="*No changes proposed.*"), # Clear changeset state and display
683
- owner_update, space_update, file_browser_update, iframe_update, # Update space/owner fields, file browser, iframe
684
- gr.update(value=runtime_status_md) # Update runtime status
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) # Refresh status after failed delete attempt
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), 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() # Yield status
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 handle_detect_user_and_list_spaces(hf_api_key_ui):
904
  _status = "Detecting user from token..."
905
- # Yield initial state: update status, clear owner/space fields, clear list display, clear duplicate fields
906
- # outputs list: [status_output, owner_name_input, space_name_input, list_spaces_display, file_browser_dropdown, space_iframe_display, formatted_space_output_display, detected_files_display, build_status_display, edit_status_display, space_runtime_status_display, changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button, target_owner_input, target_space_name_input, target_private_checkbox] (19 items)
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
- # Yield error status, keep fields cleared, update list display error
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
- # Final yield: Update status, owner, list spaces, keep others cleared/defaulted
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
- # Final yield on error: Update status, keep others cleared/defaulted, list spaces error
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
- _list_spaces_display_placeholder = gr.update(value="*List of spaces will appear here.*")
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
- _list_spaces_display_placeholder, _target_owner_clear, _target_space_clear, _target_private_clear
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
- # Yield only status and keep others as initial state
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
- # Yield updated owner and status, keep others as initial state
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
- # Yield only status and keep others as initial state
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
- # Yield only status and keep others as initial state
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
- # Yield updated owner and space textboxes, keep others as initial state
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), # Clear and hide file browser
1006
- gr.update(), gr.update(), # Keep owner/space textboxes
1007
- gr.update(value=None, visible=False), # Hide iframe
1008
- _download, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), # Build/Edit/Runtime status (from initial clear state), Changeset, Confirm UI
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), # Markdown, preview, status
1041
- file_browser_update, # File browser
1042
- gr.update(), gr.update(), # Keep owner/space textboxes
1043
- iframe_update, # Iframe
1044
- _download, # Download button
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()) # Initial yield
 
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), 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()
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), 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()
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), 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()
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), 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(); return
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), 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(); return
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="text")
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="text")
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(), "", "", "text", gr.update(), gr.update(), gr.update()
1214
  if not file_to_delete_path:
1215
- return "Delete Error: No file selected to delete.", gr.update(), "", "", "text", gr.update(), gr.update(), 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="text")
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), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
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)