import gradio as gr import os import re import tempfile import shutil import git from huggingface_hub import ( create_repo, upload_folder, list_repo_files, delete_file, Repository, whoami, ) from huggingface_hub.utils import HfHubHTTPError import logging from pathlib import Path # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Function to parse markdown input def parse_markdown(markdown_input): """Parse markdown input to extract space details and file structure.""" space_info = {"repo_name": "", "owner": "", "sdk": "gradio", "files": []} current_file = None file_content = [] in_file_content = False lines = markdown_input.strip().split("\n") for line in lines: line = line.strip() # Extract space name if line.startswith("# Space:"): space_info["repo_name"] = line.replace("# Space:", "").strip() if "/" in space_info["repo_name"]: space_info["owner"], space_info["repo_name"] = space_info["repo_name"].split("/", 1) # Detect file structure section elif line.startswith("## File Structure"): continue # Detect file in structure elif line.startswith("📄") or line.startswith("📁"): if current_file and file_content: space_info["files"].append({"path": current_file, "content": "\n".join(file_content)}) file_content = [] current_file = line[2:].strip() in_file_content = False # Detect file content section elif line.startswith("### File:"): if current_file and file_content: space_info["files"].append({"path": current_file, "content": "\n".join(file_content)}) file_content = [] current_file = line.replace("### File:", "").strip() in_file_content = True # Handle file content elif in_file_content and line.startswith("```"): if file_content: space_info["files"].append({"path": current_file, "content": "\n".join(file_content)}) file_content = [] in_file_content = False else: in_file_content = True elif in_file_content: file_content.append(line) # Append the last file if current_file and file_content: space_info["files"].append({"path": current_file, "content": "\n".join(file_content)}) return space_info # Function to create and populate a Space def create_space(api_token, space_name, owner, sdk, markdown_input): """Create a Hugging Face Space and populate it with files from markdown input.""" try: # Authenticate with Hugging Face Hub if not api_token: return "Error: Please provide a valid Hugging Face API token." # Get user info user_info = whoami(token=api_token) if not owner: owner = user_info["name"] repo_id = f"{owner}/{space_name}" # Create temporary directory with tempfile.TemporaryDirectory() as temp_dir: repo_path = os.path.join(temp_dir, space_name) os.makedirs(repo_path, exist_ok=True) # Parse markdown input space_info = parse_markdown(markdown_input) if space_info["repo_name"] != f"{owner}/{space_name}": return "Error: Space name in markdown does not match provided owner/space_name." # Write files to temporary directory for file_info in space_info["files"]: file_path = os.path.join(repo_path, file_info["path"]) os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, "w", encoding="utf-8") as f: f.write(file_info["content"]) logger.info(f"Wrote file: {file_path}") # Create repository on Hugging Face try: create_repo( repo_id=repo_id, token=api_token, repo_type="space", space_sdk=sdk, private=False, ) logger.info(f"Created Space: {repo_id}") except HfHubHTTPError as e: if "already exists" in str(e): logger.info(f"Space {repo_id} already exists, proceeding to update.") else: return f"Error creating Space: {str(e)}" # Initialize Git repository repo = git.Repo.init(repo_path) repo.git.add(all=True) repo.index.commit("Initial commit from Gradio app") # Push to Hugging Face Space upload_folder( repo_id=repo_id, folder_path=repo_path, path_in_repo=".", token=api_token, commit_message="Initial Space setup", ) return f"Successfully created Space: https://huggingface.co/spaces/{repo_id}" except Exception as e: logger.error(f"Error: {str(e)}") return f"Error: {str(e)}" # Function to view Space files def view_space_files(api_token, space_name, owner): """List files in a Hugging Face Space.""" try: if not api_token: return "Error: Please provide a valid Hugging Face API token." repo_id = f"{owner}/{space_name}" if owner else space_name files = list_repo_files(repo_id=repo_id, token=api_token, repo_type="space") return "\n".join(files) or "No files found in the Space." except Exception as e: return f"Error: {str(e)}" # Function to update a Space file def update_space_file(api_token, space_name, owner, file_path, file_content, commit_message): """Update a file in a Hugging Face Space with a commit.""" try: if not api_token: return "Error: Please provide a valid Hugging Face API token." repo_id = f"{owner}/{space_name}" if owner else space_name # Create temporary directory for cloning with tempfile.TemporaryDirectory() as temp_dir: repo_path = os.path.join(temp_dir, space_name) repo = Repository( local_dir=repo_path, clone_from=f"https://huggingface.co/spaces/{repo_id}", repo_type="space", use_auth_token=api_token, ) # Write updated file full_path = os.path.join(repo_path, file_path) os.makedirs(os.path.dirname(full_path), exist_ok=True) with open(full_path, "w", encoding="utf-8") as f: f.write(file_content) # Commit and push changes repo.git_add(file_path) repo.git_commit(commit_message or f"Update {file_path}") repo.git_push() return f"Successfully updated {file_path} in Space: https://huggingface.co/spaces/{repo_id}" except Exception as e: return f"Error: {str(e)}" # Gradio interface def main(): with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown("# Hugging Face Space Builder") gr.Markdown("Create, view, and update Hugging Face Spaces with a custom file structure.") # Authentication with gr.Group(): gr.Markdown("## Authentication") api_token = gr.Textbox( label="Hugging Face API Token", type="password", placeholder="Enter your Hugging Face API token", ) # Create Space with gr.Group(): gr.Markdown("## Create a New Space") space_name = gr.Textbox(label="Space Name", placeholder="my-space") owner = gr.Textbox( label="Owner (optional)", placeholder="Leave blank for current user" ) sdk = gr.Dropdown( label="SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio" ) markdown_input = gr.Textbox( label="Markdown File Structure", placeholder="Paste markdown with file structure and contents", lines=10, ) create_btn = gr.Button("Create Space") create_output = gr.Textbox(label="Result") # View Space Files with gr.Group(): gr.Markdown("## View Space Files") view_space_name = gr.Textbox(label="Space Name", placeholder="my-space") view_owner = gr.Textbox( label="Owner (optional)", placeholder="Leave blank for current user" ) view_btn = gr.Button("List Files") view_output = gr.Textbox(label="Files in Space") # Update Space File with gr.Group(): gr.Markdown("## Update Space File") update_space_name = gr.Textbox(label="Space Name", placeholder="my-space") update_owner = gr.Textbox( label="Owner (optional)", placeholder="Leave blank for current user" ) file_path = gr.Textbox(label="File Path", placeholder="path/to/file.py") file_content = gr.Textbox( label="File Content", placeholder="Enter new file content", lines=10 ) commit_message = gr.Textbox( label="Commit Message", placeholder="Update file content" ) update_btn = gr.Button("Update File") update_output = gr.Textbox(label="Result") # Event handlers create_btn.click( fn=create_space, inputs=[api_token, space_name, owner, sdk, markdown_input], outputs=create_output, ) view_btn.click( fn=view_space_files, inputs=[api_token, view_space_name, view_owner], outputs=view_output, ) update_btn.click( fn=update_space_file, inputs=[ api_token, update_space_name, update_owner, file_path, file_content, commit_message, ], outputs=update_output, ) return demo if __name__ == "__main__": demo = main() demo.launch()