Github-Transfer / app.py
openfree's picture
Create app.py
eea45de verified
import gradio as gr
from huggingface_hub import HfApi, login
import uuid
from slugify import slugify
import os
import json
import subprocess
import tempfile
import requests
import shutil
import time
from pathlib import Path
from typing import Optional, Dict, List
def is_lfs_pointer_file(filepath):
try:
with open(filepath, 'rb') as f:
header = f.read(100)
return header.startswith(b'version https://git-lfs.github.com/spec/v1')
except:
return False
def remove_lfs_files(folder):
removed_files = []
for root, dirs, files in os.walk(folder):
if '.git' in root:
continue
for file in files:
filepath = os.path.join(root, file)
if is_lfs_pointer_file(filepath):
os.remove(filepath)
removed_files.append(filepath.replace(folder + os.sep, ''))
return removed_files
def analyze_repository(src_path: Path) -> Dict:
analysis = {
"has_requirements": False,
"has_readme": False,
"main_language": "python",
"key_files": [],
"dependencies": [],
"description": "",
"entry_points": [],
"model_files": [],
"config_files": []
}
req_file = src_path / "requirements.txt"
if req_file.exists():
analysis["has_requirements"] = True
try:
reqs = req_file.read_text(encoding="utf-8").strip().split("\n")
cleaned_deps = []
for r in reqs:
r = r.strip()
if r and not r.startswith("#"):
if "opencv-python==4.10.0" in r:
r = "opencv-python>=4.10.0.82"
elif "opencv-python==4.10" in r:
r = "opencv-python>=4.10.0.82"
if "==" in r and not r.startswith("git+"):
pkg_name = r.split("==")[0]
if pkg_name.lower() in ["torch", "tensorflow", "transformers", "numpy"]:
cleaned_deps.append(r)
else:
version = r.split("==")[1]
if version.count('.') == 1:
version = version + ".0"
cleaned_deps.append(f"{pkg_name}>={version}")
else:
cleaned_deps.append(r)
analysis["dependencies"] = cleaned_deps
except:
analysis["dependencies"] = []
for readme_name in ["README.md", "readme.md", "README.rst", "README.txt"]:
readme_file = src_path / readme_name
if readme_file.exists():
analysis["has_readme"] = True
try:
readme_content = readme_file.read_text(encoding="utf-8")
analysis["readme_content"] = readme_content[:5000]
lines = readme_content.split("\n")
for i, line in enumerate(lines[:10]):
if line.strip() and not line.startswith("#") and not line.startswith("!"):
analysis["description"] = line.strip()
break
except:
pass
py_files = list(src_path.glob("**/*.py"))
for py_file in py_files[:20]:
if "__pycache__" not in str(py_file) and ".git" not in str(py_file):
relative_path = py_file.relative_to(src_path)
if any(name in py_file.name for name in ["main.py", "app.py", "demo.py", "run.py", "server.py", "streamlit_app.py"]):
analysis["entry_points"].append(str(relative_path))
try:
content = py_file.read_text(encoding="utf-8")[:1000]
if "if __name__" in content and "main" in content:
analysis["entry_points"].append(str(relative_path))
if any(lib in content for lib in ["torch", "tensorflow", "transformers", "numpy", "pandas", "cv2", "PIL"]):
analysis["key_files"].append({
"path": str(relative_path),
"preview": content[:500]
})
except:
pass
model_extensions = [".pth", ".pt", ".ckpt", ".h5", ".pb", ".onnx", ".safetensors"]
for ext in model_extensions:
model_files = list(src_path.glob(f"**/*{ext}"))
for mf in model_files[:5]:
if ".git" not in str(mf):
analysis["model_files"].append(str(mf.relative_to(src_path)))
config_patterns = ["config.json", "config.yaml", "config.yml", "*.json", "*.yaml"]
for pattern in config_patterns:
config_files = list(src_path.glob(pattern))
for cf in config_files[:5]:
if ".git" not in str(cf):
analysis["config_files"].append(str(cf.relative_to(src_path)))
return analysis
def generate_gradio_app(repo_url: str, analysis: Dict) -> Dict:
context = f"""Repository URL: {repo_url}
Repository Analysis:
- Description: {analysis.get('description', 'N/A')}
- Main Dependencies: {', '.join(analysis['dependencies'][:10])}
- Entry Points: {', '.join(analysis['entry_points'][:5])}
- Model Files: {', '.join(analysis['model_files'][:3])}
- Config Files: {', '.join(analysis['config_files'][:3])}
Key Files Found:
"""
for kf in analysis.get('key_files', [])[:3]:
context += f"\n--- {kf['path']} ---\n{kf['preview']}\n"
if analysis.get('readme_content'):
context += f"\n--- README.md (excerpt) ---\n{analysis['readme_content'][:2000]}\n"
system_prompt = """You are an expert at creating Gradio apps from GitHub repositories.
Your task is to generate a complete, working Gradio interface that demonstrates the main functionality of the repository.
CRITICAL REQUIREMENTS:
1. The app.py must be FULLY FUNCTIONAL and runnable
2. DO NOT use 'from agent import' or any repository-specific imports that won't exist
3. Handle errors gracefully with clear user feedback
4. Include API key inputs when external services are required
5. Create intuitive UI components for the main features
6. Always use gradio>=5.35.0
Return ONLY valid JSON with these exact keys:
- app_py: Complete Gradio app code
- requirements_txt: All necessary dependencies including gradio>=5.35.0
- summary: Brief description of what the app does"""
fireworks_key = os.getenv("FIREWORKS_API_KEY")
if fireworks_key:
try:
url = "https://api.fireworks.ai/inference/v1/chat/completions"
payload = {
"model": "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
"max_tokens": 4096,
"top_p": 1,
"top_k": 40,
"presence_penalty": 0,
"frequency_penalty": 0,
"temperature": 0.6,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Create a fully functional Gradio app for this repository:\n\n{context[:8000]}"}
]
}
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {fireworks_key.strip()}"
}
r = requests.post(url, headers=headers, data=json.dumps(payload), timeout=30)
if r.status_code == 200:
response_text = r.json()["choices"][0]["message"]["content"]
print("โœ… Fireworks AI๋กœ ์•ฑ ์ƒ์„ฑ ์„ฑ๊ณต")
try:
if "```json" in response_text:
start = response_text.find("```json") + 7
end = response_text.find("```", start)
response_text = response_text[start:end].strip()
elif "```" in response_text:
start = response_text.find("```") + 3
end = response_text.find("```", start)
response_text = response_text[start:end].strip()
result = json.loads(response_text)
if not all(key in result for key in ["app_py", "requirements_txt", "summary"]):
raise ValueError("Missing required keys in response")
if "gradio" not in result.get("requirements_txt", "").lower():
result["requirements_txt"] = "gradio>=5.35.0\n" + result.get("requirements_txt", "")
return result
except (json.JSONDecodeError, ValueError) as e:
print(f"โš ๏ธ JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜: {e}")
return None
except Exception as e:
print(f"โš ๏ธ Fireworks AI API ์˜ค๋ฅ˜: {e}")
print("โ„น๏ธ AI API๊ฐ€ ์—†์–ด ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
return create_smart_template(repo_url, analysis)
def create_smart_template(repo_url: str, analysis: Dict) -> Dict:
repo_name = Path(repo_url.rstrip("/")).name
description = analysis.get("description", "A project deployed from GitHub") if analysis else "A project deployed from GitHub"
deps = " ".join(analysis.get("dependencies", [])) if analysis else ""
has_cv = any(lib in deps for lib in ["cv2", "PIL", "pillow", "opencv"])
has_nlp = any(lib in deps for lib in ["transformers", "nltk", "spacy"])
has_3d = any(lib in deps for lib in ["gaussian", "rasterizer", "plyfile", "trimesh"])
requirements = ["gradio>=5.35.0"]
if analysis and analysis.get("dependencies"):
filtered_deps = []
for dep in analysis["dependencies"][:15]:
if not dep.startswith("git+") and not dep.startswith("-e") and not dep.startswith("file:"):
if "==" in dep and dep.split("==")[0].lower() not in ["torch", "tensorflow", "numpy"]:
pkg_name = dep.split("==")[0]
version = dep.split("==")[1]
filtered_deps.append(f"{pkg_name}>={version}")
else:
filtered_deps.append(dep)
requirements.extend(filtered_deps)
if has_3d or "gaussian" in repo_name.lower():
app_code = f'''import gradio as gr
import os
def process_3d(input_file):
if input_file is None:
return "Please upload a 3D file or image"
info = """
## โš ๏ธ Build Requirements Notice
This project requires:
1. CUDA-enabled GPU
2. Custom C++/CUDA extensions compilation
Original repository: {repo_url}
"""
return info
with gr.Blocks(title="{repo_name}") as demo:
gr.Markdown(f"""
# {repo_name.replace("-", " ").title()}
{description}
This space was created from: [{repo_url}]({repo_url})
""")
with gr.Row():
with gr.Column():
input_file = gr.File(label="Upload 3D File or Image")
process_btn = gr.Button("Process", variant="primary")
with gr.Column():
output_info = gr.Markdown()
process_btn.click(
fn=process_3d,
inputs=input_file,
outputs=output_info
)
if __name__ == "__main__":
demo.launch()
'''
elif has_cv:
app_code = f'''import gradio as gr
from PIL import Image
import numpy as np
def process_image(image):
if image is None:
return None, "Please upload an image"
img_array = np.array(image)
processed = Image.fromarray(img_array)
info = f"Image shape: {{img_array.shape}}"
return processed, info
with gr.Blocks(title="{repo_name}") as demo:
gr.Markdown(f"""
# {repo_name.replace("-", " ").title()}
{description}
This space was created from: [{repo_url}]({repo_url})
""")
with gr.Row():
with gr.Column():
input_image = gr.Image(label="Input Image", type="pil")
process_btn = gr.Button("Process Image", variant="primary")
with gr.Column():
output_image = gr.Image(label="Output Image")
output_info = gr.Textbox(label="Information")
process_btn.click(
fn=process_image,
inputs=input_image,
outputs=[output_image, output_info]
)
if __name__ == "__main__":
demo.launch()
'''
elif has_nlp:
app_code = f'''import gradio as gr
def process_text(text, max_length=100):
if not text:
return "Please enter some text"
word_count = len(text.split())
char_count = len(text)
result = f"""
**Analysis Results:**
- Word count: {{word_count}}
- Character count: {{char_count}}
- Average word length: {{char_count/max(word_count, 1):.1f}}
"""
return result
with gr.Blocks(title="{repo_name}") as demo:
gr.Markdown(f"""
# {repo_name.replace("-", " ").title()}
{description}
This space was created from: [{repo_url}]({repo_url})
""")
with gr.Row():
with gr.Column():
input_text = gr.Textbox(
label="Input Text",
placeholder="Enter your text here...",
lines=5
)
max_length = gr.Slider(
minimum=10,
maximum=500,
value=100,
label="Max Length"
)
process_btn = gr.Button("Process Text", variant="primary")
with gr.Column():
output_text = gr.Markdown(label="Results")
process_btn.click(
fn=process_text,
inputs=[input_text, max_length],
outputs=output_text
)
if __name__ == "__main__":
demo.launch()
'''
else:
app_code = f'''import gradio as gr
def main_function(input_data):
if not input_data:
return "Please provide input"
result = f"Processed successfully! Input received: {{input_data}}"
return result
with gr.Blocks(title="{repo_name}") as demo:
gr.Markdown(f"""
# {repo_name.replace("-", " ").title()}
{description}
This space was created from: [{repo_url}]({repo_url})
""")
with gr.Row():
with gr.Column():
input_data = gr.Textbox(
label="Input",
placeholder="Enter your input here...",
lines=3
)
process_btn = gr.Button("Process", variant="primary")
with gr.Column():
output_data = gr.Textbox(label="Output")
process_btn.click(
fn=main_function,
inputs=input_data,
outputs=output_data
)
if __name__ == "__main__":
demo.launch()
'''
return {
"app_py": app_code,
"requirements_txt": "\n".join(requirements),
"summary": f"Smart template created for {repo_name}"
}
def clone(repo_git, repo_hf, sdk_type, skip_lfs, enable_smart_generation, user_hf_token=None, profile: gr.OAuthProfile = None, oauth_token: gr.OAuthToken = None):
# Use temporary directory instead of current working directory
temp_base_dir = tempfile.gettempdir()
folder_name = str(uuid.uuid4())
folder = os.path.join(temp_base_dir, folder_name)
# ํ† ํฐ ์šฐ์„ ์ˆœ์œ„: 1. ์‚ฌ์šฉ์ž ์ž…๋ ฅ ํ† ํฐ, 2. OAuth ํ† ํฐ, 3. ํ™˜๊ฒฝ๋ณ€์ˆ˜
hf_token = None
username = None
if user_hf_token and user_hf_token.strip():
# ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ž…๋ ฅํ•œ ํ† ํฐ ์‚ฌ์šฉ
hf_token = user_hf_token.strip()
yield "๐Ÿ”‘ Using provided HuggingFace token..."
elif oauth_token and oauth_token.token:
# OAuth ํ† ํฐ ์‹œ๋„ (ํ•˜์ง€๋งŒ Space ์ƒ์„ฑ ๊ถŒํ•œ์ด ์—†์„ ์ˆ˜ ์žˆ์Œ)
hf_token = oauth_token.token
username = profile.username if profile else None
yield f"๐Ÿ” Attempting with OAuth token for @{username}..."
yield "โš ๏ธ Note: OAuth may have limited permissions. If Space creation fails, please provide a personal access token."
else:
# ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฒดํฌ
hf_token = os.getenv("HF_TOKEN")
if not hf_token:
yield """โŒ Authentication Required!
Please choose one of these options:
1. **Recommended**: Enter your HuggingFace token in the field below
2. Login with the 'Sign in with Hugging Face' button (may have limited permissions)
3. Set HF_TOKEN in environment variables
To get a token:
1. Go to https://huggingface.co/settings/tokens
2. Click 'New token'
3. Select 'write' role
4. Copy and paste the token"""
return
try:
yield "๐Ÿ”„ Starting clone process..."
# HuggingFace API ์ธ์ฆ
try:
api = HfApi(token=hf_token)
user_info = api.whoami()
username = user_info["name"]
yield f"โœ… Authenticated as HuggingFace user: @{username}"
# ๊ถŒํ•œ ํ™•์ธ
if "write" not in user_info.get("auth", {}).get("accessToken", {}).get("role", ""):
yield "โš ๏ธ Warning: Token may not have write permissions. If Space creation fails, please use a token with 'write' role."
except Exception as e:
if "403" in str(e) or "401" in str(e):
yield f"""โŒ Authentication failed!
The token doesn't have sufficient permissions.
Please create a new token with 'write' access:
1. Go to https://huggingface.co/settings/tokens
2. Click 'New token'
3. Give it a name
4. Select 'write' role (important!)
5. Copy and paste the token
Error: {str(e)}"""
return
else:
yield f"โŒ HuggingFace authentication failed: {str(e)}"
return
# URL ์ •๊ทœํ™” ๋ฐ ๊ฒ€์ฆ
repo_git = repo_git.strip()
# .git ํ™•์žฅ์ž ์ถ”๊ฐ€ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ)
if not repo_git.endswith('.git'):
repo_git_with_git = repo_git + '.git'
else:
repo_git_with_git = repo_git
# HTTPS URL๋กœ ๋ณ€ํ™˜
if repo_git.startswith('git@github.com:'):
repo_git = repo_git.replace('git@github.com:', 'https://github.com/')
elif not repo_git.startswith('http'):
yield "โŒ Invalid repository URL. Please use HTTPS URL (e.g., https://github.com/username/repo)"
return
yield f"๐Ÿ“ฅ Cloning repository from {repo_git}..."
yield f" Using temporary directory: {folder}"
env = os.environ.copy()
env['GIT_LFS_SKIP_SMUDGE'] = '1'
# ๋จผ์ € .git ์—†์ด ์‹œ๋„
clone_cmd = ['git', 'clone', '--depth', '1', repo_git, folder]
result = subprocess.run(clone_cmd, env=env, capture_output=True, text=True)
# ์‹คํŒจํ•˜๋ฉด .git ์ถ”๊ฐ€ํ•˜์—ฌ ์žฌ์‹œ๋„
if result.returncode != 0:
if ".git" not in repo_git:
yield " Retrying with .git extension..."
clone_cmd = ['git', 'clone', '--depth', '1', repo_git_with_git, folder]
result = subprocess.run(clone_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = result.stderr
if "Repository not found" in error_msg or "fatal: repository" in error_msg:
yield f"โŒ Repository not found. Please check:"
yield f" 1. The repository URL is correct"
yield f" 2. The repository is public"
yield f" 3. The repository exists"
yield f" URL tried: {repo_git}"
else:
yield f"โŒ Git clone failed: {error_msg[:500]}"
if os.path.exists(folder):
shutil.rmtree(folder)
return
yield "โœ… Repository cloned successfully"
# Git ํด๋ก  ์žฌ์‹œ๋„ ๋กœ์ง ๊ฐœ์„ 
if not skip_lfs:
yield "๐Ÿ“ฆ Attempting to download LFS files..."
try:
# Git LFS ์„ค์น˜ ํ™•์ธ
lfs_check = subprocess.run(['git', 'lfs', 'version'], capture_output=True, text=True)
if lfs_check.returncode != 0:
yield "โš ๏ธ Git LFS not installed. Skipping LFS files..."
skip_lfs = True
else:
subprocess.run(['git', 'lfs', 'install'], cwd=folder, check=True)
lfs_result = subprocess.run(['git', 'lfs', 'pull'], cwd=folder, capture_output=True, text=True)
if lfs_result.returncode != 0:
yield f"โš ๏ธ Warning: LFS download failed - {lfs_result.stderr[:200]}"
skip_lfs = True
else:
yield "โœ… LFS files downloaded successfully"
except Exception as e:
yield f"โš ๏ธ LFS error: {str(e)}"
skip_lfs = True
if skip_lfs:
yield "๐Ÿงน Removing LFS pointer files..."
removed_files = remove_lfs_files(folder)
if removed_files:
yield f"๐Ÿ“ Removed {len(removed_files)} LFS pointer files"
if enable_smart_generation:
yield "๐Ÿ” Analyzing repository structure..."
folder_path = Path(folder)
analysis = analyze_repository(folder_path)
yield "๐Ÿค– Generating smart Gradio app..."
generated = generate_gradio_app(repo_git, analysis)
if generated and isinstance(generated, dict) and "app_py" in generated:
app_path = folder_path / "app.py"
app_path.write_text(generated["app_py"], encoding="utf-8")
yield "โœ… Smart app.py generated"
req_path = folder_path / "requirements.txt"
existing_reqs = []
if req_path.exists():
try:
existing_reqs = req_path.read_text(encoding="utf-8").strip().split("\n")
except:
existing_reqs = []
new_reqs = generated["requirements_txt"].strip().split("\n") if generated["requirements_txt"] else []
all_reqs = set()
git_reqs = []
torch_reqs = []
regular_reqs = []
for req in existing_reqs + new_reqs:
req = req.strip()
if not req or req.startswith("#"):
continue
if req.startswith("git+"):
git_reqs.append(req)
elif "torch" in req.lower() or "cuda" in req.lower():
torch_reqs.append(req)
else:
regular_reqs.append(req)
has_gradio = any("gradio" in req for req in regular_reqs)
if not has_gradio:
regular_reqs.append("gradio>=5.35.0")
final_reqs = []
if torch_reqs:
final_reqs.extend(sorted(set(torch_reqs)))
final_reqs.append("")
final_reqs.extend(sorted(set(regular_reqs)))
if git_reqs:
final_reqs.append("")
final_reqs.extend(sorted(set(git_reqs)))
req_content = "\n".join(final_reqs)
req_path.write_text(req_content, encoding="utf-8")
yield "โœ… Requirements.txt updated"
readme_path = folder_path / "README.md"
readme_content = f"""---
title: {repo_hf.replace("-", " ").title()}
emoji: ๐Ÿš€
colorFrom: blue
colorTo: green
sdk: {sdk_type}
sdk_version: "5.35.0"
app_file: app.py
pinned: false
---
# {repo_hf.replace("-", " ").title()}
{analysis.get('description', 'Deployed from GitHub repository')}
Deployed from: {repo_git}
"""
readme_path.write_text(readme_content, encoding="utf-8")
yield "โœ… README.md created/updated"
git_dir = os.path.join(folder, '.git')
if os.path.exists(git_dir):
shutil.rmtree(git_dir)
yield "๐Ÿงน Removed .git directory"
gitattributes_path = os.path.join(folder, '.gitattributes')
if os.path.exists(gitattributes_path):
with open(gitattributes_path, 'r') as f:
lines = f.readlines()
new_lines = []
for line in lines:
if 'filter=lfs' not in line:
new_lines.append(line)
if new_lines:
with open(gitattributes_path, 'w') as f:
f.writelines(new_lines)
else:
os.remove(gitattributes_path)
yield "๐Ÿ—๏ธ Creating Hugging Face Space..."
repo_id = f"{username}/{slugify(repo_hf)}"
space_created = False
for attempt in range(3):
try:
yield f" Creating Space: {repo_id} (attempt {attempt + 1}/3)"
try:
existing_space = api.space_info(repo_id=repo_id, token=hf_token)
yield f" โ„น๏ธ Space already exists: {existing_space.id}"
space_created = True
break
except:
pass
create_result = api.create_repo(
repo_id=repo_id,
repo_type="space",
space_sdk=sdk_type,
exist_ok=True,
private=False,
token=hf_token
)
time.sleep(3)
space_info = api.space_info(repo_id=repo_id, token=hf_token)
yield f" โœ… Space created successfully: {space_info.id}"
space_created = True
break
except Exception as e:
error_msg = str(e)
if "429" in error_msg or "Too Many Requests" in error_msg:
yield f"โŒ Rate Limit Error - Try again in 17-24 hours"
if os.path.exists(folder):
shutil.rmtree(folder)
raise Exception(f"Rate limit reached.")
yield f" โš ๏ธ Attempt {attempt + 1} failed: {error_msg[:100]}..."
if attempt < 2:
yield " Retrying in 5 seconds..."
time.sleep(5)
else:
yield f" โŒ Failed to create space after 3 attempts"
if os.path.exists(folder):
shutil.rmtree(folder)
raise Exception(f"Could not create space: {error_msg}")
if not space_created:
if os.path.exists(folder):
shutil.rmtree(folder)
raise Exception("Failed to create space")
folder_size = sum(os.path.getsize(os.path.join(dirpath, filename))
for dirpath, dirnames, filenames in os.walk(folder)
for filename in filenames) / (1024 * 1024)
yield f"๐Ÿ“Š Folder size: {folder_size:.2f} MB"
file_count = sum(len(files) for _, _, files in os.walk(folder))
yield f"๐Ÿ“ Total files to upload: {file_count}"
upload_success = False
max_retries = 3
for attempt in range(max_retries):
try:
if attempt > 0:
yield f"๐Ÿ“ค Upload attempt {attempt + 1}/{max_retries}..."
time.sleep(5)
if folder_size > 500:
yield "๐Ÿ“ค Uploading large folder to Hugging Face..."
api.upload_large_folder(
folder_path=folder,
repo_id=repo_id,
repo_type="space",
token=hf_token,
commit_message="Deploy from GitHub repository",
ignore_patterns=["*.pyc", "__pycache__", ".git*", ".DS_Store", "*.egg-info"]
)
else:
yield "๐Ÿ“ค Uploading to Hugging Face..."
api.upload_folder(
folder_path=folder,
repo_id=repo_id,
repo_type="space",
token=hf_token,
commit_message="Deploy from GitHub repository",
ignore_patterns=["*.pyc", "__pycache__", ".git*", ".DS_Store", "*.egg-info"]
)
upload_success = True
yield "โœ… Upload completed successfully"
break
except Exception as upload_error:
error_msg = str(upload_error)
if "404" in error_msg and attempt < max_retries - 1:
yield f" โš ๏ธ Upload failed (404). Retrying..."
time.sleep(10)
try:
space_info = api.space_info(repo_id=repo_id, token=hf_token)
yield f" โœ… Space confirmed to exist"
except:
yield " ๐Ÿ”„ Attempting to recreate space..."
try:
api.create_repo(
repo_id=repo_id,
repo_type="space",
space_sdk=sdk_type,
exist_ok=True,
private=False,
token=hf_token
)
yield " โœ… Space recreated"
except Exception as recreate_error:
yield f" โŒ Could not recreate space: {str(recreate_error)}"
elif "LFS pointer" in error_msg:
yield "โŒ Upload failed due to remaining LFS pointer files"
if os.path.exists(folder):
shutil.rmtree(folder)
raise upload_error
elif attempt == max_retries - 1:
yield f"โŒ Upload failed after {max_retries} attempts"
if os.path.exists(folder):
shutil.rmtree(folder)
raise upload_error
else:
yield f" โš ๏ธ Upload failed: {error_msg[:100]}..."
if not upload_success:
if os.path.exists(folder):
shutil.rmtree(folder)
raise Exception("Upload failed after all retries")
# Clean up temporary folder
if os.path.exists(folder):
shutil.rmtree(folder)
space_url = f"https://huggingface.co/spaces/{repo_id}"
yield f"""
โœ… **Successfully created Space!**
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
๐ŸŽ‰ **Your Space is Ready!**
๐Ÿ”— **Space URL**: {space_url}
๐Ÿ“‹ **Click here to open**: [{repo_id}]({space_url})
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
๐Ÿ“Š **Deployment Details:**
โ€ข Space ID: `{repo_id}`
โ€ข Source: {repo_git}
โ€ข SDK: {sdk_type}
โ€ข Smart Generation: {'Enabled' if enable_smart_generation else 'Disabled'}
โ€ข LFS Files: {'Skipped' if skip_lfs else 'Included'}
โฑ๏ธ **Next Steps:**
1. Click the link above to open your Space
2. Wait 1-2 minutes for initial build
3. Check "App" tab to see your deployed application
4. Check "Logs" tab if you encounter any issues
๐Ÿ’ก **Tips:**
โ€ข Building may take 2-5 minutes depending on dependencies
โ€ข If build fails, check requirements.txt in "Files" tab
โ€ข You can edit files directly in the Space interface
"""
if skip_lfs:
yield "\nโš ๏ธ LFS files were removed."
if enable_smart_generation:
yield "\n๐Ÿค– AI-generated Gradio interface was created"
except subprocess.CalledProcessError as e:
if os.path.exists(folder):
shutil.rmtree(folder)
yield f"โŒ Git error: {str(e)}"
except Exception as e:
if os.path.exists(folder):
shutil.rmtree(folder)
yield f"โŒ Error: {str(e)}"
def space_to_github(hf_username, hf_space_name, github_username, github_repo_name, github_token):
"""Clone HuggingFace Space and push to GitHub"""
tmp_dir = None
try:
# HF Space URL ์ƒ์„ฑ
hf_repo_url = f"https://huggingface.co/spaces/{hf_username}/{hf_space_name}.git"
# ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ ๋ฐ ํด๋ก 
tmp_dir = tempfile.mkdtemp()
yield f"๐Ÿ“ฅ Cloning HF Space: {hf_username}/{hf_space_name}..."
yield f" Using temporary directory: {tmp_dir}"
# Git ํด๋ก 
env = os.environ.copy()
env['GIT_LFS_SKIP_SMUDGE'] = '1' # LFS ํŒŒ์ผ ์Šคํ‚ต
clone_cmd = ['git', 'clone', hf_repo_url, tmp_dir]
result = subprocess.run(clone_cmd, env=env, capture_output=True, text=True, cwd=tempfile.gettempdir())
if result.returncode != 0:
raise Exception(f"Clone failed: {result.stderr}")
yield "โœ… HF Space cloned successfully"
# GitHub ์ธ์ฆ ๋ฐ ๋ ˆํฌ ์ƒ์„ฑ
yield "๐Ÿ” Authenticating with GitHub..."
# GitHub API ํ˜ธ์ถœ๋กœ ๋ ˆํฌ ์ƒ์„ฑ
headers = {
"Authorization": f"token {github_token}",
"Accept": "application/vnd.github.v3+json"
}
# ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
user_response = requests.get("https://api.github.com/user", headers=headers)
if user_response.status_code != 200:
raise Exception("GitHub authentication failed. Please check your token.")
actual_github_username = user_response.json()["login"]
# ์ž…๋ ฅ๋œ username๊ณผ ์‹ค์ œ username์ด ๋‹ค๋ฅธ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ
if github_username.lower() != actual_github_username.lower():
yield f"โš ๏ธ Note: Using authenticated user '{actual_github_username}' instead of '{github_username}'"
github_username = actual_github_username
yield f"โœ… Authenticated as GitHub user: {github_username}"
# ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์ƒ์„ฑ
yield f"๐Ÿ“ฆ Creating GitHub repository: {github_repo_name}..."
create_data = {
"name": github_repo_name,
"private": False,
"auto_init": False,
"description": f"Exported from HuggingFace Space: {hf_username}/{hf_space_name}"
}
create_response = requests.post(
"https://api.github.com/user/repos",
headers=headers,
json=create_data
)
if create_response.status_code == 201:
repo_info = create_response.json()
github_url = repo_info['html_url']
yield f"โœ… GitHub repository created: {github_url}"
elif create_response.status_code == 422:
# ๋ ˆํฌ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•จ
github_url = f"https://github.com/{github_username}/{github_repo_name}"
yield f"โ„น๏ธ Repository already exists: {github_url}"
else:
error_msg = create_response.json().get('message', 'Unknown error')
raise Exception(f"Failed to create repository: {error_msg}")
# Git remote ๋ณ€๊ฒฝ ๋ฐ ํ‘ธ์‹œ
yield "๐Ÿ“ค Pushing to GitHub..."
# git ๋ช…๋ น์–ด๋กœ remote ๋ณ€๊ฒฝ ๋ฐ ํ‘ธ์‹œ
os.chdir(tmp_dir)
# ๊ธฐ์กด origin ์ œ๊ฑฐ
subprocess.run(['git', 'remote', 'remove', 'origin'], capture_output=True)
# GitHub remote ์ถ”๊ฐ€
github_remote_url = f"https://{github_username}:{github_token}@github.com/{github_username}/{github_repo_name}.git"
subprocess.run(['git', 'remote', 'add', 'origin', github_remote_url], check=True)
# main ๋ธŒ๋žœ์น˜๋กœ ํ‘ธ์‹œ
push_result = subprocess.run(
['git', 'push', '-u', 'origin', 'HEAD:main', '--force'],
capture_output=True,
text=True
)
if push_result.returncode != 0:
# master ๋ธŒ๋žœ์น˜๋กœ ์žฌ์‹œ๋„
push_result = subprocess.run(
['git', 'push', '-u', 'origin', 'HEAD:master', '--force'],
capture_output=True,
text=True
)
if push_result.returncode != 0:
raise Exception(f"Push failed: {push_result.stderr}")
yield "โœ… Successfully pushed to GitHub!"
# ์ตœ์ข… ๊ฒฐ๊ณผ ์ถœ๋ ฅ - ํด๋ฆญ ๊ฐ€๋Šฅํ•œ ๋งํฌ
final_url = f"https://github.com/{github_username}/{github_repo_name}"
yield f"""
๐ŸŽ‰ **Export Complete!**
๐Ÿ“ฆ **GitHub Repository**: [{final_url}]({final_url})
๐Ÿ“‹ **Details:**
- Source: HuggingFace Space `{hf_username}/{hf_space_name}`
- Destination: GitHub `{github_username}/{github_repo_name}`
๐Ÿ”ง **Next Steps:**
```bash
git clone {final_url}.git
cd {github_repo_name}
```
"""
except Exception as e:
yield f"โŒ Error: {str(e)}"
finally:
# ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ •๋ฆฌ
if tmp_dir and os.path.exists(tmp_dir):
os.chdir(tempfile.gettempdir()) # ์•ˆ์ „ํ•œ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™
try:
shutil.rmtree(tmp_dir)
except:
pass # ์ •๋ฆฌ ์‹คํŒจํ•ด๋„ ๊ณ„์† ์ง„ํ–‰
# CSS styling (keeping the same)
css = """
/* Modern Professional UI */
.container {
max-width: 1200px !important;
margin: auto;
padding: 20px;
}
/* Gradient backgrounds */
.main-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 2rem;
border-radius: 15px;
margin-bottom: 2rem;
color: white;
text-align: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
/* Tab styling */
.tabs {
box-shadow: 0 4px 6px rgba(0,0,0,0.07);
border-radius: 12px;
overflow: hidden;
}
/* Card-like sections */
.input-section {
background: white;
padding: 25px;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
margin-bottom: 20px;
border: 1px solid rgba(0,0,0,0.05);
}
/* Output box styling */
.output-box {
min-height: 400px !important;
max-height: 600px !important;
overflow-y: auto !important;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace !important;
font-size: 13px !important;
line-height: 1.6 !important;
background: linear-gradient(to bottom, #1e1e1e, #2d2d30) !important;
color: #d4d4d4 !important;
padding: 20px !important;
border-radius: 10px !important;
border: 1px solid rgba(255,255,255,0.1) !important;
}
/* Custom button styling */
.primary-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
border: none !important;
padding: 12px 30px !important;
font-size: 16px !important;
font-weight: 600 !important;
border-radius: 8px !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
}
.primary-btn:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;
}
/* Input field styling */
input[type="text"], input[type="password"], textarea {
border: 2px solid #e2e8f0 !important;
border-radius: 8px !important;
padding: 10px 15px !important;
font-size: 14px !important;
transition: all 0.3s ease !important;
background: #f8fafc !important;
}
input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
border-color: #667eea !important;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
background: white !important;
}
/* Radio button group styling */
.radio-group {
background: #f8fafc;
padding: 15px;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
/* Checkbox styling */
input[type="checkbox"] {
width: 20px !important;
height: 20px !important;
cursor: pointer !important;
}
/* Label styling */
label {
font-weight: 600 !important;
color: #334155 !important;
margin-bottom: 8px !important;
display: block !important;
font-size: 14px !important;
}
/* Info text styling */
.info-text {
color: #64748b !important;
font-size: 13px !important;
margin-top: 5px !important;
}
/* Scrollbar styling */
.output-box::-webkit-scrollbar {
width: 10px;
}
.output-box::-webkit-scrollbar-track {
background: #2d2d30;
border-radius: 5px;
}
.output-box::-webkit-scrollbar-thumb {
background: #555;
border-radius: 5px;
}
.output-box::-webkit-scrollbar-thumb:hover {
background: #666;
}
/* Animation */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.input-section {
animation: fadeIn 0.5s ease;
}
/* Responsive design */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.main-header {
padding: 1.5rem;
}
.input-section {
padding: 15px;
}
}
"""
# Gradio interface (remains the same structure)
with gr.Blocks(css=css, theme=gr.themes.Soft(
primary_hue="purple",
secondary_hue="purple",
neutral_hue="slate"
)) as demo:
gr.HTML("""
<div class="main-header">
<h1 style="font-size: 2.5rem; margin: 0; font-weight: 700;">
๐Ÿ”„ Repository Converter Pro
</h1>
<p style="font-size: 1.1rem; margin-top: 10px; opacity: 0.95;">
Seamlessly transfer repositories between GitHub and HuggingFace
</p>
</div>
""")
# OAuth ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ์ƒ๋‹จ์— ๋ฐฐ์น˜
with gr.Row():
with gr.Column(scale=4):
gr.LoginButton()
with gr.Column(scale=8):
login_status = gr.HTML("""
<div style="padding: 10px; background: #fef2f2; border: 1px solid #fecaca;
border-radius: 8px; margin: 5px 0;">
<span style="color: #dc2626;">โš ๏ธ Please login with HuggingFace to continue</span>
</div>
""")
with gr.Tabs(elem_classes="tabs") as main_tabs:
with gr.Tab("๐Ÿš€ GitHub โ†’ HF Space", elem_id="tab1"):
# ์ธ์ฆ ์˜ต์…˜ ์„น์…˜ ์ถ”๊ฐ€
with gr.Group(elem_classes="input-section"):
gr.Markdown("""
### ๐Ÿ” Authentication Options
**Option 1**: Enter your HuggingFace token (Recommended)
**Option 2**: Use OAuth login (may have limited permissions)
""")
user_hf_token = gr.Textbox(
label="HuggingFace Access Token (Optional but Recommended)",
type="password",
placeholder="hf_xxxxxxxxxxxxxxxxxxxxx",
info="Get token from: https://huggingface.co/settings/tokens (needs 'write' role)"
)
with gr.Row():
with gr.Column(scale=5):
with gr.Group(elem_classes="input-section"):
gr.Markdown("### ๐Ÿ“ฆ Source Repository")
repo_git = gr.Textbox(
label="GitHub Repository URL",
placeholder="https://github.com/username/repository",
info="Enter the full GitHub repository URL"
)
gr.Markdown("### ๐ŸŽฏ Destination Settings")
repo_hf = gr.Textbox(
label="HuggingFace Space Name",
placeholder="my-awesome-space",
info="Choose a unique name for your Space"
)
sdk_choices = gr.Radio(
["gradio", "streamlit", "docker", "static"],
label="Space SDK Type",
value="gradio",
elem_classes="radio-group"
)
gr.Markdown("### โš™๏ธ Advanced Options")
with gr.Row():
skip_lfs = gr.Checkbox(
label="Skip Large Files (LFS)",
value=True,
info="Recommended for faster deployment"
)
enable_smart_generation = gr.Checkbox(
label="๐Ÿค– AI-Powered App Generation",
value=False,
info="Generate app.py automatically (requires FIREWORKS_API_KEY)"
)
btn_to_hf = gr.Button("๐Ÿš€ Deploy to HuggingFace",
variant="primary",
elem_classes="primary-btn",
size="lg")
with gr.Column(scale=7):
with gr.Group(elem_classes="input-section"):
gr.Markdown("### ๐Ÿ“Š Deployment Progress")
output_to_hf = gr.Textbox(
label="",
lines=20,
elem_classes="output-box",
interactive=False,
show_copy_button=True
)
btn_to_hf.click(
fn=clone,
inputs=[repo_git, repo_hf, sdk_choices, skip_lfs, enable_smart_generation, user_hf_token],
outputs=output_to_hf
)
with gr.Tab("๐Ÿ“ค HF Space โ†’ GitHub", elem_id="tab2"):
with gr.Row():
with gr.Column(scale=5):
with gr.Group(elem_classes="input-section"):
gr.Markdown("### ๐ŸŽฏ Source Space")
hf_username = gr.Textbox(
label="HuggingFace Username",
placeholder="your-hf-username",
info="Your HuggingFace account name"
)
hf_space_name = gr.Textbox(
label="Space Name",
placeholder="your-space-name",
info="Name of the Space to export"
)
gr.Markdown("### ๐Ÿ“ฆ GitHub Destination")
github_username = gr.Textbox(
label="GitHub Username",
placeholder="your-github-username",
info="Your GitHub account name"
)
github_repo_name = gr.Textbox(
label="Repository Name",
placeholder="new-repo-name",
info="Name for the new GitHub repository"
)
gr.Markdown("### ๐Ÿ” Authentication")
github_token = gr.Textbox(
label="GitHub Personal Access Token",
type="password",
placeholder="ghp_xxxxxxxxxxxxxxxxxxxx",
info="Generate at: github.com/settings/tokens (needs 'repo' scope)"
)
btn_to_github = gr.Button("๐Ÿ“ค Export to GitHub",
variant="primary",
elem_classes="primary-btn",
size="lg")
with gr.Column(scale=7):
with gr.Group(elem_classes="input-section"):
gr.Markdown("### ๐Ÿ“Š Export Progress")
output_to_github = gr.Textbox(
label="",
lines=20,
elem_classes="output-box",
interactive=False,
show_copy_button=True
)
btn_to_github.click(
fn=space_to_github,
inputs=[hf_username, hf_space_name, github_username, github_repo_name, github_token],
outputs=output_to_github
)
if __name__ == "__main__":
# OAuth ํ™˜๊ฒฝ ์„ค์ • ์ฒดํฌ
if os.getenv("SPACE_ID"):
print("Running on HuggingFace Spaces")
print("OAuth callback URL: https://huggingface.co/spaces/" + os.getenv("SPACE_ID"))
demo.launch()