openfree commited on
Commit
26bad09
·
verified ·
1 Parent(s): e3c85e7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +151 -0
app.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ repo_to_space_auto.py
4
+ ---------------------
5
+ 자동으로 Git 레포를 Hugging Face Gradio Space로 배포.
6
+ 필요 환경변수:
7
+ BAPI_TOKEN : Brave Search API Key
8
+ FRIENDLI_TOKEN : Friendli Dedicated Endpoint Token
9
+ CLI 인자:
10
+ --repo_url <URL> : 원본 GitHub/GitLab 등 공개 레포 URL
11
+ --hf_token <TOK> : 쓰기 권한 Hugging Face Access Token
12
+ 옵션:
13
+ --private : 비공개 Space 생성
14
+ --hardware <tier> : 예) 't4-medium' GPU 인스턴스 지정
15
+ """
16
+ import os, sys, json, argparse, subprocess, tempfile, textwrap, requests, shutil
17
+ from pathlib import Path
18
+ import git # GitPython
19
+ from huggingface_hub import HfApi, login
20
+
21
+ # ---------- Brave Search 헬퍼 ---------- #
22
+ def brave_search_repo(repo_url: str, count: int = 5) -> list[dict]:
23
+ api_key = os.getenv("BAPI_TOKEN")
24
+ if not api_key:
25
+ raise RuntimeError("환경변수 BAPI_TOKEN이 설정돼 있지 않습니다.")
26
+ headers = {"X-Subscription-Token": api_key, "Accept": "application/json"}
27
+ params = {"q": f'site:github.com "{repo_url}"', "count": count, "search_lang": "en"}
28
+ resp = requests.get(
29
+ "https://api.search.brave.com/res/v1/web/search",
30
+ headers=headers, params=params, timeout=10
31
+ )
32
+ resp.raise_for_status()
33
+ return resp.json().get("web", {}).get("results", [])
34
+
35
+ # ---------- Friendli LLM 헬퍼 ---------- #
36
+ def friendli_generate_scaffold(context: str) -> dict:
37
+ token = os.getenv("FRIENDLI_TOKEN")
38
+ if not token:
39
+ raise RuntimeError("환경변수 FRIENDLI_TOKEN이 설정돼 있지 않습니다.")
40
+ payload = {
41
+ "model": "dep89a2fld32mcm",
42
+ "messages": [
43
+ {
44
+ "role": "system",
45
+ "content": (
46
+ "You are an expert Hugging Face Space architect. "
47
+ "Given repository context, output JSON with keys "
48
+ "`app_py`, `requirements_txt`, `need_docker` (bool), "
49
+ "`dockerfile` (if needed) and `summary`."
50
+ ),
51
+ },
52
+ {"role": "user", "content": context},
53
+ ],
54
+ "max_tokens": 16384,
55
+ "top_p": 0.8,
56
+ "stream": False,
57
+ }
58
+ headers = {
59
+ "Authorization": f"Bearer {token}",
60
+ "Content-Type": "application/json",
61
+ }
62
+ r = requests.post(
63
+ "https://api.friendli.ai/dedicated/v1/chat/completions",
64
+ json=payload, headers=headers, timeout=120
65
+ )
66
+ r.raise_for_status()
67
+ return json.loads(r.json()["choices"][0]["message"]["content"])
68
+
69
+ # ---------- 메인 배포 로직 ---------- #
70
+ def deploy(repo_url: str, hf_token: str, private=False, hardware=None) -> str:
71
+ """레포를 Gradio Space로 배포하고 Space URL 반환."""
72
+ login(hf_token)
73
+ api = HfApi(token=hf_token)
74
+ user = api.whoami()["name"]
75
+ space = f"{user}/{Path(repo_url).stem.lower().replace('.', '-')}"
76
+ api.create_repo(
77
+ repo_id=space,
78
+ repo_type="space",
79
+ space_sdk="gradio",
80
+ private=private,
81
+ exist_ok=True,
82
+ hardware=hardware,
83
+ )
84
+
85
+ with tempfile.TemporaryDirectory() as work:
86
+ # 1) Brave 메타데이터
87
+ brave_meta = brave_search_repo(repo_url)
88
+
89
+ # 2) 원본 레포 클론
90
+ src = Path(work) / "src"
91
+ git.Repo.clone_from(repo_url, src)
92
+
93
+ readme = ""
94
+ readme_path = src / "README.md"
95
+ if readme_path.exists():
96
+ readme = readme_path.read_text(encoding="utf-8", errors="ignore")[:4000]
97
+
98
+ tree_out = subprocess.run(
99
+ ["bash", "-lc", f"tree -L 2 {src} | head -n 40"],
100
+ text=True, capture_output=True
101
+ ).stdout
102
+
103
+ context = textwrap.dedent(f"""
104
+ ## Brave meta
105
+ {json.dumps(brave_meta, ensure_ascii=False, indent=2)}
106
+
107
+ ## Repository tree (depth 2)
108
+ {tree_out}
109
+
110
+ ## README (first 4 kB)
111
+ {readme}
112
+ """)
113
+
114
+ # 3) Friendli 스캐폴드 생성
115
+ scaffold = friendli_generate_scaffold(context)
116
+
117
+ # 4) Space 레포 클론 & 파일 쓰기
118
+ dst = Path(work) / "space"
119
+ api.clone_repo(space, local_dir=dst)
120
+
121
+ (dst / "app.py").write_text(scaffold["app_py"], encoding="utf-8")
122
+ (dst / "requirements.txt").write_text(scaffold["requirements_txt"], encoding="utf-8")
123
+ if scaffold.get("need_docker"):
124
+ (dst / "Dockerfile").write_text(scaffold["dockerfile"], encoding="utf-8")
125
+ (dst / "README.md").write_text(scaffold["summary"], encoding="utf-8")
126
+
127
+ # 5) 커밋 & 푸시
128
+ subprocess.run(["git", "-C", dst, "add", "."], check=True)
129
+ subprocess.run(["git", "-C", dst, "commit", "-m", "Initial auto-deploy"], check=True)
130
+ subprocess.run(["git", "-C", dst, "push"], check=True)
131
+
132
+ return f"https://huggingface.co/spaces/{space}"
133
+
134
+ # ---------- CLI 엔트리 ---------- #
135
+ def main():
136
+ p = argparse.ArgumentParser(description="Git 레포를 Hugging Face Gradio Space로 자동 배포")
137
+ p.add_argument("--repo_url", required=True, help="원본 Git 레포지토리 URL")
138
+ p.add_argument("--hf_token", required=True, help="쓰기 권한 Hugging Face 토큰")
139
+ p.add_argument("--private", action="store_true", help="비공개 Space 생성")
140
+ p.add_argument("--hardware", default=None, help="예: 't4-medium'")
141
+ args = p.parse_args()
142
+
143
+ try:
144
+ url = deploy(args.repo_url, args.hf_token, args.private, args.hardware)
145
+ print(f"✅ 배포 성공: {url}")
146
+ except Exception as e:
147
+ print(f"❌ 배포 실패: {e}", file=sys.stderr)
148
+ sys.exit(1)
149
+
150
+ if __name__ == "__main__":
151
+ main()