broadfield-dev commited on
Commit
4fad6e3
·
verified ·
1 Parent(s): fab8f61

Update build_logic.py

Browse files
Files changed (1) hide show
  1. build_logic.py +177 -138
build_logic.py CHANGED
@@ -4,6 +4,7 @@ import tempfile
4
  import shutil
5
  import logging
6
  from pathlib import Path
 
7
 
8
  from huggingface_hub import (
9
  create_repo,
@@ -13,6 +14,10 @@ from huggingface_hub import (
13
  hf_hub_download,
14
  delete_file as hf_delete_file,
15
  HfApi,
 
 
 
 
16
  )
17
  from huggingface_hub.hf_api import CommitOperationDelete, CommitOperationAdd, CommitOperation
18
  from huggingface_hub.utils import HfHubHTTPError
@@ -25,12 +30,12 @@ logger = logging.getLogger(__name__)
25
 
26
  def _get_api_token(ui_token_from_textbox=None):
27
  env_token = os.getenv('HF_TOKEN')
28
- if env_token:
29
- logger.debug("Using HF_TOKEN from environment variable.")
30
- return env_token, None
31
  if ui_token_from_textbox:
32
  logger.debug("Using HF_TOKEN from UI textbox.")
33
  return ui_token_from_textbox.strip(), None
 
 
 
34
  logger.warning("Hugging Face API token not provided in UI or HF_TOKEN env var.")
35
  return None, "Error: Hugging Face API token not provided in UI or HF_TOKEN env var."
36
 
@@ -294,7 +299,6 @@ def get_space_file_content(ui_api_token_from_textbox, space_name_ui, owner_ui, f
294
  logger.exception(f"Error fetching file content for {file_path_in_repo} from {repo_id_for_error_logging or 'unknown repo'}:")
295
  return None, f"Error fetching file content: {str(e)}"
296
 
297
- # Renamed and modified to only handle file changes (CREATE, UPDATE, DELETE)
298
  def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui, file_changeset):
299
  repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
300
  repo_id = None
@@ -334,7 +338,7 @@ def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui
334
  logger.warning(f"Could not stage .gitattributes: {e}")
335
 
336
 
337
- for change in file_changeset: # Iterate only through file changes
338
  if change['type'] == 'UPDATE_FILE' or change['type'] == 'CREATE_FILE':
339
  file_path_in_repo = change['path'].lstrip('/').replace(os.sep, '/')
340
  if not file_path_in_repo:
@@ -402,7 +406,7 @@ def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui
402
  repo_type="space",
403
  commit_message=commit_message_upload,
404
  allow_patterns=["*"],
405
- timeout=120 # Increased timeout for uploads
406
  )
407
  status_messages.append(f"File Uploads/Updates: Successfully uploaded/updated {len(paths_to_upload)} files.")
408
  logger.info("Upload/Update commit successful.")
@@ -437,6 +441,136 @@ def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui
437
  logger.info(f"Finished applying staged file changes. Final status: {final_status}")
438
  return final_status
439
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  def delete_space_file(ui_api_token_from_textbox, space_name_ui, owner_ui, file_path_in_repo, commit_message_ui=None):
441
  repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
442
  repo_id = None
@@ -631,6 +765,7 @@ def build_logic_delete_space(hf_api_key, owner, space_name):
631
  logger.exception(f"Error deleting space {repo_id}:")
632
  return f"Error deleting space `{repo_id}`: {e}"
633
 
 
634
  def build_logic_create_pull_request(hf_api_key, source_repo_id, target_repo_id, title, body=""):
635
  logger.info(f"Attempting to create PR from '{source_repo_id}' to '{target_repo_id}'. Title: '{title}'")
636
  try:
