broadfield-dev commited on
Commit
13591d5
·
verified ·
1 Parent(s): fe71f92

Update app_logic.py

Browse files
Files changed (1) hide show
  1. app_logic.py +143 -117
app_logic.py CHANGED
@@ -1,7 +1,8 @@
1
  import os
2
  import re
3
  import tempfile
4
- import git
 
5
  from huggingface_hub import (
6
  create_repo,
7
  upload_folder,
@@ -13,13 +14,14 @@ import logging
13
  from pathlib import Path
14
  from PIL import Image
15
 
 
16
  try:
17
  from keylock_decode import decode_from_image_pil
18
  KEYLOCK_DECODE_AVAILABLE = True
19
  except ImportError:
20
  KEYLOCK_DECODE_AVAILABLE = False
21
  decode_from_image_pil = None
22
- logging.warning("keylock-decode library not found. Image decoding feature will be disabled.")
23
 
24
  logging.basicConfig(
25
  level=logging.INFO,
@@ -37,36 +39,21 @@ def _get_api_token(ui_token_from_textbox=None):
37
  logger.info("Using API token from UI textbox.")
38
  return ui_token_from_textbox, None
39
  logger.warning("HF API token not found in environment or UI textbox.")
40
- return None, "Error: Hugging Face API token not provided. Please enter it in the textbox or load it from an image."
41
 
42
- # --- `load_token_from_image_and_set_env` (Unchanged from previous debug version) ---
43
  def load_token_from_image_and_set_env(image_pil_object: Image.Image, password: str):
44
  if not KEYLOCK_DECODE_AVAILABLE:
45
  return "Error: KeyLock-Decode library is not installed. This feature is disabled."
46
- if image_pil_object is None: return "Error: No image provided for decoding."
47
  if not password: return "Error: Password cannot be empty for image decoding."
48
  status_messages_display = []
49
- debug_image_path_str = "Not saved (error or no image)."
50
- try:
51
- if image_pil_object:
52
- original_mode = getattr(image_pil_object, 'mode', 'N/A')
53
- original_format = getattr(image_pil_object, 'format', 'N/A')
54
- original_size = getattr(image_pil_object, 'size', 'N/A')
55
- logger.info(f"Received PIL Image from Gradio. Mode: {original_mode}, Original Format: {original_format}, Size: {original_size}")
56
- status_messages_display.append(f"[DEBUG] Gradio provided PIL Image - Mode: {original_mode}, Original Format: {original_format}, Size: {original_size}")
57
- temp_dir = Path(tempfile.gettempdir()) / "gradio_image_debug"
58
- temp_dir.mkdir(parents=True, exist_ok=True)
59
- debug_image_path = temp_dir / "image_from_gradio.png"
60
- image_pil_object.save(debug_image_path, "PNG")
61
- debug_image_path_str = str(debug_image_path)
62
- status_messages_display.append(f"**[DEBUG ACTION REQUIRED]** Image from Gradio saved to: `{debug_image_path_str}`. Try decoding this file with the standalone `keylock-decode` CLI.")
63
- logger.info(f"Debug image from Gradio saved to: {debug_image_path_str}")
64
- else:
65
- status_messages_display.append("[DEBUG] No PIL image object received for debug-saving.")
66
- except Exception as save_exc:
67
- logger.exception("Error during debug-saving of image from Gradio:")
68
- status_messages_display.append(f"[DEBUG] Error saving image for debugging: {str(save_exc)}. Path: {debug_image_path_str}")
69
  try:
 
70
  decoded_data, status_msgs_from_lib = decode_from_image_pil(image_pil_object, password, set_environment_variables=True)
71
  status_messages_display.extend(status_msgs_from_lib)
72
  if decoded_data:
@@ -75,92 +62,48 @@ def load_token_from_image_and_set_env(image_pil_object: Image.Image, password: s
75
  display_value = '********' if any(k_word in key.upper() for k_word in ['TOKEN', 'KEY', 'SECRET', 'PASS']) else value
76
  status_messages_display.append(f"- {key}: {display_value}")
77
  if os.getenv('HF_TOKEN'):
78
- status_messages_display.append(f"\n**SUCCESS: HF_TOKEN was found and set in environment.**")
79
  elif 'HF_TOKEN' in decoded_data:
80
- status_messages_display.append(f"\nWarning: HF_TOKEN decoded but os.getenv('HF_TOKEN') not found.")
81
  else:
82
- status_messages_display.append("\nNote: HF_TOKEN not specifically found in decoded image data.")
83
- except ValueError as e:
84
  status_messages_display.append(f"**Decoding Error (e.g., bad password, corrupted data):** {e}")
85
  except Exception as e:
 
86
  status_messages_display.append(f"**An unexpected error occurred during decoding:** {str(e)}")
87
  return "\n".join(status_messages_display)
88
 
89
-
90
- # --- Updated `parse_markdown` function ---
91
  def parse_markdown(markdown_input):
92
- """
93
- Parse markdown input to extract space details and file structure.
94
- Correctly handles code blocks by excluding the ``` fences.
95
- """
96
  space_info = {"repo_name_md": "", "owner_md": "", "files": []}
97
- current_file_path = None
98
- current_file_content_lines = []
99
-
100
- # State machine variables
101
- # in_file_definition: True when we are after "### File: " and before the next "### File: " or end of input.
102
- # in_code_block: True when we are between ``` and ```.
103
- in_file_definition = False
104
- in_code_block = False
105
-
106
  lines = markdown_input.strip().split("\n")
107
-
108
  for line_content_orig in lines:
109
  line_content_stripped = line_content_orig.strip()
110
-
111
- # Check for new file definition first
112
  if line_content_stripped.startswith("### File:"):
113
- # Save previous file's content if any
114
  if current_file_path and in_file_definition:
115
- space_info["files"].append({
116
- "path": current_file_path,
117
- "content": "\n".join(current_file_content_lines)
118
- })
119
- current_file_content_lines = [] # Reset for the new file
120
-
121
  current_file_path = line_content_stripped.replace("### File:", "").strip()
122
- in_file_definition = True
123
- in_code_block = False # Reset code block state for a new file
124
- continue # Move to next line, don't process "### File:" as content
125
-
126
- # If we are not in any file definition, skip lines until we find one
127
  if not in_file_definition:
128
- if line_content_stripped.startswith("# Space:"): # Parse space name from markdown
129
  full_space_name_md = line_content_stripped.replace("# Space:", "").strip()
130
- if "/" in full_space_name_md:
131
- space_info["owner_md"], space_info["repo_name_md"] = full_space_name_md.split("/", 1)
132
- else:
133
- space_info["repo_name_md"] = full_space_name_md
134
- # Other top-level constructs like "## File Structure" can be ignored or parsed if needed
135
  continue
136
-
137
- # Now, we are definitely within a "### File: ..." block's content area
138
-
139
- # Handle code block fences ```
140
  if line_content_stripped.startswith("```"):
141
- if in_code_block:
142
- in_code_block = False # Exiting a code block
143
- else:
144
- in_code_block = True # Entering a code block
145
- # In either case, we *don't* append the ``` line itself to content
146
- continue # Move to next line
147
-
148
- # If we are inside a code block OR not (i.e., plain text lines for the file),
149
- # append the original line (preserving leading/trailing whitespace for that line).
150
  current_file_content_lines.append(line_content_orig)
151
-
152
- # After loop, append the last file's content if any
153
- if current_file_path and in_file_definition: # Ensure we were actually defining a file
154
- space_info["files"].append({
155
- "path": current_file_path,
156
- "content": "\n".join(current_file_content_lines)
157
- })
158
-
159
- # Filter out any entries that might have been added with no path (shouldn't happen with current logic)
160
  space_info["files"] = [f for f in space_info["files"] if f.get("path")]
161
  return space_info
162
 
163
-
164
  # --- `_determine_repo_id` (Unchanged) ---
165
  def _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui):
166
  if not space_name_ui: return None, "Error: Space Name cannot be empty."
@@ -179,8 +122,80 @@ def _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui):
179
  if not final_owner: return None, "Error: Owner could not be determined."
180
  return f"{final_owner}/{space_name_ui}", None
181
 
182
- # --- Core Functions: `create_space`, `view_space_files`, `update_space_file` (Unchanged) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  def create_space(ui_api_token_from_textbox, space_name_ui, owner_ui, sdk_ui, markdown_input):
 
184
  repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
185
  try:
186
  resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
@@ -188,61 +203,72 @@ def create_space(ui_api_token_from_textbox, space_name_ui, owner_ui, sdk_ui, mar
188
  repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
189
  if err: return err
190
  repo_id_for_error_logging = repo_id
191
- space_info = parse_markdown(markdown_input) # Uses the updated parser
192
  if not space_info["files"]: return "Error: No files found in markdown. Use '### File: path/to/file.ext'."
193
- with tempfile.TemporaryDirectory() as temp_dir:
194
- repo_local_path = Path(temp_dir) / "repo_upload_content"
195
- repo_local_path.mkdir(exist_ok=True)
196
  for file_info in space_info["files"]:
197
  if not file_info.get("path"): continue
198
- file_path_abs = repo_local_path / file_info["path"]
199
  file_path_abs.parent.mkdir(parents=True, exist_ok=True)
200
- with open(file_path_abs, "w", encoding="utf-8") as f: f.write(file_info["content"]) # Content is now clean
201
  try:
202
  create_repo(repo_id=repo_id, token=resolved_api_token, repo_type="space", space_sdk=sdk_ui, private=False)
203
  except Exception as e:
204
  err_str = str(e).lower()
205
  if not ("already exists" in err_str or "you already created this repo" in err_str or "exists" in err_str):
206
  return f"Error creating Space '{repo_id}': {str(e)}"
207
- upload_folder(repo_id=repo_id, folder_path=str(repo_local_path), path_in_repo=".", token=resolved_api_token, repo_type="space", commit_message=f"Initial Space setup of {repo_id} via Builder")
 
208
  return f"Successfully created/updated Space: [{repo_id}](https://huggingface.co/spaces/{repo_id})"
209
  except Exception as e:
210
  logger.exception(f"Error in create_space for {repo_id_for_error_logging}:")
211
  return f"Error during Space creation/update: {str(e)}"
212
 
213
- def view_space_files(ui_api_token_from_textbox, space_name_ui, owner_ui):
214
- repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
215
- try:
216
- resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
217
- if token_err: return token_err
218
- repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
219
- if err: return err
220
- repo_id_for_error_logging = repo_id
221
- files = list_repo_files(repo_id=repo_id, token=resolved_api_token, repo_type="space")
222
- if files: return f"Files in `{repo_id}`:\n\n" + "\n".join([f"- `{f}`" for f in files])
223
- return f"No files found in Space `{repo_id}`."
224
- except Exception as e:
225
- logger.exception(f"Error in view_space_files for {repo_id_for_error_logging}:")
226
- return f"Error listing files for `{repo_id_for_error_logging}`: {str(e)}"
227
 
228
  def update_space_file(ui_api_token_from_textbox, space_name_ui, owner_ui, file_path_in_repo, file_content, commit_message_ui):
 
 
229
  repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
230
  try:
231
  resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
232
  if token_err: return token_err
 
233
  repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
234
  if err: return err
235
  repo_id_for_error_logging = repo_id
236
- if not file_path_in_repo: return "Error: File Path cannot be empty."
 
 
 
 
237
  commit_message_ui = commit_message_ui or f"Update {file_path_in_repo} via Space Builder"
238
- with tempfile.TemporaryDirectory() as temp_dir:
239
- repo_local_clone_path = Path(temp_dir) / "cloned_space_repo_for_update"
240
- cloned_repo = Repository(local_dir=str(repo_local_clone_path), clone_from=f"https://huggingface.co/spaces/{repo_id}", repo_type="space", use_auth_token=resolved_api_token, git_user="Space Builder Bot", git_email="[email protected]")
 
 
 
 
 
 
 
 
 
 
241
  full_local_file_path = Path(cloned_repo.local_dir) / file_path_in_repo
242
- full_local_file_path.parent.mkdir(parents=True, exist_ok=True)
243
- with open(full_local_file_path, "w", encoding="utf-8") as f: f.write(file_content)
 
 
 
244
  cloned_repo.push_to_hub(commit_message=commit_message_ui)
 
 
245
  return f"Successfully updated `{file_path_in_repo}` in Space [{repo_id}](https://huggingface.co/spaces/{repo_id})"
246
  except Exception as e:
247
- logger.exception(f"Error in update_space_file for {repo_id_for_error_logging}:")
248
  return f"Error updating file for `{repo_id_for_error_logging}`: {str(e)}"
 
1
  import os
2
  import re
3
  import tempfile
4
+ import shutil # For rmtree
5
+ import git # Used by Repository indirectly
6
  from huggingface_hub import (
7
  create_repo,
8
  upload_folder,
 
14
  from pathlib import Path
15
  from PIL import Image
16
 
17
+ # Attempt to import keylock_decode
18
  try:
19
  from keylock_decode import decode_from_image_pil
20
  KEYLOCK_DECODE_AVAILABLE = True
21
  except ImportError:
22
  KEYLOCK_DECODE_AVAILABLE = False
23
  decode_from_image_pil = None
24
+ logging.warning("keylock-decode library not found. KeyLock Wallet image feature will be disabled.")
25
 
26
  logging.basicConfig(
27
  level=logging.INFO,
 
39
  logger.info("Using API token from UI textbox.")
40
  return ui_token_from_textbox, None
41
  logger.warning("HF API token not found in environment or UI textbox.")
42
+ return None, "Error: Hugging Face API token not provided. Please enter it or load from a KeyLock Wallet image."
43
 
44
+ # --- `load_token_from_image_and_set_env` (Unchanged from previous debug version, ensure debug lines are suitable) ---
45
  def load_token_from_image_and_set_env(image_pil_object: Image.Image, password: str):
46
  if not KEYLOCK_DECODE_AVAILABLE:
47
  return "Error: KeyLock-Decode library is not installed. This feature is disabled."
48
+ if image_pil_object is None: return "Error: No KeyLock Wallet image provided for decoding."
49
  if not password: return "Error: Password cannot be empty for image decoding."
50
  status_messages_display = []
51
+ # Optional: Keep debug saving if still needed, otherwise remove for cleaner output
52
+ # debug_image_path_str = "Not saved."
53
+ # try: ... debug save logic ...
54
+ # except Exception as save_exc: ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  try:
56
+ logger.info(f"Attempting to decode from KeyLock Wallet image...")
57
  decoded_data, status_msgs_from_lib = decode_from_image_pil(image_pil_object, password, set_environment_variables=True)
58
  status_messages_display.extend(status_msgs_from_lib)
59
  if decoded_data:
 
62
  display_value = '********' if any(k_word in key.upper() for k_word in ['TOKEN', 'KEY', 'SECRET', 'PASS']) else value
63
  status_messages_display.append(f"- {key}: {display_value}")
64
  if os.getenv('HF_TOKEN'):
65
+ status_messages_display.append(f"\n**SUCCESS: HF_TOKEN was found and set in environment from KeyLock Wallet image.**")
66
  elif 'HF_TOKEN' in decoded_data:
67
+ status_messages_display.append(f"\nWarning: HF_TOKEN decoded but os.getenv('HF_TOKEN') not found (unexpected).")
68
  else:
69
+ status_messages_display.append("\nNote: HF_TOKEN not specifically found in decoded KeyLock Wallet image data.")
70
+ except ValueError as e: # Specific errors from keylock-decode
71
  status_messages_display.append(f"**Decoding Error (e.g., bad password, corrupted data):** {e}")
72
  except Exception as e:
73
+ logger.exception("Unexpected error during KeyLock Wallet image decoding:")
74
  status_messages_display.append(f"**An unexpected error occurred during decoding:** {str(e)}")
75
  return "\n".join(status_messages_display)
76
 
77
+ # --- `parse_markdown` (Unchanged from previous corrected version) ---
 
78
  def parse_markdown(markdown_input):
 
 
 
 
79
  space_info = {"repo_name_md": "", "owner_md": "", "files": []}
80
+ current_file_path = None; current_file_content_lines = []
81
+ in_file_definition = False; in_code_block = False
 
 
 
 
 
 
 
82
  lines = markdown_input.strip().split("\n")
 
83
  for line_content_orig in lines:
84
  line_content_stripped = line_content_orig.strip()
 
 
85
  if line_content_stripped.startswith("### File:"):
 
86
  if current_file_path and in_file_definition:
87
+ space_info["files"].append({"path": current_file_path, "content": "\n".join(current_file_content_lines)})
 
 
 
 
 
88
  current_file_path = line_content_stripped.replace("### File:", "").strip()
89
+ current_file_content_lines = []
90
+ in_file_definition = True; in_code_block = False
91
+ continue
 
 
92
  if not in_file_definition:
93
+ if line_content_stripped.startswith("# Space:"):
94
  full_space_name_md = line_content_stripped.replace("# Space:", "").strip()
95
+ if "/" in full_space_name_md: space_info["owner_md"], space_info["repo_name_md"] = full_space_name_md.split("/", 1)
96
+ else: space_info["repo_name_md"] = full_space_name_md
 
 
 
97
  continue
 
 
 
 
98
  if line_content_stripped.startswith("```"):
99
+ in_code_block = not in_code_block
100
+ continue
 
 
 
 
 
 
 
101
  current_file_content_lines.append(line_content_orig)
102
+ if current_file_path and in_file_definition:
103
+ space_info["files"].append({"path": current_file_path, "content": "\n".join(current_file_content_lines)})
 
 
 
 
 
 
 
104
  space_info["files"] = [f for f in space_info["files"] if f.get("path")]
105
  return space_info
106
 
 
107
  # --- `_determine_repo_id` (Unchanged) ---
108
  def _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui):
109
  if not space_name_ui: return None, "Error: Space Name cannot be empty."
 
122
  if not final_owner: return None, "Error: Owner could not be determined."
123
  return f"{final_owner}/{space_name_ui}", None
124
 
125
+ # --- New/Modified Functions for File Browsing and Editing ---
126
+ CLONE_BASE_DIR = Path(tempfile.gettempdir()) / "space_builder_active_clones"
127
+ CLONE_BASE_DIR.mkdir(parents=True, exist_ok=True)
128
+
129
+ def _cleanup_old_clones(repo_id_slug_to_keep=None):
130
+ """Clean up old clone directories, optionally keeping one specific repo's clone."""
131
+ try:
132
+ for item in CLONE_BASE_DIR.iterdir():
133
+ if item.is_dir():
134
+ if repo_id_slug_to_keep and item.name == repo_id_slug_to_keep:
135
+ continue
136
+ logger.info(f"Cleaning up old clone: {item}")
137
+ shutil.rmtree(item)
138
+ except Exception as e:
139
+ logger.error(f"Error during old clone cleanup: {e}")
140
+
141
+ def get_space_local_clone_path(ui_api_token_from_textbox, space_name_ui, owner_ui, force_refresh=False):
142
+ """Clones a Space locally, manages cleanup, and returns the path."""
143
+ repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
144
+ try:
145
+ resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
146
+ if token_err: return None, token_err
147
+
148
+ repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
149
+ if err: return None, err
150
+ repo_id_for_error_logging = repo_id
151
+
152
+ repo_id_slug = repo_id.replace("/", "_") # Sanitize for directory name
153
+ _cleanup_old_clones(repo_id_slug_to_keep=repo_id_slug)
154
+ local_clone_dir = CLONE_BASE_DIR / repo_id_slug
155
+
156
+ if force_refresh and local_clone_dir.exists():
157
+ logger.info(f"Force refresh: Removing existing clone at {local_clone_dir}")
158
+ shutil.rmtree(local_clone_dir)
159
+
160
+ if not local_clone_dir.exists():
161
+ local_clone_dir.mkdir(parents=True, exist_ok=True) # Ensure parent exists before Repository tries to use it
162
+ logger.info(f"Cloning Space {repo_id} to {local_clone_dir}...")
163
+ try:
164
+ Repository(
165
+ local_dir=str(local_clone_dir),
166
+ clone_from=f"https://huggingface.co/spaces/{repo_id}",
167
+ repo_type="space", use_auth_token=resolved_api_token,
168
+ )
169
+ logger.info(f"Successfully cloned {repo_id} to {local_clone_dir}")
170
+ except Exception as clone_exc:
171
+ logger.exception(f"Error cloning {repo_id}:")
172
+ if local_clone_dir.exists(): shutil.rmtree(local_clone_dir) # Cleanup partial clone
173
+ return None, f"Error cloning Space '{repo_id}': {str(clone_exc)}"
174
+ else:
175
+ logger.info(f"Using existing clone for {repo_id} at {local_clone_dir}")
176
+ # For a true refresh of an existing clone, you'd `git pull` here.
177
+ # `force_refresh=True` handles this by re-cloning.
178
+
179
+ return str(local_clone_dir), None
180
+ except Exception as e:
181
+ logger.exception(f"Error in get_space_local_clone_path for {repo_id_for_error_logging}:")
182
+ return None, f"Error preparing local clone: {str(e)}"
183
+
184
+ def read_file_from_local_path(absolute_local_file_path_str: str):
185
+ """Reads content of a file given its absolute local path."""
186
+ try:
187
+ file_path = Path(absolute_local_file_path_str)
188
+ if not file_path.is_file():
189
+ return None, f"Error: Path is not a file or does not exist: {absolute_local_file_path_str}"
190
+ content = file_path.read_text(encoding="utf-8")
191
+ return content, None
192
+ except Exception as e:
193
+ logger.exception(f"Error reading local file {absolute_local_file_path_str}:")
194
+ return None, f"Error reading file content: {str(e)}"
195
+
196
+ # --- Core Functions: `create_space`, `update_space_file` (view_space_files can be deprecated) ---
197
  def create_space(ui_api_token_from_textbox, space_name_ui, owner_ui, sdk_ui, markdown_input):
198
+ # (Unchanged from previous correct version)
199
  repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
200
  try:
201
  resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
 
203
  repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
204
  if err: return err
205
  repo_id_for_error_logging = repo_id
206
+ space_info = parse_markdown(markdown_input)
207
  if not space_info["files"]: return "Error: No files found in markdown. Use '### File: path/to/file.ext'."
208
+ with tempfile.TemporaryDirectory() as temp_dir: # Temp dir for staging files from markdown
209
+ repo_staging_path = Path(temp_dir) / "repo_staging_content"
210
+ repo_staging_path.mkdir(exist_ok=True)
211
  for file_info in space_info["files"]:
212
  if not file_info.get("path"): continue
213
+ file_path_abs = repo_staging_path / file_info["path"]
214
  file_path_abs.parent.mkdir(parents=True, exist_ok=True)
215
+ with open(file_path_abs, "w", encoding="utf-8") as f: f.write(file_info["content"])
216
  try:
217
  create_repo(repo_id=repo_id, token=resolved_api_token, repo_type="space", space_sdk=sdk_ui, private=False)
218
  except Exception as e:
219
  err_str = str(e).lower()
220
  if not ("already exists" in err_str or "you already created this repo" in err_str or "exists" in err_str):
221
  return f"Error creating Space '{repo_id}': {str(e)}"
222
+ logger.info(f"Space {repo_id} already exists or creation confirmed, proceeding with upload.")
223
+ upload_folder(repo_id=repo_id, folder_path=str(repo_staging_path), path_in_repo=".", token=resolved_api_token, repo_type="space", commit_message=f"Initial Space setup of {repo_id} via Builder")
224
  return f"Successfully created/updated Space: [{repo_id}](https://huggingface.co/spaces/{repo_id})"
225
  except Exception as e:
226
  logger.exception(f"Error in create_space for {repo_id_for_error_logging}:")
227
  return f"Error during Space creation/update: {str(e)}"
228
 
229
+ # view_space_files can be removed or kept as a simple alternative list view
230
+ # def view_space_files(...)
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
  def update_space_file(ui_api_token_from_textbox, space_name_ui, owner_ui, file_path_in_repo, file_content, commit_message_ui):
233
+ # This function is now simpler as it expects file_path_in_repo to be the correct relative path.
234
+ # It still does its own clone for transactional integrity.
235
  repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
236
  try:
237
  resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
238
  if token_err: return token_err
239
+
240
  repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
241
  if err: return err
242
  repo_id_for_error_logging = repo_id
243
+
244
+ if not file_path_in_repo: return "Error: File Path to update cannot be empty."
245
+ # Sanitize file_path_in_repo: remove leading slashes, ensure forward slashes
246
+ file_path_in_repo = file_path_in_repo.lstrip('/').replace(os.sep, '/')
247
+
248
  commit_message_ui = commit_message_ui or f"Update {file_path_in_repo} via Space Builder"
249
+
250
+ with tempfile.TemporaryDirectory() as temp_dir_for_update: # Fresh temp dir for this update operation
251
+ repo_local_clone_path = Path(temp_dir_for_update) / "update_clone"
252
+
253
+ cloned_repo = Repository(
254
+ local_dir=str(repo_local_clone_path),
255
+ clone_from=f"https://huggingface.co/spaces/{repo_id}",
256
+ repo_type="space", use_auth_token=resolved_api_token,
257
+ git_user="Space Builder Bot", git_email="[email protected]" # Optional
258
+ )
259
+ logger.info(f"Cloned Space {repo_id} to {repo_local_clone_path} for update operation.")
260
+
261
+ # The file_path_in_repo is relative to the cloned_repo.local_dir
262
  full_local_file_path = Path(cloned_repo.local_dir) / file_path_in_repo
263
+ full_local_file_path.parent.mkdir(parents=True, exist_ok=True) # Create parent dirs if needed
264
+ with open(full_local_file_path, "w", encoding="utf-8") as f:
265
+ f.write(file_content)
266
+ logger.info(f"Wrote updated content to {full_local_file_path} for commit.")
267
+
268
  cloned_repo.push_to_hub(commit_message=commit_message_ui)
269
+ logger.info(f"Pushed update for {file_path_in_repo} to {repo_id}")
270
+
271
  return f"Successfully updated `{file_path_in_repo}` in Space [{repo_id}](https://huggingface.co/spaces/{repo_id})"
272
  except Exception as e:
273
+ logger.exception(f"Error in update_space_file for {repo_id_for_error_logging}, file {file_path_in_repo}:")
274
  return f"Error updating file for `{repo_id_for_error_logging}`: {str(e)}"