File size: 7,789 Bytes
3e1c4b9 2d7a058 3e1c4b9 2d7a058 3e1c4b9 2d7a058 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 2f92e9e 3e1c4b9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# deploy.py
import os
import base64
import webbrowser
import urllib.parse
import tempfile
import gradio as gr
from huggingface_hub import HfApi, duplicate_space
# ------------------------------------------------------------------
# Utilities for live‐preview sandbox in the Gradio UI
# ------------------------------------------------------------------
def send_to_sandbox(code: str) -> str:
"""Wrap HTML code in a sandboxed iframe via a data URI."""
wrapped = f"""
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head>
<body>{code}</body>
</html>
"""
b64 = base64.b64encode(wrapped.encode("utf-8")).decode("utf-8")
return (
f'<iframe src="data:text/html;base64,{b64}" '
'width="100%" height="920px" sandbox="allow-scripts allow-same-origin '
'allow-forms allow-popups allow-modals allow-presentation" allow="display-capture"></iframe>'
)
def demo_card_click(e: gr.EventData) -> str:
"""Return the description of a demo card when clicked."""
try:
idx = (
e._data.get("index")
or e._data.get("component", {}).get("index")
or e._data.get("target", {}).get("index")
)
except Exception:
idx = 0
from constants import DEMO_LIST
if not (0 <= idx < len(DEMO_LIST)):
idx = 0
return DEMO_LIST[idx]["description"]
# ------------------------------------------------------------------
# Functions for importing existing Hugging Face Spaces projects
# ------------------------------------------------------------------
def wrap_html_in_gradio_app(html_code: str) -> str:
"""Generate a minimal Gradio app.py that renders given HTML."""
safe = html_code.replace('"""', r'\"\"\"')
return (
"import gradio as gr\n\n"
"def show_html():\n"
f' return """{safe}"""\n\n'
"demo = gr.Interface(fn=show_html, inputs=None, outputs=gr.HTML())\n\n"
"if __name__ == '__main__':\n"
" demo.launch()\n"
)
def deploy_to_spaces(code: str) -> None:
"""Open a new tab to create a Hugging Face Space with a Gradio app."""
if not code.strip():
return
app_py = wrap_html_in_gradio_app(code)
params = urllib.parse.urlencode({
"name": "new-space",
"sdk": "gradio"
})
files = {
"files[0][path]": "app.py",
"files[0][content]": app_py
}
files_params = urllib.parse.urlencode(files)
url = f"https://huggingface.co/new-space?{params}&{files_params}"
webbrowser.open_new_tab(url)
def wrap_html_in_static_app(html_code: str) -> str:
"""Return raw HTML for a static Hugging Face Space."""
return html_code
def deploy_to_spaces_static(code: str) -> None:
"""Open a new tab to create a static HTML Space."""
if not code.strip():
return
html = wrap_html_in_static_app(code)
params = urllib.parse.urlencode({
"name": "new-space",
"sdk": "static"
})
files = {
"files[0][path]": "index.html",
"files[0][content]": html
}
files_params = urllib.parse.urlencode(files)
url = f"https://huggingface.co/new-space?{params}&{files_params}"
webbrowser.open_new_tab(url)
# ------------------------------------------------------------------
# Functions for loading and updating user Spaces
# ------------------------------------------------------------------
def check_hf_space_url(url: str):
"""Validate a HF Spaces URL and extract username/project."""
import re
pattern = re.compile(r'^(?:https?://)?(?:huggingface\.co|hf\.co)/spaces/([\w-]+)/([\w-]+)$', re.IGNORECASE)
m = pattern.match(url.strip())
if not m:
return False, None, None
return True, m.group(1), m.group(2)
def fetch_hf_space_content(username: str, project: str) -> str:
"""Download the main file of a Space and return its content."""
api = HfApi()
info = api.space_info(f"{username}/{project}")
sdk = info.sdk
# Choose main file based on SDK
if sdk == "static":
main_file = "index.html"
elif sdk == "gradio":
main_file = "app.py"
else:
# Fallback: try common filenames
for fname in ["app.py", "index.html", "streamlit_app.py", "main.py"]:
try:
_ = api.hf_hub_download(repo_id=f"{username}/{project}", filename=fname, repo_type="space")
main_file = fname
break
except:
continue
path = api.hf_hub_download(repo_id=f"{username}/{project}", filename=main_file, repo_type="space")
with open(path, "r", encoding="utf-8") as f:
return f.read()
def load_project_from_url(url: str):
"""Return (status_message, code_content) for importing a Space URL."""
valid, user, proj = check_hf_space_url(url)
if not valid:
return "Error: Invalid Hugging Face Space URL.", ""
try:
content = fetch_hf_space_content(user, proj)
return f"✅ Imported {user}/{proj}", content
except Exception as e:
return f"Error fetching project: {e}", ""
def deploy_to_user_space(code, space_name, sdk_choice, profile: gr.OAuthProfile | None = None, token: gr.OAuthToken | None = None):
"""Create or update a user's own HF Space with proper authentication."""
if not profile or not token or not token.token or token.token.startswith("hf_"):
return gr.update(value="Please log in with a valid Hugging Face write token.", visible=True)
api = HfApi(token=token.token)
is_update = "/" in space_name.strip()
if is_update:
repo_id = space_name.strip()
else:
repo_id = f"{profile.username}/{space_name.strip()}"
sdk_map = {
"Gradio (Python)": "gradio",
"Streamlit (Python)": "docker",
"Static (HTML)": "static",
"Transformers.js": "static"
}
sdk = sdk_map.get(sdk_choice, "gradio")
# Create or update repo
if not is_update and sdk != "docker":
api.create_repo(repo_id=repo_id, repo_type="space", space_sdk=sdk, exist_ok=True)
# Duplicate and upload logic for docker (Streamlit) or transformers.js
if sdk == "docker":
dup = duplicate_space(from_id="streamlit/streamlit-template-space", to_id=repo_id, token=token.token, exist_ok=True)
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as f:
f.write(code)
path = f.name
api.upload_file(path_or_fileobj=path, path_in_repo="src/streamlit_app.py", repo_id=repo_id, repo_type="space")
os.unlink(path)
elif sdk_choice == "Transformers.js":
dup = duplicate_space(from_id="static-templates/transformers.js", to_id=repo_id, token=token.token, exist_ok=True)
from utils import parse_transformers_js_output # ensure we have that helper
files = parse_transformers_js_output(code)
for name, content in files.items():
with tempfile.NamedTemporaryFile("w", suffix=f".{name.split('.')[-1]}", delete=False) as f:
f.write(content)
path = f.name
api.upload_file(path_or_fileobj=path, path_in_repo=name, repo_id=repo_id, repo_type="space")
os.unlink(path)
else:
# Static or Gradio: upload single file
filename = "index.html" if sdk == "static" else "app.py"
with tempfile.NamedTemporaryFile("w", suffix=f".{filename.split('.')[-1]}", delete=False) as f:
f.write(code)
path = f.name
api.upload_file(path_or_fileobj=path, path_in_repo=filename, repo_id=repo_id, repo_type="space")
os.unlink(path)
return gr.update(value=f"✅ Deployed to https://huggingface.co/spaces/{repo_id}", visible=True)
|