@@ -641,30 +776,11 @@ def build_logic_create_pull_request(hf_api_key, source_repo_id, target_repo_id,
641
 
642
  api = HfApi(token=token)
643
 
644
- # Assuming the source repo is a Space and the target can be any repo type
645
- # Assuming PR is from source_repo_id main branch to target_repo_id main branch
646
- # This might need refinement based on actual use cases (e.g., PR from space branch to model repo main)
647
- # For simplicity now, assume PR from source_repo_id (space) 'main' to target_repo_id 'main'
648
- # Need to check if the token has write access to target_repo_id.
649
-
650
- # A PR is created on the *target* repository
651
- # We need to check if the token can write to the target.
652
- # There isn't a direct "check write permission" API, so we rely on create_pull_request errors.
653
-
654
- pr_url = api.create_pull_request(
655
- repo_id=target_repo_id, # PR is created ON the target repo
656
  title=title,
657
  description=body,
658
- # Source branch is the branch *in the source repo* (the space)
659
- # Target branch is the branch *in the target repo*
660
- # By default, create_pull_request assumes 'main' for both,
661
- # but source is interpreted as 'main' in the *source_repo_id*
662
- # and target as 'main' in the *repo_id* parameter (target_repo_id).
663
- # The API docs mention `repo_id` is the target and `base_repo` is the source for cross-repo PRs.
664
- # Let's use `repo_id` as target and `base_repo` as source (the space).
665
- base_repo=source_repo_id, # The Space repo is the source
666
- base="main", # Source branch in the Space (source_repo_id)
667
- head="main", # Target branch in the target_repo_id
668
  token=token,
669
  timeout=30
670
  )
@@ -675,13 +791,11 @@ def build_logic_create_pull_request(hf_api_key, source_repo_id, target_repo_id,
675
  except HfHubHTTPError as e_http:
676
  logger.error(f"HTTP error creating PR from {source_repo_id} to {target_repo_id}: {e_http}")
677
  status_code = e_http.response.status_code if e_http.response else 'N/A'
678
- # Check for common errors like permission denied or target not found
679
  if status_code in (401, 403):
680
  return f"PR Error ({status_code}): Access denied or authentication required to create PR on '{target_repo_id}'. Check token permissions."
681
  if status_code == 404:
682
  return f"PR Error ({status_code}): Target repository '{target_repo_id}' not found."
683
- # Add more specific error checks if needed (e.g., PR already exists, invalid branches)
684
- if e_http.response and 'already exists' in e_http.response.text:
685
  return f"PR Error: Pull Request already exists."
686
  return f"PR HTTP Error ({status_code}): {e_http.response.text if e_http.response else str(e_http)}"
687
 
@@ -698,47 +812,17 @@ def build_logic_add_comment(hf_api_key, repo_id, comment_text):
698
  logger.error(f"Token error adding comment: {err or 'Token not found'}")
699
  return f"Error getting token: {err or 'Token not found.'}"
700
 
701
- api = HfApi(token=token)
702
- # The create_discussion method can be used for comments on the repo itself
703
- api.create_discussion(
704
  repo_id=repo_id,
705
- title=f"Comment from AI Space Commander [{time.strftime('%Y-%m-%d %H:%M')}]", # Title required, use timestamp
706
- description=comment_text,
707
  token=token,
708
  timeout=20
709
  )
710
- # Note: This creates a *discussion* rather than a direct comment stream entry.
711
- # A more direct comment API might exist or could be simulated via other means,
712
- # but create_discussion is the closest public API method for adding arbitrary text.
713
- # Let's clarify this limitation or use a different method if available (e.g., adding a commit comment).
714
- # HfApi does not seem to expose a simple "add comment to repo page" API.
715
- # create_discussion is the most reasonable public function.
716
- # Re-reading the API docs... maybe there's no public API for the little comment boxes?
717
- # The Discussions API is the closest. Let's use that, but inform the user it's a discussion.
718
- # Or, maybe the AI should be suggesting adding a comment via a COMMIT message?
719
- # The prompt asks for "send comment", which implies a direct comment stream.
720
- # This might require an internal API or simulating via another method.
721
- # Let's stick to the most direct public API for now: Discussions.
722
- # Or, better, tell the AI this action is not supported via public API or needs clarification.
723
- # Let's return an error message for now, as `create_discussion` isn't what the user likely means by "add comment".
724
- # Okay, let's re-read the original codebase. There *was* no comment/like function. The user is *requesting* them.
725
- # The simplest interpretation of "add comment" is adding to the discussions tab or commit comments.
726
- # Let's add `create_discussion` for now as it's a public API and the closest fit.
727
- # We should clarify the AI prompt/action description to mention it creates a *discussion*.
728
- # Reverted: create_discussion requires a *title*. The prompt just says "comment text".
729
- # This reinforces that the requested action might not map directly to a public API.
730
- # Let's implement it using `create_discussion` and add a default title.
731
-
732
- api.create_discussion(
733
- repo_id=repo_id,
734
- title=f"AI Space Commander Comment: {comment_text[:50]}{'...' if len(comment_text) > 50 else ''} [{time.strftime('%Y-%m-%d %H:%M')}]", # Use part of comment + timestamp as title
735
- description=comment_text, # Full comment in description
736
- token=token,
737
- timeout=20
738
- )
739
 
740
- logger.info(f"Successfully added comment (as discussion) to {repo_id}.")
741
- return f"Successfully added comment (as a discussion) to `{repo_id}`."
 
742
 
743
  except HfHubHTTPError as e_http:
744
  logger.error(f"HTTP error adding comment to {repo_id}: {e_http}")
@@ -762,39 +846,29 @@ def build_logic_like_space(hf_api_key, repo_id):
762
  logger.error(f"Token error liking space: {err or 'Token not found'}")
763
  return f"Error getting token: {err or 'Token not found.'}"
764
 
765
- api = HfApi(token=token)
766
- # HfApi does not have a direct 'like' method.
767
- # This action might also require an internal API or not be publicly exposed.
768
- # Let's return an error for now, or note it's not directly supported.
769
- # Given the request "add... like", it implies it *should* be possible.
770
- # A manual approach might involve an authenticated API call not wrapped by hf_hub.
771
- # For now, let's return an informative error.
772
- # Reverted: There *is* an internal endpoint used by the UI. It's not in the public `HfApi`.
773
- # Implementing this reliably without internal API knowledge is difficult and fragile.
774
- # Let's return an error message stating it's not supported via the public API.
775
-
776
- return f"Like Error: Liking spaces is not directly supported via the public Hugging Face Hub API used by this tool."
777
-
778
- # Example (likely unstable/unsupported) attempt using requests if we knew the endpoint:
779
- # like_url = f"https://huggingface.co/api/v1/repos/{repo_id}/like"
780
- # headers = {"Authorization": f"Bearer {token}"}
781
- # response = requests.post(like_url, headers=headers, timeout=10)
782
- # response.raise_for_status()
783
- # logger.info(f"Successfully liked space: {repo_id}")
784
- # return f"Successfully liked space: `{repo_id}`."
785
- # except requests.exceptions.HTTPError as e:
786
- # # Handle 409 Conflict (already liked) or other errors
787
- # status_code = e.response.status_code if e.response else 'N/A'
788
- # if status_code == 409: return f"Like Error: Space '{repo_id}' already liked."
789
- # return f"Like HTTP Error ({status_code}): {e.response.text if e.response else str(e)}"
790
- # except Exception as e:
791
- # logger.exception(f"Error liking space {repo_id}:")
792
- # return f"Like Error: {e}"
793
-
794
-
795
- except Exception as e: # Catch potential errors even in the error path above
796
- logger.exception(f"Unexpected error in build_logic_like_space for {repo_id}:")
797
- return f"Like Error: An unexpected error occurred: {e}"
798
 
799
 
800
  def duplicate_space(hf_api_key, source_repo_id, target_repo_id, private: bool = False):
@@ -821,7 +895,7 @@ def duplicate_space(hf_api_key, source_repo_id, target_repo_id, private: bool =
821
  logger.error(f"Could not determine target owner from token: {e}")
822
  return f"Error: Target repository ID '{target_repo_id}' is missing owner, and owner could not be determined from token ({e}). Use '<owner>/<space_name>' format or set the Owner field."
823
 
824
- api = HfApi(token=token) # Use HfApi object to call duplicate_repo
825
  api.duplicate_repo(
826
  from_repo=source_repo_id,
827
  to_repo=target_repo_id,
@@ -861,23 +935,7 @@ def list_user_spaces(hf_api_key, owner=None):
861
  return None, f"Error auto-detecting owner for listing: {e}. Please specify Owner field."
862
 
863
  api = HfApi(token=token)
864
- #spaces = hf_list_repos(effective_owner,token=token)
865
-
866
- user_repos = {}
867
-
868
- # List models for the user
869
- models = api.list_models(author=owner)
870
- user_repos['models'] = [model.id for model in models]
871
-
872
- # List datasets for the user
873
- datasets = api.list_datasets(author=owner)
874
- user_repos['datasets'] = [dataset.id for dataset in datasets]
875
-
876
- # List Spaces for the user
877
- spaces = api.list_spaces(author=owner)
878
- user_repos['spaces'] = [space.id for space in spaces]
879
-
880
-
881
  space_ids = [f"{r.author}/{r.id}" for r in spaces]
882
 
883
  logger.info(f"Successfully listed {len(space_ids)} spaces for {effective_owner}.")
@@ -893,23 +951,4 @@ def list_user_spaces(hf_api_key, owner=None):
893
  return None, f"HTTP Error ({status_code}) listing spaces for '{owner or 'authenticated user'}': {e_http.response.text if e_http.response else str(e_http)}"
894
  except Exception as e:
895
  logger.exception(f"Error listing spaces for {owner or 'authenticated user'}:")
896
- return None, f"Error listing spaces: {e}"
897
-
898
-
899
- def list_user_spaces(username):
900
- api = HfApi()
901
- user_repos = {}
902
-
903
- # List models for the user
904
- models = api.list_models(author=username)
905
- user_repos['models'] = [model.id for model in models]
906
-
907
- # List datasets for the user
908
- datasets = api.list_datasets(author=username)
909
- user_repos['datasets'] = [dataset.id for dataset in datasets]
910
-
911
- # List Spaces for the user
912
- spaces = api.list_spaces(author=username)
913
- user_repos['spaces'] = [space.id for space in spaces]
914
-
915
- return user_repos
 
4
  import shutil
5
  import logging
6
  from pathlib import Path
7
+ import time
8
 
9
  from huggingface_hub import (
10
  create_repo,
 
14
  hf_hub_download,
15
  delete_file as hf_delete_file,
16
  HfApi,
17
+ list_repos as hf_list_repos,
18
+ create_pull_request as hf_create_pull_request,
19
+ add_space_comment as hf_add_space_comment, # Public API for adding comments to Space discussions
20
+ like as hf_like # Public API for liking repos
21
  )
22
  from huggingface_hub.hf_api import CommitOperationDelete, CommitOperationAdd, CommitOperation
23
  from huggingface_hub.utils import HfHubHTTPError
 
30
 
31
  def _get_api_token(ui_token_from_textbox=None):
32
  env_token = os.getenv('HF_TOKEN')
 
 
 
33
  if ui_token_from_textbox:
34
  logger.debug("Using HF_TOKEN from UI textbox.")
35
  return ui_token_from_textbox.strip(), None
36
+ if env_token:
37
+ logger.debug("Using HF_TOKEN from environment variable.")
38
+ return env_token, None
39
  logger.warning("Hugging Face API token not provided in UI or HF_TOKEN env var.")
40
  return None, "Error: Hugging Face API token not provided in UI or HF_TOKEN env var."
41
 
 
299
  logger.exception(f"Error fetching file content for {file_path_in_repo} from {repo_id_for_error_logging or 'unknown repo'}:")
300
  return None, f"Error fetching file content: {str(e)}"
301
 
 
302
  def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui, file_changeset):
303
  repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
304
  repo_id = None
 
338
  logger.warning(f"Could not stage .gitattributes: {e}")
339
 
340
 
341
+ for change in file_changeset:
342
  if change['type'] == 'UPDATE_FILE' or change['type'] == 'CREATE_FILE':
343
  file_path_in_repo = change['path'].lstrip('/').replace(os.sep, '/')
344
  if not file_path_in_repo:
 
406
  repo_type="space",
407
  commit_message=commit_message_upload,
408
  allow_patterns=["*"],
409
+ timeout=120
410
  )
411
  status_messages.append(f"File Uploads/Updates: Successfully uploaded/updated {len(paths_to_upload)} files.")
412
  logger.info("Upload/Update commit successful.")
 
441
  logger.info(f"Finished applying staged file changes. Final status: {final_status}")
442
  return final_status
443
 
444
+ def build_logic_create_space(ui_api_token_from_textbox, space_name_ui, owner_ui, sdk_ui, markdown_input, private):
445
+ repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
446
+ logger.info(f"Attempting to create space: {repo_id_for_error_logging}")
447
+
448
+ if not space_name_ui: return "Error: Space Name cannot be empty."
449
+ if "/" in space_name_ui: return "Error: Space Name should not contain '/'."
450
+
451
+ resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
452
+ if token_err: return f"API Token Error: {token_err}"
453
+
454
+ final_owner = owner_ui
455
+ if not final_owner:
456
+ try:
457
+ user_info = whoami(token=resolved_api_token)
458
+ final_owner = user_info.get('name')
459
+ if not final_owner: raise Exception("Could not find user name from token.")
460
+ except Exception as e:
461
+ return f"Error auto-detecting owner: {str(e)}. Specify Owner field."
462
+
463
+ if not final_owner: return "Error: Owner could not be determined."
464
+
465
+ repo_id = f"{final_owner}/{space_name_ui}"
466
+ temp_dir = None
467
+
468
+ try:
469
+ api = HfApi(token=resolved_api_token)
470
+
471
+ # Create the repository
472
+ api.create_repo(repo_id=repo_id, repo_type="space", space_sdk=sdk_ui, private=private, exist_ok=False)
473
+ logger.info(f"Successfully created empty space: {repo_id}")
474
+
475
+ # Stage files from markdown for upload if markdown is provided
476
+ if markdown_input:
477
+ parsed_md = parse_markdown(markdown_input)
478
+ files_to_upload = parsed_md.get("files", [])
479
+ if files_to_upload:
480
+ logger.info(f"Staging {len(files_to_upload)} files for upload after creation.")
481
+ temp_dir = tempfile.TemporaryDirectory()
482
+ repo_staging_path = Path(temp_dir.name) / "initial_content"
483
+ repo_staging_path.mkdir(exist_ok=True)
484
+ paths_to_upload = {}
485
+ status_messages = []
486
+
487
+ # Add .gitattributes
488
+ gitattributes_path_local = repo_staging_path / ".gitattributes"
489
+ try:
490
+ with open(gitattributes_path_local, "w", encoding="utf-8") as f:
491
+ f.write("* text=auto eol=lf\n")
492
+ paths_to_upload[str(gitattributes_path_local)] = ".gitattributes"
493
+ except Exception as e:
494
+ status_messages.append(f"Warning: Could not stage .gitattributes file: {e}")
495
+ logger.warning(f"Could not stage .gitattributes: {e}")
496
+
497
+
498
+ for file_info in files_to_upload:
499
+ file_path_in_repo = file_info['path'].lstrip('/').replace(os.sep, '/')
500
+ content_to_write = file_info.get('content', '')
501
+
502
+ if not file_path_in_repo:
503
+ status_messages.append(f"Skipping file creation: empty path.")
504
+ continue
505
+
506
+ if content_to_write.startswith("[Binary file") or content_to_write.startswith("[Error loading content:") or content_to_write.startswith("[Binary or Skipped file]"):
507
+ status_messages.append(f"Skipping file '{file_path_in_repo}': Content is a binary/error placeholder.")
508
+ logger.warning(f"Skipping file '{file_path_in_repo}': Content is binary/error placeholder.")
509
+ continue
510
+
511
+ file_path_local = repo_staging_path / file_path_in_repo
512
+ file_path_local.parent.mkdir(parents=True, exist_ok=True)
513
+ try:
514
+ with open(file_path_local, "w", encoding="utf-8") as f:
515
+ f.write(content_to_write)
516
+ paths_to_upload[str(file_path_local)] = file_path_in_repo
517
+ except Exception as file_write_error:
518
+ status_messages.append(f"Error staging file {file_path_in_repo}: {file_write_error}")
519
+
520
+
521
+ if paths_to_upload:
522
+ try:
523
+ commit_message_upload = "Initial files from AI Space Builder"
524
+ upload_folder(
525
+ repo_id=repo_id,
526
+ folder_path=str(repo_staging_path),
527
+ path_in_repo=".",
528
+ token=resolved_api_token,
529
+ repo_type="space",
530
+ commit_message=commit_message_upload,
531
+ allow_patterns=["*"],
532
+ timeout=120
533
+ )
534
+ status_messages.append(f"Successfully uploaded initial {len(paths_to_upload)} files.")
535
+ logger.info("Initial upload successful.")
536
+ except HfHubHTTPError as e_http:
537
+ status_messages.append(f"Initial Upload 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.")
538
+ logger.error(f"HTTP error during initial upload for {repo_id}: {e_http}")
539
+ except Exception as e_upload:
540
+ status_messages.append(f"Initial Upload Error: {str(e_upload)}. Check logs.")
541
+ logger.exception(f"Error during initial upload for {repo_id}:")
542
+ else:
543
+ status_messages = ["No files parsed from markdown for initial upload."]
544
+ else:
545
+ status_messages = ["No markdown provided for initial files."]
546
+
547
+
548
+ final_status = "Successfully created space"
549
+ if status_messages:
550
+ final_status += " | " + " | ".join(status_messages)
551
+
552
+ return final_status
553
+
554
+ except HfHubHTTPError as e_http:
555
+ logger.error(f"HTTP error creating space {repo_id_for_error_logging or 'unknown repo'}: {e_http}")
556
+ status_code = e_http.response.status_code if e_http.response else 'N/A'
557
+ if status_code == 409: # Conflict - repo already exists
558
+ return f"Create Space Error ({status_code}): Space '{repo_id_for_error_logging or 'unknown'}' already exists. Use 'Build / Update' instead."
559
+ if status_code in (401, 403):
560
+ return f"Create Space Error ({status_code}): Access denied or authentication required for '{repo_id_for_error_logging or 'unknown'}'. Check token permissions."
561
+ return f"Create Space HTTP Error ({status_code}): {e_http.response.text if e_http.response else str(e_http)}"
562
+
563
+ except Exception as e:
564
+ logger.exception(f"Error creating space {repo_id_for_error_logging or 'unknown repo'}:")
565
+ return f"Create Space Error: {str(e)}"
566
+ finally:
567
+ if temp_dir:
568
+ try:
569
+ temp_dir.cleanup()
570
+ except Exception as e:
571
+ logger.error(f"Error cleaning up temp dir: {e}")
572
+
573
+
574
  def delete_space_file(ui_api_token_from_textbox, space_name_ui, owner_ui, file_path_in_repo, commit_message_ui=None):
575
  repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
576
  repo_id = None
 
765
  logger.exception(f"Error deleting space {repo_id}:")
766
  return f"Error deleting space `{repo_id}`: {e}"
767
 
768
+
769
  def build_logic_create_pull_request(hf_api_key, source_repo_id, target_repo_id, title, body=""):
770
  logger.info(f"Attempting to create PR from '{source_repo_id}' to '{target_repo_id}'. Title: '{title}'")
771
  try:
 
776
 
777
  api = HfApi(token=token)
778
 
779
+ pr_url = hf_create_pull_request(
780
+ repo_id=target_repo_id,
 
 
 
 
 
 
 
 
 
 
781
  title=title,
782
  description=body,
783
+ base_repo=source_repo_id,
 
 
 
 
 
 
 
 
 
784
  token=token,
785
  timeout=30
786
  )
 
791
  except HfHubHTTPError as e_http:
792
  logger.error(f"HTTP error creating PR from {source_repo_id} to {target_repo_id}: {e_http}")
793
  status_code = e_http.response.status_code if e_http.response else 'N/A'
 
794
  if status_code in (401, 403):
795
  return f"PR Error ({status_code}): Access denied or authentication required to create PR on '{target_repo_id}'. Check token permissions."
796
  if status_code == 404:
797
  return f"PR Error ({status_code}): Target repository '{target_repo_id}' not found."
798
+ if e_http.response and isinstance(e_http.response.text, str) and 'already exists' in e_http.response.text:
 
799
  return f"PR Error: Pull Request already exists."
800
  return f"PR HTTP Error ({status_code}): {e_http.response.text if e_http.response else str(e_http)}"
801
 
 
812
  logger.error(f"Token error adding comment: {err or 'Token not found'}")
813
  return f"Error getting token: {err or 'Token not found.'}"
814
 
815
+ # Use the new public API method add_space_comment
816
+ hf_add_space_comment(
 
817
  repo_id=repo_id,
818
+ comment=comment_text,
 
819
  token=token,
820
  timeout=20
821
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822
 
823
+ logger.info(f"Successfully added comment to {repo_id}.")
824
+ # Note: hf_add_space_comment doesn't return the comment URL directly
825
+ return f"Successfully added comment to `{repo_id}`."
826
 
827
  except HfHubHTTPError as e_http:
828
  logger.error(f"HTTP error adding comment to {repo_id}: {e_http}")
 
846
  logger.error(f"Token error liking space: {err or 'Token not found'}")
847
  return f"Error getting token: {err or 'Token not found.'}"
848
 
849
+ # Use the new public API method like
850
+ hf_like(
851
+ repo_id=repo_id,
852
+ token=token,
853
+ timeout=10
854
+ )
855
+
856
+ logger.info(f"Successfully liked space: {repo_id}")
857
+ return f"Successfully liked space: `{repo_id}`."
858
+
859
+ except HfHubHTTPError as e_http:
860
+ logger.error(f"HTTP error liking space {repo_id}: {e_http}")
861
+ status_code = e_http.response.status_code if e_http.response else 'N/A'
862
+ if status_code == 409:
863
+ return f"Like Error ({status_code}): Space '{repo_id}' already liked."
864
+ if status_code in (401, 403):
865
+ return f"Like Error ({status_code}): Access denied or authentication required to like '{repo_id}'. Check token permissions."
866
+ if status_code == 404:
867
+ return f"Like Error ({status_code}): Space '{repo_id}' not found."
868
+ return f"Like HTTP Error ({status_code}): {e_http.response.text if e_http.response else str(e_http)}"
869
+ except Exception as e:
870
+ logger.exception(f"Error liking space {repo_id}:")
871
+ return f"Like Error: {e}"
 
 
 
 
 
 
 
 
 
 
872
 
873
 
874
  def duplicate_space(hf_api_key, source_repo_id, target_repo_id, private: bool = False):
 
895
  logger.error(f"Could not determine target owner from token: {e}")
896
  return f"Error: Target repository ID '{target_repo_id}' is missing owner, and owner could not be determined from token ({e}). Use '<owner>/<space_name>' format or set the Owner field."
897
 
898
+ api = HfApi(token=token)
899
  api.duplicate_repo(
900
  from_repo=source_repo_id,
901
  to_repo=target_repo_id,
 
935
  return None, f"Error auto-detecting owner for listing: {e}. Please specify Owner field."
936
 
937
  api = HfApi(token=token)
938
+ spaces = api.list_repos(author=effective_owner, type="space", token=token, timeout=20)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
  space_ids = [f"{r.author}/{r.id}" for r in spaces]
940
 
941
  logger.info(f"Successfully listed {len(space_ids)} spaces for {effective_owner}.")
 
951
  return None, f"HTTP Error ({status_code}) listing spaces for '{owner or 'authenticated user'}': {e_http.response.text if e_http.response else str(e_http)}"
952
  except Exception as e:
953
  logger.exception(f"Error listing spaces for {owner or 'authenticated user'}:")
954
+ return None, f"Error listing spaces: {e}"