Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| repo_to_space_auto.py | |
| --------------------- | |
| ๊ณต๊ฐ Git ๋ ํฌ๋ฅผ Hugging Face Gradio Space๋ก ์๋ ๋ฐฐํฌํฉ๋๋ค. | |
| ํ์ ํ๊ฒฝ๋ณ์ | |
| ------------- | |
| BAPI_TOKEN : Brave Search API Key | |
| FRIENDLI_TOKEN : Friendli Dedicated Endpoint Token | |
| CLI ์ธ์ | |
| -------- | |
| --repo_url <URL> : ์๋ณธ Git ๋ ํฌ URL (ํ์*ยน) | |
| --hf_token <TOK> : ์ฐ๊ธฐ ๊ถํ Hugging Face Access Token (ํ์*ยน) | |
| --private : ๋น๊ณต๊ฐ Space๋ก ์์ฑ | |
| --hardware <tier> : ์) 't4-medium' GPU ์ธ์คํด์ค ์ง์ | |
| *ยน ์ธ์๋ฅผ ์๋ตํ๋ฉด ๋๋ช ํ๊ฒฝ๋ณ์(REPO_URL, HF_TOKEN)๊ฐ ๋์ ์ฌ์ฉ๋ฉ๋๋ค. | |
| """ | |
| import os, sys, json, argparse, subprocess, tempfile, textwrap, requests, shutil | |
| from pathlib import Path | |
| import git # GitPython | |
| from huggingface_hub import HfApi, login # HF Hub SDK | |
| # ---------- Brave Search ํฌํผ ---------- # | |
| def brave_search_repo(repo_url: str, count: int = 5) -> list[dict]: | |
| api_key = os.getenv("BAPI_TOKEN") | |
| if not api_key: | |
| raise RuntimeError("ํ๊ฒฝ๋ณ์ BAPI_TOKEN์ด ์ค์ ๋ผ ์์ง ์์ต๋๋ค.") | |
| headers = {"X-Subscription-Token": api_key, "Accept": "application/json"} | |
| params = {"q": f'site:github.com "{repo_url}"', "count": count, "search_lang": "en"} | |
| resp = requests.get("https://api.search.brave.com/res/v1/web/search", | |
| headers=headers, params=params, timeout=10) | |
| resp.raise_for_status() | |
| return resp.json().get("web", {}).get("results", []) | |
| # ---------- Friendli LLM ํฌํผ ---------- # | |
| def friendli_generate_scaffold(context: str) -> dict: | |
| token = os.getenv("FRIENDLI_TOKEN") | |
| if not token: | |
| raise RuntimeError("ํ๊ฒฝ๋ณ์ FRIENDLI_TOKEN์ด ์ค์ ๋ผ ์์ง ์์ต๋๋ค.") | |
| payload = { | |
| "model": "dep89a2fld32mcm", | |
| "messages": [ | |
| {"role": "system", | |
| "content": ("You are an expert Hugging Face Space architect. " | |
| "Given repository context, output JSON with keys " | |
| "`app_py`, `requirements_txt`, `need_docker` (bool), " | |
| "`dockerfile` (if needed) and `summary`.")}, | |
| {"role": "user", "content": context} | |
| ], | |
| "max_tokens": 16384, | |
| "top_p": 0.8, | |
| "stream": False | |
| } | |
| headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} | |
| r = requests.post("https://api.friendli.ai/dedicated/v1/chat/completions", | |
| json=payload, headers=headers, timeout=120) | |
| r.raise_for_status() | |
| return json.loads(r.json()["choices"][0]["message"]["content"]) | |
| # ---------- ๋ฉ์ธ ๋ฐฐํฌ ๋ก์ง ---------- # | |
| def deploy(repo_url: str, hf_token: str, private=False, hardware=None) -> str: | |
| """๋ ํฌ๋ฅผ Gradio Space๋ก ๋ฐฐํฌํ๊ณ Space URL ๋ฐํ.""" | |
| login(hf_token) | |
| api = HfApi(token=hf_token) | |
| user = api.whoami()["name"] | |
| space = f"{user}/{Path(repo_url).stem.lower().replace('.', '-')}" | |
| # create_repo ํธ์ถ (hardware ํ๋ผ๋ฏธํฐ ์ ๊ฑฐ) | |
| api.create_repo( | |
| repo_id=space, | |
| repo_type="space", | |
| space_sdk="gradio", | |
| private=private, | |
| exist_ok=True | |
| ) | |
| # hardware ์ค์ ์ด ํ์ํ ๊ฒฝ์ฐ ๋ณ๋๋ก update_repo_settings ํธ์ถ | |
| if hardware: | |
| try: | |
| # Space ์ค์ ์ ๋ฐ์ดํธ (hardware) | |
| api.request_space_hardware(repo_id=space, hardware=hardware) | |
| except Exception as e: | |
| print(f"โ ๏ธ Hardware ์ค์ ์คํจ (Space๋ ์์ฑ๋จ): {e}") | |
| with tempfile.TemporaryDirectory() as work: | |
| # 1) Brave ๋ฉํ๋ฐ์ดํฐ | |
| brave_meta = brave_search_repo(repo_url) | |
| # 2) ์๋ณธ ๋ ํฌ ํด๋ก | |
| src = Path(work) / "src" | |
| git.Repo.clone_from(repo_url, src) | |
| readme = "" | |
| if (src / "README.md").exists(): | |
| readme = (src / "README.md").read_text(encoding="utf-8", errors="ignore")[:4000] | |
| tree_out = subprocess.run(["bash", "-lc", f"tree -L 2 {src} 2>/dev/null || find {src} -maxdepth 2 -type f | head -n 40"], | |
| text=True, capture_output=True).stdout | |
| context = textwrap.dedent(f""" | |
| ## Brave meta | |
| {json.dumps(brave_meta, ensure_ascii=False, indent=2)} | |
| ## Repository tree (depth 2) | |
| {tree_out} | |
| ## README (first 4 kB) | |
| {readme} | |
| """) | |
| # 3) Friendli ์ค์บํด๋ ์์ฑ | |
| scaffold = friendli_generate_scaffold(context) | |
| # 4) Space ๋ ํฌ ํด๋ก & ํ์ผ ์ฐ๊ธฐ | |
| dst = Path(work) / "space" | |
| space_repo = git.Repo.clone_from( | |
| f"https://huggingface.co/spaces/{space}", | |
| dst, | |
| env={"HF_TOKEN": hf_token} | |
| ) | |
| (dst / "app.py").write_text(scaffold["app_py"], encoding="utf-8") | |
| (dst / "requirements.txt").write_text(scaffold["requirements_txt"], encoding="utf-8") | |
| if scaffold.get("need_docker"): | |
| (dst / "Dockerfile").write_text(scaffold["dockerfile"], encoding="utf-8") | |
| # README.md์ ์๋ณธ ๋ ํฌ ์ ๋ณด ์ถ๊ฐ | |
| readme_content = f"""--- | |
| title: {Path(repo_url).stem} | |
| emoji: ๐ | |
| colorFrom: blue | |
| colorTo: green | |
| sdk: gradio | |
| sdk_version: 4.44.1 | |
| app_file: app.py | |
| pinned: false | |
| --- | |
| # {Path(repo_url).stem} | |
| Automatically deployed from: {repo_url} | |
| ## Summary | |
| {scaffold["summary"]} | |
| --- | |
| *Created by HF Space Auto-Deployer* | |
| """ | |
| (dst / "README.md").write_text(readme_content, encoding="utf-8") | |
| # Git ์ปค๋ฐ ๋ฐ ํธ์ | |
| space_repo.index.add(["app.py", "requirements.txt", "README.md"]) | |
| if scaffold.get("need_docker"): | |
| space_repo.index.add(["Dockerfile"]) | |
| space_repo.index.commit("Initial auto-deploy") | |
| # Push with authentication | |
| origin = space_repo.remote("origin") | |
| origin.push(env={"HF_TOKEN": hf_token}) | |
| return f"https://huggingface.co/spaces/{space}" | |
| # ---------- CLI ์ํธ๋ฆฌ ---------- # | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Git ๋ ํฌ๋ฅผ Hugging Face Gradio Space๋ก ์๋ ๋ฐฐํฌ") | |
| parser.add_argument("--repo_url", default=os.getenv("REPO_URL"), | |
| help="์๋ณธ Git ๋ ํฌ์งํ ๋ฆฌ URL (or env REPO_URL)") | |
| parser.add_argument("--hf_token", default=os.getenv("HF_TOKEN"), | |
| help="์ฐ๊ธฐ ๊ถํ HF ํ ํฐ (or env HF_TOKEN)") | |
| parser.add_argument("--private", action="store_true", help="๋น๊ณต๊ฐ Space ์์ฑ") | |
| parser.add_argument("--hardware", default=None, help="์: 't4-medium'") | |
| args = parser.parse_args() | |
| if not args.repo_url or not args.hf_token: | |
| parser.error("--repo_url ๋ฐ --hf_token (๋๋ ๋๋ช ํ๊ฒฝ๋ณ์) ๋ ๋ค ํ์ํฉ๋๋ค.") | |
| try: | |
| url = deploy(args.repo_url, args.hf_token, args.private, args.hardware) | |
| print(f"โ ๋ฐฐํฌ ์ฑ๊ณต: {url}") | |
| except Exception as e: | |
| print(f"โ ๋ฐฐํฌ ์คํจ: {e}", file=sys.stderr) | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |