openfree commited on
Commit
eea45de
ยท
verified ยท
1 Parent(s): f19ec1c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1348 -0
app.py ADDED
@@ -0,0 +1,1348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from huggingface_hub import HfApi, login
3
+ import uuid
4
+ from slugify import slugify
5
+ import os
6
+ import json
7
+ import subprocess
8
+ import tempfile
9
+ import requests
10
+ import shutil
11
+ import time
12
+ from pathlib import Path
13
+ from typing import Optional, Dict, List
14
+
15
+ def is_lfs_pointer_file(filepath):
16
+ try:
17
+ with open(filepath, 'rb') as f:
18
+ header = f.read(100)
19
+ return header.startswith(b'version https://git-lfs.github.com/spec/v1')
20
+ except:
21
+ return False
22
+
23
+ def remove_lfs_files(folder):
24
+ removed_files = []
25
+ for root, dirs, files in os.walk(folder):
26
+ if '.git' in root:
27
+ continue
28
+ for file in files:
29
+ filepath = os.path.join(root, file)
30
+ if is_lfs_pointer_file(filepath):
31
+ os.remove(filepath)
32
+ removed_files.append(filepath.replace(folder + os.sep, ''))
33
+ return removed_files
34
+
35
+ def analyze_repository(src_path: Path) -> Dict:
36
+ analysis = {
37
+ "has_requirements": False,
38
+ "has_readme": False,
39
+ "main_language": "python",
40
+ "key_files": [],
41
+ "dependencies": [],
42
+ "description": "",
43
+ "entry_points": [],
44
+ "model_files": [],
45
+ "config_files": []
46
+ }
47
+
48
+ req_file = src_path / "requirements.txt"
49
+ if req_file.exists():
50
+ analysis["has_requirements"] = True
51
+ try:
52
+ reqs = req_file.read_text(encoding="utf-8").strip().split("\n")
53
+ cleaned_deps = []
54
+ for r in reqs:
55
+ r = r.strip()
56
+ if r and not r.startswith("#"):
57
+ if "opencv-python==4.10.0" in r:
58
+ r = "opencv-python>=4.10.0.82"
59
+ elif "opencv-python==4.10" in r:
60
+ r = "opencv-python>=4.10.0.82"
61
+
62
+ if "==" in r and not r.startswith("git+"):
63
+ pkg_name = r.split("==")[0]
64
+ if pkg_name.lower() in ["torch", "tensorflow", "transformers", "numpy"]:
65
+ cleaned_deps.append(r)
66
+ else:
67
+ version = r.split("==")[1]
68
+ if version.count('.') == 1:
69
+ version = version + ".0"
70
+ cleaned_deps.append(f"{pkg_name}>={version}")
71
+ else:
72
+ cleaned_deps.append(r)
73
+ analysis["dependencies"] = cleaned_deps
74
+ except:
75
+ analysis["dependencies"] = []
76
+
77
+ for readme_name in ["README.md", "readme.md", "README.rst", "README.txt"]:
78
+ readme_file = src_path / readme_name
79
+ if readme_file.exists():
80
+ analysis["has_readme"] = True
81
+ try:
82
+ readme_content = readme_file.read_text(encoding="utf-8")
83
+ analysis["readme_content"] = readme_content[:5000]
84
+ lines = readme_content.split("\n")
85
+ for i, line in enumerate(lines[:10]):
86
+ if line.strip() and not line.startswith("#") and not line.startswith("!"):
87
+ analysis["description"] = line.strip()
88
+ break
89
+ except:
90
+ pass
91
+
92
+ py_files = list(src_path.glob("**/*.py"))
93
+ for py_file in py_files[:20]:
94
+ if "__pycache__" not in str(py_file) and ".git" not in str(py_file):
95
+ relative_path = py_file.relative_to(src_path)
96
+
97
+ if any(name in py_file.name for name in ["main.py", "app.py", "demo.py", "run.py", "server.py", "streamlit_app.py"]):
98
+ analysis["entry_points"].append(str(relative_path))
99
+
100
+ try:
101
+ content = py_file.read_text(encoding="utf-8")[:1000]
102
+ if "if __name__" in content and "main" in content:
103
+ analysis["entry_points"].append(str(relative_path))
104
+
105
+ if any(lib in content for lib in ["torch", "tensorflow", "transformers", "numpy", "pandas", "cv2", "PIL"]):
106
+ analysis["key_files"].append({
107
+ "path": str(relative_path),
108
+ "preview": content[:500]
109
+ })
110
+ except:
111
+ pass
112
+
113
+ model_extensions = [".pth", ".pt", ".ckpt", ".h5", ".pb", ".onnx", ".safetensors"]
114
+ for ext in model_extensions:
115
+ model_files = list(src_path.glob(f"**/*{ext}"))
116
+ for mf in model_files[:5]:
117
+ if ".git" not in str(mf):
118
+ analysis["model_files"].append(str(mf.relative_to(src_path)))
119
+
120
+ config_patterns = ["config.json", "config.yaml", "config.yml", "*.json", "*.yaml"]
121
+ for pattern in config_patterns:
122
+ config_files = list(src_path.glob(pattern))
123
+ for cf in config_files[:5]:
124
+ if ".git" not in str(cf):
125
+ analysis["config_files"].append(str(cf.relative_to(src_path)))
126
+
127
+ return analysis
128
+
129
+ def generate_gradio_app(repo_url: str, analysis: Dict) -> Dict:
130
+ context = f"""Repository URL: {repo_url}
131
+
132
+ Repository Analysis:
133
+ - Description: {analysis.get('description', 'N/A')}
134
+ - Main Dependencies: {', '.join(analysis['dependencies'][:10])}
135
+ - Entry Points: {', '.join(analysis['entry_points'][:5])}
136
+ - Model Files: {', '.join(analysis['model_files'][:3])}
137
+ - Config Files: {', '.join(analysis['config_files'][:3])}
138
+
139
+ Key Files Found:
140
+ """
141
+
142
+ for kf in analysis.get('key_files', [])[:3]:
143
+ context += f"\n--- {kf['path']} ---\n{kf['preview']}\n"
144
+
145
+ if analysis.get('readme_content'):
146
+ context += f"\n--- README.md (excerpt) ---\n{analysis['readme_content'][:2000]}\n"
147
+
148
+ system_prompt = """You are an expert at creating Gradio apps from GitHub repositories.
149
+ Your task is to generate a complete, working Gradio interface that demonstrates the main functionality of the repository.
150
+
151
+ CRITICAL REQUIREMENTS:
152
+ 1. The app.py must be FULLY FUNCTIONAL and runnable
153
+ 2. DO NOT use 'from agent import' or any repository-specific imports that won't exist
154
+ 3. Handle errors gracefully with clear user feedback
155
+ 4. Include API key inputs when external services are required
156
+ 5. Create intuitive UI components for the main features
157
+ 6. Always use gradio>=5.35.0
158
+
159
+ Return ONLY valid JSON with these exact keys:
160
+ - app_py: Complete Gradio app code
161
+ - requirements_txt: All necessary dependencies including gradio>=5.35.0
162
+ - summary: Brief description of what the app does"""
163
+
164
+ fireworks_key = os.getenv("FIREWORKS_API_KEY")
165
+ if fireworks_key:
166
+ try:
167
+ url = "https://api.fireworks.ai/inference/v1/chat/completions"
168
+ payload = {
169
+ "model": "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
170
+ "max_tokens": 4096,
171
+ "top_p": 1,
172
+ "top_k": 40,
173
+ "presence_penalty": 0,
174
+ "frequency_penalty": 0,
175
+ "temperature": 0.6,
176
+ "messages": [
177
+ {"role": "system", "content": system_prompt},
178
+ {"role": "user", "content": f"Create a fully functional Gradio app for this repository:\n\n{context[:8000]}"}
179
+ ]
180
+ }
181
+ headers = {
182
+ "Accept": "application/json",
183
+ "Content-Type": "application/json",
184
+ "Authorization": f"Bearer {fireworks_key.strip()}"
185
+ }
186
+
187
+ r = requests.post(url, headers=headers, data=json.dumps(payload), timeout=30)
188
+
189
+ if r.status_code == 200:
190
+ response_text = r.json()["choices"][0]["message"]["content"]
191
+ print("โœ… Fireworks AI๋กœ ์•ฑ ์ƒ์„ฑ ์„ฑ๊ณต")
192
+
193
+ try:
194
+ if "```json" in response_text:
195
+ start = response_text.find("```json") + 7
196
+ end = response_text.find("```", start)
197
+ response_text = response_text[start:end].strip()
198
+ elif "```" in response_text:
199
+ start = response_text.find("```") + 3
200
+ end = response_text.find("```", start)
201
+ response_text = response_text[start:end].strip()
202
+
203
+ result = json.loads(response_text)
204
+
205
+ if not all(key in result for key in ["app_py", "requirements_txt", "summary"]):
206
+ raise ValueError("Missing required keys in response")
207
+
208
+ if "gradio" not in result.get("requirements_txt", "").lower():
209
+ result["requirements_txt"] = "gradio>=5.35.0\n" + result.get("requirements_txt", "")
210
+
211
+ return result
212
+
213
+ except (json.JSONDecodeError, ValueError) as e:
214
+ print(f"โš ๏ธ JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜: {e}")
215
+ return None
216
+ except Exception as e:
217
+ print(f"โš ๏ธ Fireworks AI API ์˜ค๋ฅ˜: {e}")
218
+
219
+ print("โ„น๏ธ AI API๊ฐ€ ์—†์–ด ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
220
+ return create_smart_template(repo_url, analysis)
221
+
222
+ def create_smart_template(repo_url: str, analysis: Dict) -> Dict:
223
+ repo_name = Path(repo_url.rstrip("/")).name
224
+ description = analysis.get("description", "A project deployed from GitHub") if analysis else "A project deployed from GitHub"
225
+
226
+ deps = " ".join(analysis.get("dependencies", [])) if analysis else ""
227
+ has_cv = any(lib in deps for lib in ["cv2", "PIL", "pillow", "opencv"])
228
+ has_nlp = any(lib in deps for lib in ["transformers", "nltk", "spacy"])
229
+ has_3d = any(lib in deps for lib in ["gaussian", "rasterizer", "plyfile", "trimesh"])
230
+
231
+ requirements = ["gradio>=5.35.0"]
232
+ if analysis and analysis.get("dependencies"):
233
+ filtered_deps = []
234
+ for dep in analysis["dependencies"][:15]:
235
+ if not dep.startswith("git+") and not dep.startswith("-e") and not dep.startswith("file:"):
236
+ if "==" in dep and dep.split("==")[0].lower() not in ["torch", "tensorflow", "numpy"]:
237
+ pkg_name = dep.split("==")[0]
238
+ version = dep.split("==")[1]
239
+ filtered_deps.append(f"{pkg_name}>={version}")
240
+ else:
241
+ filtered_deps.append(dep)
242
+ requirements.extend(filtered_deps)
243
+
244
+ if has_3d or "gaussian" in repo_name.lower():
245
+ app_code = f'''import gradio as gr
246
+ import os
247
+
248
+ def process_3d(input_file):
249
+ if input_file is None:
250
+ return "Please upload a 3D file or image"
251
+
252
+ info = """
253
+ ## โš ๏ธ Build Requirements Notice
254
+
255
+ This project requires:
256
+ 1. CUDA-enabled GPU
257
+ 2. Custom C++/CUDA extensions compilation
258
+
259
+ Original repository: {repo_url}
260
+ """
261
+ return info
262
+
263
+ with gr.Blocks(title="{repo_name}") as demo:
264
+ gr.Markdown(f"""
265
+ # {repo_name.replace("-", " ").title()}
266
+
267
+ {description}
268
+
269
+ This space was created from: [{repo_url}]({repo_url})
270
+ """)
271
+
272
+ with gr.Row():
273
+ with gr.Column():
274
+ input_file = gr.File(label="Upload 3D File or Image")
275
+ process_btn = gr.Button("Process", variant="primary")
276
+
277
+ with gr.Column():
278
+ output_info = gr.Markdown()
279
+
280
+ process_btn.click(
281
+ fn=process_3d,
282
+ inputs=input_file,
283
+ outputs=output_info
284
+ )
285
+
286
+ if __name__ == "__main__":
287
+ demo.launch()
288
+ '''
289
+ elif has_cv:
290
+ app_code = f'''import gradio as gr
291
+ from PIL import Image
292
+ import numpy as np
293
+
294
+ def process_image(image):
295
+ if image is None:
296
+ return None, "Please upload an image"
297
+
298
+ img_array = np.array(image)
299
+ processed = Image.fromarray(img_array)
300
+
301
+ info = f"Image shape: {{img_array.shape}}"
302
+ return processed, info
303
+
304
+ with gr.Blocks(title="{repo_name}") as demo:
305
+ gr.Markdown(f"""
306
+ # {repo_name.replace("-", " ").title()}
307
+
308
+ {description}
309
+
310
+ This space was created from: [{repo_url}]({repo_url})
311
+ """)
312
+
313
+ with gr.Row():
314
+ with gr.Column():
315
+ input_image = gr.Image(label="Input Image", type="pil")
316
+ process_btn = gr.Button("Process Image", variant="primary")
317
+
318
+ with gr.Column():
319
+ output_image = gr.Image(label="Output Image")
320
+ output_info = gr.Textbox(label="Information")
321
+
322
+ process_btn.click(
323
+ fn=process_image,
324
+ inputs=input_image,
325
+ outputs=[output_image, output_info]
326
+ )
327
+
328
+ if __name__ == "__main__":
329
+ demo.launch()
330
+ '''
331
+
332
+ elif has_nlp:
333
+ app_code = f'''import gradio as gr
334
+
335
+ def process_text(text, max_length=100):
336
+ if not text:
337
+ return "Please enter some text"
338
+
339
+ word_count = len(text.split())
340
+ char_count = len(text)
341
+
342
+ result = f"""
343
+ **Analysis Results:**
344
+ - Word count: {{word_count}}
345
+ - Character count: {{char_count}}
346
+ - Average word length: {{char_count/max(word_count, 1):.1f}}
347
+ """
348
+
349
+ return result
350
+
351
+ with gr.Blocks(title="{repo_name}") as demo:
352
+ gr.Markdown(f"""
353
+ # {repo_name.replace("-", " ").title()}
354
+
355
+ {description}
356
+
357
+ This space was created from: [{repo_url}]({repo_url})
358
+ """)
359
+
360
+ with gr.Row():
361
+ with gr.Column():
362
+ input_text = gr.Textbox(
363
+ label="Input Text",
364
+ placeholder="Enter your text here...",
365
+ lines=5
366
+ )
367
+ max_length = gr.Slider(
368
+ minimum=10,
369
+ maximum=500,
370
+ value=100,
371
+ label="Max Length"
372
+ )
373
+ process_btn = gr.Button("Process Text", variant="primary")
374
+
375
+ with gr.Column():
376
+ output_text = gr.Markdown(label="Results")
377
+
378
+ process_btn.click(
379
+ fn=process_text,
380
+ inputs=[input_text, max_length],
381
+ outputs=output_text
382
+ )
383
+
384
+ if __name__ == "__main__":
385
+ demo.launch()
386
+ '''
387
+
388
+ else:
389
+ app_code = f'''import gradio as gr
390
+
391
+ def main_function(input_data):
392
+ if not input_data:
393
+ return "Please provide input"
394
+
395
+ result = f"Processed successfully! Input received: {{input_data}}"
396
+ return result
397
+
398
+ with gr.Blocks(title="{repo_name}") as demo:
399
+ gr.Markdown(f"""
400
+ # {repo_name.replace("-", " ").title()}
401
+
402
+ {description}
403
+
404
+ This space was created from: [{repo_url}]({repo_url})
405
+ """)
406
+
407
+ with gr.Row():
408
+ with gr.Column():
409
+ input_data = gr.Textbox(
410
+ label="Input",
411
+ placeholder="Enter your input here...",
412
+ lines=3
413
+ )
414
+ process_btn = gr.Button("Process", variant="primary")
415
+
416
+ with gr.Column():
417
+ output_data = gr.Textbox(label="Output")
418
+
419
+ process_btn.click(
420
+ fn=main_function,
421
+ inputs=input_data,
422
+ outputs=output_data
423
+ )
424
+
425
+ if __name__ == "__main__":
426
+ demo.launch()
427
+ '''
428
+
429
+ return {
430
+ "app_py": app_code,
431
+ "requirements_txt": "\n".join(requirements),
432
+ "summary": f"Smart template created for {repo_name}"
433
+ }
434
+
435
+ 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):
436
+ # Use temporary directory instead of current working directory
437
+ temp_base_dir = tempfile.gettempdir()
438
+ folder_name = str(uuid.uuid4())
439
+ folder = os.path.join(temp_base_dir, folder_name)
440
+
441
+ # ํ† ํฐ ์šฐ์„ ์ˆœ์œ„: 1. ์‚ฌ์šฉ์ž ์ž…๋ ฅ ํ† ํฐ, 2. OAuth ํ† ํฐ, 3. ํ™˜๊ฒฝ๋ณ€์ˆ˜
442
+ hf_token = None
443
+ username = None
444
+
445
+ if user_hf_token and user_hf_token.strip():
446
+ # ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ž…๋ ฅํ•œ ํ† ํฐ ์‚ฌ์šฉ
447
+ hf_token = user_hf_token.strip()
448
+ yield "๐Ÿ”‘ Using provided HuggingFace token..."
449
+ elif oauth_token and oauth_token.token:
450
+ # OAuth ํ† ํฐ ์‹œ๋„ (ํ•˜์ง€๋งŒ Space ์ƒ์„ฑ ๊ถŒํ•œ์ด ์—†์„ ์ˆ˜ ์žˆ์Œ)
451
+ hf_token = oauth_token.token
452
+ username = profile.username if profile else None
453
+ yield f"๐Ÿ” Attempting with OAuth token for @{username}..."
454
+ yield "โš ๏ธ Note: OAuth may have limited permissions. If Space creation fails, please provide a personal access token."
455
+ else:
456
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฒดํฌ
457
+ hf_token = os.getenv("HF_TOKEN")
458
+ if not hf_token:
459
+ yield """โŒ Authentication Required!
460
+
461
+ Please choose one of these options:
462
+ 1. **Recommended**: Enter your HuggingFace token in the field below
463
+ 2. Login with the 'Sign in with Hugging Face' button (may have limited permissions)
464
+ 3. Set HF_TOKEN in environment variables
465
+
466
+ To get a token:
467
+ 1. Go to https://huggingface.co/settings/tokens
468
+ 2. Click 'New token'
469
+ 3. Select 'write' role
470
+ 4. Copy and paste the token"""
471
+ return
472
+
473
+ try:
474
+ yield "๐Ÿ”„ Starting clone process..."
475
+
476
+ # HuggingFace API ์ธ์ฆ
477
+ try:
478
+ api = HfApi(token=hf_token)
479
+ user_info = api.whoami()
480
+ username = user_info["name"]
481
+ yield f"โœ… Authenticated as HuggingFace user: @{username}"
482
+
483
+ # ๊ถŒํ•œ ํ™•์ธ
484
+ if "write" not in user_info.get("auth", {}).get("accessToken", {}).get("role", ""):
485
+ yield "โš ๏ธ Warning: Token may not have write permissions. If Space creation fails, please use a token with 'write' role."
486
+ except Exception as e:
487
+ if "403" in str(e) or "401" in str(e):
488
+ yield f"""โŒ Authentication failed!
489
+
490
+ The token doesn't have sufficient permissions.
491
+ Please create a new token with 'write' access:
492
+ 1. Go to https://huggingface.co/settings/tokens
493
+ 2. Click 'New token'
494
+ 3. Give it a name
495
+ 4. Select 'write' role (important!)
496
+ 5. Copy and paste the token
497
+
498
+ Error: {str(e)}"""
499
+ return
500
+ else:
501
+ yield f"โŒ HuggingFace authentication failed: {str(e)}"
502
+ return
503
+
504
+ # URL ์ •๊ทœํ™” ๋ฐ ๊ฒ€์ฆ
505
+ repo_git = repo_git.strip()
506
+
507
+ # .git ํ™•์žฅ์ž ์ถ”๊ฐ€ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ)
508
+ if not repo_git.endswith('.git'):
509
+ repo_git_with_git = repo_git + '.git'
510
+ else:
511
+ repo_git_with_git = repo_git
512
+
513
+ # HTTPS URL๋กœ ๋ณ€ํ™˜
514
+ if repo_git.startswith('git@github.com:'):
515
+ repo_git = repo_git.replace('git@github.com:', 'https://github.com/')
516
+ elif not repo_git.startswith('http'):
517
+ yield "โŒ Invalid repository URL. Please use HTTPS URL (e.g., https://github.com/username/repo)"
518
+ return
519
+
520
+ yield f"๐Ÿ“ฅ Cloning repository from {repo_git}..."
521
+ yield f" Using temporary directory: {folder}"
522
+
523
+ env = os.environ.copy()
524
+ env['GIT_LFS_SKIP_SMUDGE'] = '1'
525
+
526
+ # ๋จผ์ € .git ์—†์ด ์‹œ๋„
527
+ clone_cmd = ['git', 'clone', '--depth', '1', repo_git, folder]
528
+ result = subprocess.run(clone_cmd, env=env, capture_output=True, text=True)
529
+
530
+ # ์‹คํŒจํ•˜๋ฉด .git ์ถ”๊ฐ€ํ•˜์—ฌ ์žฌ์‹œ๋„
531
+ if result.returncode != 0:
532
+ if ".git" not in repo_git:
533
+ yield " Retrying with .git extension..."
534
+ clone_cmd = ['git', 'clone', '--depth', '1', repo_git_with_git, folder]
535
+ result = subprocess.run(clone_cmd, env=env, capture_output=True, text=True)
536
+
537
+ if result.returncode != 0:
538
+ error_msg = result.stderr
539
+ if "Repository not found" in error_msg or "fatal: repository" in error_msg:
540
+ yield f"โŒ Repository not found. Please check:"
541
+ yield f" 1. The repository URL is correct"
542
+ yield f" 2. The repository is public"
543
+ yield f" 3. The repository exists"
544
+ yield f" URL tried: {repo_git}"
545
+ else:
546
+ yield f"โŒ Git clone failed: {error_msg[:500]}"
547
+ if os.path.exists(folder):
548
+ shutil.rmtree(folder)
549
+ return
550
+
551
+ yield "โœ… Repository cloned successfully"
552
+
553
+ # Git ํด๋ก  ์žฌ์‹œ๋„ ๋กœ์ง ๊ฐœ์„ 
554
+ if not skip_lfs:
555
+ yield "๐Ÿ“ฆ Attempting to download LFS files..."
556
+ try:
557
+ # Git LFS ์„ค์น˜ ํ™•์ธ
558
+ lfs_check = subprocess.run(['git', 'lfs', 'version'], capture_output=True, text=True)
559
+ if lfs_check.returncode != 0:
560
+ yield "โš ๏ธ Git LFS not installed. Skipping LFS files..."
561
+ skip_lfs = True
562
+ else:
563
+ subprocess.run(['git', 'lfs', 'install'], cwd=folder, check=True)
564
+ lfs_result = subprocess.run(['git', 'lfs', 'pull'], cwd=folder, capture_output=True, text=True)
565
+
566
+ if lfs_result.returncode != 0:
567
+ yield f"โš ๏ธ Warning: LFS download failed - {lfs_result.stderr[:200]}"
568
+ skip_lfs = True
569
+ else:
570
+ yield "โœ… LFS files downloaded successfully"
571
+ except Exception as e:
572
+ yield f"โš ๏ธ LFS error: {str(e)}"
573
+ skip_lfs = True
574
+
575
+ if skip_lfs:
576
+ yield "๐Ÿงน Removing LFS pointer files..."
577
+ removed_files = remove_lfs_files(folder)
578
+ if removed_files:
579
+ yield f"๐Ÿ“ Removed {len(removed_files)} LFS pointer files"
580
+
581
+ if enable_smart_generation:
582
+ yield "๐Ÿ” Analyzing repository structure..."
583
+ folder_path = Path(folder)
584
+ analysis = analyze_repository(folder_path)
585
+
586
+ yield "๐Ÿค– Generating smart Gradio app..."
587
+ generated = generate_gradio_app(repo_git, analysis)
588
+
589
+ if generated and isinstance(generated, dict) and "app_py" in generated:
590
+ app_path = folder_path / "app.py"
591
+ app_path.write_text(generated["app_py"], encoding="utf-8")
592
+ yield "โœ… Smart app.py generated"
593
+
594
+ req_path = folder_path / "requirements.txt"
595
+ existing_reqs = []
596
+ if req_path.exists():
597
+ try:
598
+ existing_reqs = req_path.read_text(encoding="utf-8").strip().split("\n")
599
+ except:
600
+ existing_reqs = []
601
+
602
+ new_reqs = generated["requirements_txt"].strip().split("\n") if generated["requirements_txt"] else []
603
+
604
+ all_reqs = set()
605
+ git_reqs = []
606
+ torch_reqs = []
607
+ regular_reqs = []
608
+
609
+ for req in existing_reqs + new_reqs:
610
+ req = req.strip()
611
+ if not req or req.startswith("#"):
612
+ continue
613
+
614
+ if req.startswith("git+"):
615
+ git_reqs.append(req)
616
+ elif "torch" in req.lower() or "cuda" in req.lower():
617
+ torch_reqs.append(req)
618
+ else:
619
+ regular_reqs.append(req)
620
+
621
+ has_gradio = any("gradio" in req for req in regular_reqs)
622
+ if not has_gradio:
623
+ regular_reqs.append("gradio>=5.35.0")
624
+
625
+ final_reqs = []
626
+
627
+ if torch_reqs:
628
+ final_reqs.extend(sorted(set(torch_reqs)))
629
+ final_reqs.append("")
630
+
631
+ final_reqs.extend(sorted(set(regular_reqs)))
632
+
633
+ if git_reqs:
634
+ final_reqs.append("")
635
+ final_reqs.extend(sorted(set(git_reqs)))
636
+
637
+ req_content = "\n".join(final_reqs)
638
+ req_path.write_text(req_content, encoding="utf-8")
639
+ yield "โœ… Requirements.txt updated"
640
+
641
+ readme_path = folder_path / "README.md"
642
+ readme_content = f"""---
643
+ title: {repo_hf.replace("-", " ").title()}
644
+ emoji: ๐Ÿš€
645
+ colorFrom: blue
646
+ colorTo: green
647
+ sdk: {sdk_type}
648
+ sdk_version: "5.35.0"
649
+ app_file: app.py
650
+ pinned: false
651
+ ---
652
+
653
+ # {repo_hf.replace("-", " ").title()}
654
+
655
+ {analysis.get('description', 'Deployed from GitHub repository')}
656
+
657
+ Deployed from: {repo_git}
658
+ """
659
+ readme_path.write_text(readme_content, encoding="utf-8")
660
+ yield "โœ… README.md created/updated"
661
+
662
+ git_dir = os.path.join(folder, '.git')
663
+ if os.path.exists(git_dir):
664
+ shutil.rmtree(git_dir)
665
+ yield "๐Ÿงน Removed .git directory"
666
+
667
+ gitattributes_path = os.path.join(folder, '.gitattributes')
668
+ if os.path.exists(gitattributes_path):
669
+ with open(gitattributes_path, 'r') as f:
670
+ lines = f.readlines()
671
+
672
+ new_lines = []
673
+ for line in lines:
674
+ if 'filter=lfs' not in line:
675
+ new_lines.append(line)
676
+
677
+ if new_lines:
678
+ with open(gitattributes_path, 'w') as f:
679
+ f.writelines(new_lines)
680
+ else:
681
+ os.remove(gitattributes_path)
682
+
683
+ yield "๐Ÿ—๏ธ Creating Hugging Face Space..."
684
+
685
+ repo_id = f"{username}/{slugify(repo_hf)}"
686
+ space_created = False
687
+
688
+ for attempt in range(3):
689
+ try:
690
+ yield f" Creating Space: {repo_id} (attempt {attempt + 1}/3)"
691
+
692
+ try:
693
+ existing_space = api.space_info(repo_id=repo_id, token=hf_token)
694
+ yield f" โ„น๏ธ Space already exists: {existing_space.id}"
695
+ space_created = True
696
+ break
697
+ except:
698
+ pass
699
+
700
+ create_result = api.create_repo(
701
+ repo_id=repo_id,
702
+ repo_type="space",
703
+ space_sdk=sdk_type,
704
+ exist_ok=True,
705
+ private=False,
706
+ token=hf_token
707
+ )
708
+
709
+ time.sleep(3)
710
+
711
+ space_info = api.space_info(repo_id=repo_id, token=hf_token)
712
+ yield f" โœ… Space created successfully: {space_info.id}"
713
+ space_created = True
714
+ break
715
+
716
+ except Exception as e:
717
+ error_msg = str(e)
718
+
719
+ if "429" in error_msg or "Too Many Requests" in error_msg:
720
+ yield f"โŒ Rate Limit Error - Try again in 17-24 hours"
721
+ if os.path.exists(folder):
722
+ shutil.rmtree(folder)
723
+ raise Exception(f"Rate limit reached.")
724
+
725
+ yield f" โš ๏ธ Attempt {attempt + 1} failed: {error_msg[:100]}..."
726
+ if attempt < 2:
727
+ yield " Retrying in 5 seconds..."
728
+ time.sleep(5)
729
+ else:
730
+ yield f" โŒ Failed to create space after 3 attempts"
731
+ if os.path.exists(folder):
732
+ shutil.rmtree(folder)
733
+ raise Exception(f"Could not create space: {error_msg}")
734
+
735
+ if not space_created:
736
+ if os.path.exists(folder):
737
+ shutil.rmtree(folder)
738
+ raise Exception("Failed to create space")
739
+
740
+ folder_size = sum(os.path.getsize(os.path.join(dirpath, filename))
741
+ for dirpath, dirnames, filenames in os.walk(folder)
742
+ for filename in filenames) / (1024 * 1024)
743
+
744
+ yield f"๐Ÿ“Š Folder size: {folder_size:.2f} MB"
745
+
746
+ file_count = sum(len(files) for _, _, files in os.walk(folder))
747
+ yield f"๐Ÿ“ Total files to upload: {file_count}"
748
+
749
+ upload_success = False
750
+ max_retries = 3
751
+
752
+ for attempt in range(max_retries):
753
+ try:
754
+ if attempt > 0:
755
+ yield f"๐Ÿ“ค Upload attempt {attempt + 1}/{max_retries}..."
756
+ time.sleep(5)
757
+
758
+ if folder_size > 500:
759
+ yield "๐Ÿ“ค Uploading large folder to Hugging Face..."
760
+ api.upload_large_folder(
761
+ folder_path=folder,
762
+ repo_id=repo_id,
763
+ repo_type="space",
764
+ token=hf_token,
765
+ commit_message="Deploy from GitHub repository",
766
+ ignore_patterns=["*.pyc", "__pycache__", ".git*", ".DS_Store", "*.egg-info"]
767
+ )
768
+ else:
769
+ yield "๐Ÿ“ค Uploading to Hugging Face..."
770
+ api.upload_folder(
771
+ folder_path=folder,
772
+ repo_id=repo_id,
773
+ repo_type="space",
774
+ token=hf_token,
775
+ commit_message="Deploy from GitHub repository",
776
+ ignore_patterns=["*.pyc", "__pycache__", ".git*", ".DS_Store", "*.egg-info"]
777
+ )
778
+
779
+ upload_success = True
780
+ yield "โœ… Upload completed successfully"
781
+ break
782
+
783
+ except Exception as upload_error:
784
+ error_msg = str(upload_error)
785
+
786
+ if "404" in error_msg and attempt < max_retries - 1:
787
+ yield f" โš ๏ธ Upload failed (404). Retrying..."
788
+ time.sleep(10)
789
+
790
+ try:
791
+ space_info = api.space_info(repo_id=repo_id, token=hf_token)
792
+ yield f" โœ… Space confirmed to exist"
793
+ except:
794
+ yield " ๐Ÿ”„ Attempting to recreate space..."
795
+ try:
796
+ api.create_repo(
797
+ repo_id=repo_id,
798
+ repo_type="space",
799
+ space_sdk=sdk_type,
800
+ exist_ok=True,
801
+ private=False,
802
+ token=hf_token
803
+ )
804
+ yield " โœ… Space recreated"
805
+ except Exception as recreate_error:
806
+ yield f" โŒ Could not recreate space: {str(recreate_error)}"
807
+
808
+ elif "LFS pointer" in error_msg:
809
+ yield "โŒ Upload failed due to remaining LFS pointer files"
810
+ if os.path.exists(folder):
811
+ shutil.rmtree(folder)
812
+ raise upload_error
813
+
814
+ elif attempt == max_retries - 1:
815
+ yield f"โŒ Upload failed after {max_retries} attempts"
816
+ if os.path.exists(folder):
817
+ shutil.rmtree(folder)
818
+ raise upload_error
819
+ else:
820
+ yield f" โš ๏ธ Upload failed: {error_msg[:100]}..."
821
+
822
+ if not upload_success:
823
+ if os.path.exists(folder):
824
+ shutil.rmtree(folder)
825
+ raise Exception("Upload failed after all retries")
826
+
827
+ # Clean up temporary folder
828
+ if os.path.exists(folder):
829
+ shutil.rmtree(folder)
830
+
831
+ space_url = f"https://huggingface.co/spaces/{repo_id}"
832
+
833
+ yield f"""
834
+ โœ… **Successfully created Space!**
835
+
836
+ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
837
+
838
+ ๐ŸŽ‰ **Your Space is Ready!**
839
+
840
+ ๐Ÿ”— **Space URL**: {space_url}
841
+
842
+ ๐Ÿ“‹ **Click here to open**: [{repo_id}]({space_url})
843
+
844
+ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
845
+
846
+ ๐Ÿ“Š **Deployment Details:**
847
+ โ€ข Space ID: `{repo_id}`
848
+ โ€ข Source: {repo_git}
849
+ โ€ข SDK: {sdk_type}
850
+ โ€ข Smart Generation: {'Enabled' if enable_smart_generation else 'Disabled'}
851
+ โ€ข LFS Files: {'Skipped' if skip_lfs else 'Included'}
852
+
853
+ โฑ๏ธ **Next Steps:**
854
+ 1. Click the link above to open your Space
855
+ 2. Wait 1-2 minutes for initial build
856
+ 3. Check "App" tab to see your deployed application
857
+ 4. Check "Logs" tab if you encounter any issues
858
+
859
+ ๐Ÿ’ก **Tips:**
860
+ โ€ข Building may take 2-5 minutes depending on dependencies
861
+ โ€ข If build fails, check requirements.txt in "Files" tab
862
+ โ€ข You can edit files directly in the Space interface
863
+ """
864
+
865
+ if skip_lfs:
866
+ yield "\nโš ๏ธ LFS files were removed."
867
+
868
+ if enable_smart_generation:
869
+ yield "\n๐Ÿค– AI-generated Gradio interface was created"
870
+
871
+ except subprocess.CalledProcessError as e:
872
+ if os.path.exists(folder):
873
+ shutil.rmtree(folder)
874
+ yield f"โŒ Git error: {str(e)}"
875
+ except Exception as e:
876
+ if os.path.exists(folder):
877
+ shutil.rmtree(folder)
878
+ yield f"โŒ Error: {str(e)}"
879
+
880
+ def space_to_github(hf_username, hf_space_name, github_username, github_repo_name, github_token):
881
+ """Clone HuggingFace Space and push to GitHub"""
882
+ tmp_dir = None
883
+ try:
884
+ # HF Space URL ์ƒ์„ฑ
885
+ hf_repo_url = f"https://huggingface.co/spaces/{hf_username}/{hf_space_name}.git"
886
+
887
+ # ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ ๋ฐ ํด๋ก 
888
+ tmp_dir = tempfile.mkdtemp()
889
+ yield f"๐Ÿ“ฅ Cloning HF Space: {hf_username}/{hf_space_name}..."
890
+ yield f" Using temporary directory: {tmp_dir}"
891
+
892
+ # Git ํด๋ก 
893
+ env = os.environ.copy()
894
+ env['GIT_LFS_SKIP_SMUDGE'] = '1' # LFS ํŒŒ์ผ ์Šคํ‚ต
895
+
896
+ clone_cmd = ['git', 'clone', hf_repo_url, tmp_dir]
897
+ result = subprocess.run(clone_cmd, env=env, capture_output=True, text=True, cwd=tempfile.gettempdir())
898
+
899
+ if result.returncode != 0:
900
+ raise Exception(f"Clone failed: {result.stderr}")
901
+
902
+ yield "โœ… HF Space cloned successfully"
903
+
904
+ # GitHub ์ธ์ฆ ๋ฐ ๋ ˆํฌ ์ƒ์„ฑ
905
+ yield "๐Ÿ” Authenticating with GitHub..."
906
+
907
+ # GitHub API ํ˜ธ์ถœ๋กœ ๋ ˆํฌ ์ƒ์„ฑ
908
+ headers = {
909
+ "Authorization": f"token {github_token}",
910
+ "Accept": "application/vnd.github.v3+json"
911
+ }
912
+
913
+ # ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
914
+ user_response = requests.get("https://api.github.com/user", headers=headers)
915
+ if user_response.status_code != 200:
916
+ raise Exception("GitHub authentication failed. Please check your token.")
917
+
918
+ actual_github_username = user_response.json()["login"]
919
+
920
+ # ์ž…๋ ฅ๋œ username๊ณผ ์‹ค์ œ username์ด ๋‹ค๋ฅธ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ
921
+ if github_username.lower() != actual_github_username.lower():
922
+ yield f"โš ๏ธ Note: Using authenticated user '{actual_github_username}' instead of '{github_username}'"
923
+ github_username = actual_github_username
924
+
925
+ yield f"โœ… Authenticated as GitHub user: {github_username}"
926
+
927
+ # ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์ƒ์„ฑ
928
+ yield f"๐Ÿ“ฆ Creating GitHub repository: {github_repo_name}..."
929
+ create_data = {
930
+ "name": github_repo_name,
931
+ "private": False,
932
+ "auto_init": False,
933
+ "description": f"Exported from HuggingFace Space: {hf_username}/{hf_space_name}"
934
+ }
935
+
936
+ create_response = requests.post(
937
+ "https://api.github.com/user/repos",
938
+ headers=headers,
939
+ json=create_data
940
+ )
941
+
942
+ if create_response.status_code == 201:
943
+ repo_info = create_response.json()
944
+ github_url = repo_info['html_url']
945
+ yield f"โœ… GitHub repository created: {github_url}"
946
+ elif create_response.status_code == 422:
947
+ # ๋ ˆํฌ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•จ
948
+ github_url = f"https://github.com/{github_username}/{github_repo_name}"
949
+ yield f"โ„น๏ธ Repository already exists: {github_url}"
950
+ else:
951
+ error_msg = create_response.json().get('message', 'Unknown error')
952
+ raise Exception(f"Failed to create repository: {error_msg}")
953
+
954
+ # Git remote ๋ณ€๊ฒฝ ๋ฐ ํ‘ธ์‹œ
955
+ yield "๐Ÿ“ค Pushing to GitHub..."
956
+
957
+ # git ๋ช…๋ น์–ด๋กœ remote ๋ณ€๊ฒฝ ๋ฐ ํ‘ธ์‹œ
958
+ os.chdir(tmp_dir)
959
+
960
+ # ๊ธฐ์กด origin ์ œ๊ฑฐ
961
+ subprocess.run(['git', 'remote', 'remove', 'origin'], capture_output=True)
962
+
963
+ # GitHub remote ์ถ”๊ฐ€
964
+ github_remote_url = f"https://{github_username}:{github_token}@github.com/{github_username}/{github_repo_name}.git"
965
+ subprocess.run(['git', 'remote', 'add', 'origin', github_remote_url], check=True)
966
+
967
+ # main ๋ธŒ๋žœ์น˜๋กœ ํ‘ธ์‹œ
968
+ push_result = subprocess.run(
969
+ ['git', 'push', '-u', 'origin', 'HEAD:main', '--force'],
970
+ capture_output=True,
971
+ text=True
972
+ )
973
+
974
+ if push_result.returncode != 0:
975
+ # master ๋ธŒ๋žœ์น˜๋กœ ์žฌ์‹œ๋„
976
+ push_result = subprocess.run(
977
+ ['git', 'push', '-u', 'origin', 'HEAD:master', '--force'],
978
+ capture_output=True,
979
+ text=True
980
+ )
981
+
982
+ if push_result.returncode != 0:
983
+ raise Exception(f"Push failed: {push_result.stderr}")
984
+
985
+ yield "โœ… Successfully pushed to GitHub!"
986
+
987
+ # ์ตœ์ข… ๊ฒฐ๊ณผ ์ถœ๋ ฅ - ํด๋ฆญ ๊ฐ€๋Šฅํ•œ ๋งํฌ
988
+ final_url = f"https://github.com/{github_username}/{github_repo_name}"
989
+ yield f"""
990
+ ๐ŸŽ‰ **Export Complete!**
991
+
992
+ ๐Ÿ“ฆ **GitHub Repository**: [{final_url}]({final_url})
993
+
994
+ ๐Ÿ“‹ **Details:**
995
+ - Source: HuggingFace Space `{hf_username}/{hf_space_name}`
996
+ - Destination: GitHub `{github_username}/{github_repo_name}`
997
+
998
+ ๐Ÿ”ง **Next Steps:**
999
+ ```bash
1000
+ git clone {final_url}.git
1001
+ cd {github_repo_name}
1002
+ ```
1003
+ """
1004
+
1005
+ except Exception as e:
1006
+ yield f"โŒ Error: {str(e)}"
1007
+ finally:
1008
+ # ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ •๋ฆฌ
1009
+ if tmp_dir and os.path.exists(tmp_dir):
1010
+ os.chdir(tempfile.gettempdir()) # ์•ˆ์ „ํ•œ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™
1011
+ try:
1012
+ shutil.rmtree(tmp_dir)
1013
+ except:
1014
+ pass # ์ •๋ฆฌ ์‹คํŒจํ•ด๋„ ๊ณ„์† ์ง„ํ–‰
1015
+
1016
+ # CSS styling (keeping the same)
1017
+ css = """
1018
+ /* Modern Professional UI */
1019
+ .container {
1020
+ max-width: 1200px !important;
1021
+ margin: auto;
1022
+ padding: 20px;
1023
+ }
1024
+
1025
+ /* Gradient backgrounds */
1026
+ .main-header {
1027
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1028
+ padding: 2rem;
1029
+ border-radius: 15px;
1030
+ margin-bottom: 2rem;
1031
+ color: white;
1032
+ text-align: center;
1033
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
1034
+ }
1035
+
1036
+ /* Tab styling */
1037
+ .tabs {
1038
+ box-shadow: 0 4px 6px rgba(0,0,0,0.07);
1039
+ border-radius: 12px;
1040
+ overflow: hidden;
1041
+ }
1042
+
1043
+ /* Card-like sections */
1044
+ .input-section {
1045
+ background: white;
1046
+ padding: 25px;
1047
+ border-radius: 12px;
1048
+ box-shadow: 0 2px 12px rgba(0,0,0,0.08);
1049
+ margin-bottom: 20px;
1050
+ border: 1px solid rgba(0,0,0,0.05);
1051
+ }
1052
+
1053
+ /* Output box styling */
1054
+ .output-box {
1055
+ min-height: 400px !important;
1056
+ max-height: 600px !important;
1057
+ overflow-y: auto !important;
1058
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace !important;
1059
+ font-size: 13px !important;
1060
+ line-height: 1.6 !important;
1061
+ background: linear-gradient(to bottom, #1e1e1e, #2d2d30) !important;
1062
+ color: #d4d4d4 !important;
1063
+ padding: 20px !important;
1064
+ border-radius: 10px !important;
1065
+ border: 1px solid rgba(255,255,255,0.1) !important;
1066
+ }
1067
+
1068
+ /* Custom button styling */
1069
+ .primary-btn {
1070
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
1071
+ color: white !important;
1072
+ border: none !important;
1073
+ padding: 12px 30px !important;
1074
+ font-size: 16px !important;
1075
+ font-weight: 600 !important;
1076
+ border-radius: 8px !important;
1077
+ cursor: pointer !important;
1078
+ transition: all 0.3s ease !important;
1079
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
1080
+ }
1081
+
1082
+ .primary-btn:hover {
1083
+ transform: translateY(-2px) !important;
1084
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;
1085
+ }
1086
+
1087
+ /* Input field styling */
1088
+ input[type="text"], input[type="password"], textarea {
1089
+ border: 2px solid #e2e8f0 !important;
1090
+ border-radius: 8px !important;
1091
+ padding: 10px 15px !important;
1092
+ font-size: 14px !important;
1093
+ transition: all 0.3s ease !important;
1094
+ background: #f8fafc !important;
1095
+ }
1096
+
1097
+ input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
1098
+ border-color: #667eea !important;
1099
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
1100
+ background: white !important;
1101
+ }
1102
+
1103
+ /* Radio button group styling */
1104
+ .radio-group {
1105
+ background: #f8fafc;
1106
+ padding: 15px;
1107
+ border-radius: 8px;
1108
+ border: 1px solid #e2e8f0;
1109
+ }
1110
+
1111
+ /* Checkbox styling */
1112
+ input[type="checkbox"] {
1113
+ width: 20px !important;
1114
+ height: 20px !important;
1115
+ cursor: pointer !important;
1116
+ }
1117
+
1118
+ /* Label styling */
1119
+ label {
1120
+ font-weight: 600 !important;
1121
+ color: #334155 !important;
1122
+ margin-bottom: 8px !important;
1123
+ display: block !important;
1124
+ font-size: 14px !important;
1125
+ }
1126
+
1127
+ /* Info text styling */
1128
+ .info-text {
1129
+ color: #64748b !important;
1130
+ font-size: 13px !important;
1131
+ margin-top: 5px !important;
1132
+ }
1133
+
1134
+ /* Scrollbar styling */
1135
+ .output-box::-webkit-scrollbar {
1136
+ width: 10px;
1137
+ }
1138
+
1139
+ .output-box::-webkit-scrollbar-track {
1140
+ background: #2d2d30;
1141
+ border-radius: 5px;
1142
+ }
1143
+
1144
+ .output-box::-webkit-scrollbar-thumb {
1145
+ background: #555;
1146
+ border-radius: 5px;
1147
+ }
1148
+
1149
+ .output-box::-webkit-scrollbar-thumb:hover {
1150
+ background: #666;
1151
+ }
1152
+
1153
+ /* Animation */
1154
+ @keyframes fadeIn {
1155
+ from { opacity: 0; transform: translateY(10px); }
1156
+ to { opacity: 1; transform: translateY(0); }
1157
+ }
1158
+
1159
+ .input-section {
1160
+ animation: fadeIn 0.5s ease;
1161
+ }
1162
+
1163
+ /* Responsive design */
1164
+ @media (max-width: 768px) {
1165
+ .container {
1166
+ padding: 10px;
1167
+ }
1168
+
1169
+ .main-header {
1170
+ padding: 1.5rem;
1171
+ }
1172
+
1173
+ .input-section {
1174
+ padding: 15px;
1175
+ }
1176
+ }
1177
+ """
1178
+
1179
+ # Gradio interface (remains the same structure)
1180
+ with gr.Blocks(css=css, theme=gr.themes.Soft(
1181
+ primary_hue="purple",
1182
+ secondary_hue="purple",
1183
+ neutral_hue="slate"
1184
+ )) as demo:
1185
+ gr.HTML("""
1186
+ <div class="main-header">
1187
+ <h1 style="font-size: 2.5rem; margin: 0; font-weight: 700;">
1188
+ ๐Ÿ”„ Repository Converter Pro
1189
+ </h1>
1190
+ <p style="font-size: 1.1rem; margin-top: 10px; opacity: 0.95;">
1191
+ Seamlessly transfer repositories between GitHub and HuggingFace
1192
+ </p>
1193
+ </div>
1194
+ """)
1195
+
1196
+ # OAuth ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ์ƒ๋‹จ์— ๋ฐฐ์น˜
1197
+ with gr.Row():
1198
+ with gr.Column(scale=4):
1199
+ gr.LoginButton()
1200
+ with gr.Column(scale=8):
1201
+ login_status = gr.HTML("""
1202
+ <div style="padding: 10px; background: #fef2f2; border: 1px solid #fecaca;
1203
+ border-radius: 8px; margin: 5px 0;">
1204
+ <span style="color: #dc2626;">โš ๏ธ Please login with HuggingFace to continue</span>
1205
+ </div>
1206
+ """)
1207
+
1208
+ with gr.Tabs(elem_classes="tabs") as main_tabs:
1209
+ with gr.Tab("๐Ÿš€ GitHub โ†’ HF Space", elem_id="tab1"):
1210
+ # ์ธ์ฆ ์˜ต์…˜ ์„น์…˜ ์ถ”๊ฐ€
1211
+ with gr.Group(elem_classes="input-section"):
1212
+ gr.Markdown("""
1213
+ ### ๐Ÿ” Authentication Options
1214
+ **Option 1**: Enter your HuggingFace token (Recommended)
1215
+ **Option 2**: Use OAuth login (may have limited permissions)
1216
+ """)
1217
+
1218
+ user_hf_token = gr.Textbox(
1219
+ label="HuggingFace Access Token (Optional but Recommended)",
1220
+ type="password",
1221
+ placeholder="hf_xxxxxxxxxxxxxxxxxxxxx",
1222
+ info="Get token from: https://huggingface.co/settings/tokens (needs 'write' role)"
1223
+ )
1224
+
1225
+ with gr.Row():
1226
+ with gr.Column(scale=5):
1227
+ with gr.Group(elem_classes="input-section"):
1228
+ gr.Markdown("### ๐Ÿ“ฆ Source Repository")
1229
+ repo_git = gr.Textbox(
1230
+ label="GitHub Repository URL",
1231
+ placeholder="https://github.com/username/repository",
1232
+ info="Enter the full GitHub repository URL"
1233
+ )
1234
+
1235
+ gr.Markdown("### ๐ŸŽฏ Destination Settings")
1236
+ repo_hf = gr.Textbox(
1237
+ label="HuggingFace Space Name",
1238
+ placeholder="my-awesome-space",
1239
+ info="Choose a unique name for your Space"
1240
+ )
1241
+
1242
+ sdk_choices = gr.Radio(
1243
+ ["gradio", "streamlit", "docker", "static"],
1244
+ label="Space SDK Type",
1245
+ value="gradio",
1246
+ elem_classes="radio-group"
1247
+ )
1248
+
1249
+ gr.Markdown("### โš™๏ธ Advanced Options")
1250
+ with gr.Row():
1251
+ skip_lfs = gr.Checkbox(
1252
+ label="Skip Large Files (LFS)",
1253
+ value=True,
1254
+ info="Recommended for faster deployment"
1255
+ )
1256
+ enable_smart_generation = gr.Checkbox(
1257
+ label="๐Ÿค– AI-Powered App Generation",
1258
+ value=False,
1259
+ info="Generate app.py automatically (requires FIREWORKS_API_KEY)"
1260
+ )
1261
+
1262
+ btn_to_hf = gr.Button("๐Ÿš€ Deploy to HuggingFace",
1263
+ variant="primary",
1264
+ elem_classes="primary-btn",
1265
+ size="lg")
1266
+
1267
+ with gr.Column(scale=7):
1268
+ with gr.Group(elem_classes="input-section"):
1269
+ gr.Markdown("### ๐Ÿ“Š Deployment Progress")
1270
+ output_to_hf = gr.Textbox(
1271
+ label="",
1272
+ lines=20,
1273
+ elem_classes="output-box",
1274
+ interactive=False,
1275
+ show_copy_button=True
1276
+ )
1277
+
1278
+ btn_to_hf.click(
1279
+ fn=clone,
1280
+ inputs=[repo_git, repo_hf, sdk_choices, skip_lfs, enable_smart_generation, user_hf_token],
1281
+ outputs=output_to_hf
1282
+ )
1283
+
1284
+ with gr.Tab("๐Ÿ“ค HF Space โ†’ GitHub", elem_id="tab2"):
1285
+ with gr.Row():
1286
+ with gr.Column(scale=5):
1287
+ with gr.Group(elem_classes="input-section"):
1288
+ gr.Markdown("### ๐ŸŽฏ Source Space")
1289
+ hf_username = gr.Textbox(
1290
+ label="HuggingFace Username",
1291
+ placeholder="your-hf-username",
1292
+ info="Your HuggingFace account name"
1293
+ )
1294
+ hf_space_name = gr.Textbox(
1295
+ label="Space Name",
1296
+ placeholder="your-space-name",
1297
+ info="Name of the Space to export"
1298
+ )
1299
+
1300
+ gr.Markdown("### ๐Ÿ“ฆ GitHub Destination")
1301
+ github_username = gr.Textbox(
1302
+ label="GitHub Username",
1303
+ placeholder="your-github-username",
1304
+ info="Your GitHub account name"
1305
+ )
1306
+ github_repo_name = gr.Textbox(
1307
+ label="Repository Name",
1308
+ placeholder="new-repo-name",
1309
+ info="Name for the new GitHub repository"
1310
+ )
1311
+
1312
+ gr.Markdown("### ๐Ÿ” Authentication")
1313
+ github_token = gr.Textbox(
1314
+ label="GitHub Personal Access Token",
1315
+ type="password",
1316
+ placeholder="ghp_xxxxxxxxxxxxxxxxxxxx",
1317
+ info="Generate at: github.com/settings/tokens (needs 'repo' scope)"
1318
+ )
1319
+
1320
+ btn_to_github = gr.Button("๐Ÿ“ค Export to GitHub",
1321
+ variant="primary",
1322
+ elem_classes="primary-btn",
1323
+ size="lg")
1324
+
1325
+ with gr.Column(scale=7):
1326
+ with gr.Group(elem_classes="input-section"):
1327
+ gr.Markdown("### ๐Ÿ“Š Export Progress")
1328
+ output_to_github = gr.Textbox(
1329
+ label="",
1330
+ lines=20,
1331
+ elem_classes="output-box",
1332
+ interactive=False,
1333
+ show_copy_button=True
1334
+ )
1335
+
1336
+ btn_to_github.click(
1337
+ fn=space_to_github,
1338
+ inputs=[hf_username, hf_space_name, github_username, github_repo_name, github_token],
1339
+ outputs=output_to_github
1340
+ )
1341
+
1342
+ if __name__ == "__main__":
1343
+ # OAuth ํ™˜๊ฒฝ ์„ค์ • ์ฒดํฌ
1344
+ if os.getenv("SPACE_ID"):
1345
+ print("Running on HuggingFace Spaces")
1346
+ print("OAuth callback URL: https://huggingface.co/spaces/" + os.getenv("SPACE_ID"))
1347
+
1348
+ demo.launch()