autoapp-builder / app /codegen /repo_generator.py
ruslanmv's picture
feat: complete AutoApp Builder - AI-powered HF Space generator
2c304fc verified
"""
Orchestrate full repository generation by delegating to SDK-specific generators.
"""
from app.codegen.gradio_generator import GradioGenerator
from app.codegen.docker_generator import DockerGenerator
from app.codegen.readme_generator import ReadmeGenerator
STATIC_TEMPLATE = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: #0f172a;
color: #e2e8f0;
min-height: 100vh;
}}
.hero {{
min-height: 60vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 2rem;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
}}
.hero h1 {{
font-size: 3.5rem;
font-weight: 800;
background: linear-gradient(135deg, #38bdf8, #818cf8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 1rem;
}}
.hero p {{
font-size: 1.25rem;
color: #94a3b8;
max-width: 600px;
line-height: 1.8;
}}
.section {{
max-width: 1000px;
margin: 0 auto;
padding: 4rem 2rem;
}}
.section h2 {{
font-size: 2rem;
margin-bottom: 2rem;
color: #38bdf8;
}}
.grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}}
.card {{
background: rgba(30, 41, 59, 0.8);
border: 1px solid rgba(56, 189, 248, 0.1);
border-radius: 12px;
padding: 1.5rem;
transition: transform 0.2s, border-color 0.2s;
}}
.card:hover {{
transform: translateY(-4px);
border-color: rgba(56, 189, 248, 0.4);
}}
.card h3 {{ color: #f1f5f9; margin-bottom: 0.5rem; }}
.card p {{ color: #94a3b8; line-height: 1.6; }}
.skills {{
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}}
.skill-tag {{
background: rgba(56, 189, 248, 0.15);
color: #38bdf8;
padding: 0.5rem 1rem;
border-radius: 9999px;
font-size: 0.875rem;
}}
.contact {{
background: rgba(30, 41, 59, 0.6);
border-radius: 16px;
padding: 2rem;
text-align: center;
}}
.contact a {{
display: inline-block;
margin: 0.5rem;
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #38bdf8, #818cf8);
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
}}
footer {{
text-align: center;
padding: 2rem;
color: #475569;
font-size: 0.875rem;
}}
</style>
</head>
<body>
<div class="hero">
<h1>{title}</h1>
<p>{description}</p>
</div>
<div class="section">
<h2>Projects</h2>
<div class="grid">
<div class="card">
<h3>Project Alpha</h3>
<p>A cutting-edge machine learning project that pushes the boundaries of what's possible.</p>
</div>
<div class="card">
<h3>Project Beta</h3>
<p>An innovative data pipeline that processes millions of records in real-time.</p>
</div>
<div class="card">
<h3>Project Gamma</h3>
<p>A beautiful visualization dashboard that makes complex data accessible.</p>
</div>
</div>
</div>
<div class="section">
<h2>Skills</h2>
<div class="skills">
<span class="skill-tag">Python</span>
<span class="skill-tag">Machine Learning</span>
<span class="skill-tag">Deep Learning</span>
<span class="skill-tag">NLP</span>
<span class="skill-tag">Computer Vision</span>
<span class="skill-tag">Data Science</span>
<span class="skill-tag">FastAPI</span>
<span class="skill-tag">Docker</span>
</div>
</div>
<div class="section">
<div class="contact">
<h2>Get in Touch</h2>
<p style="color: #94a3b8; margin: 1rem 0;">Interested in collaborating? Reach out!</p>
<a href="mailto:hello@example.com">Email</a>
<a href="https://github.com">GitHub</a>
<a href="https://linkedin.com">LinkedIn</a>
</div>
</div>
<footer>
<p>Built with care. Hosted on Hugging Face Spaces.</p>
</footer>
</body>
</html>
"""
class RepoGenerator:
"""Orchestrate full repo generation for any SDK type."""
def __init__(self):
self.gradio_gen = GradioGenerator()
self.docker_gen = DockerGenerator()
self.readme_gen = ReadmeGenerator()
def generate(self, plan: dict, prompt: str) -> dict:
"""
Generate all files for a complete HF Space repo.
Returns dict of {filepath: content}.
"""
sdk = plan.get("sdk", "gradio")
if sdk == "gradio":
return self._generate_gradio_repo(plan, prompt)
elif sdk == "docker":
return self._generate_docker_repo(plan, prompt)
else:
return self._generate_static_repo(plan, prompt)
def _generate_gradio_repo(self, plan: dict, prompt: str) -> dict:
"""Generate a complete Gradio Space repo."""
files = {}
# README.md
files["README.md"] = self.readme_gen.generate(plan, "gradio")
# app.py - the main Gradio application
files["app.py"] = self.gradio_gen.generate(plan, prompt)
# requirements.txt
files["requirements.txt"] = self._gradio_requirements(plan)
# .gitignore
files[".gitignore"] = self._gitignore()
return files
def _generate_docker_repo(self, plan: dict, prompt: str) -> dict:
"""Generate a complete Docker Space repo."""
# Get docker-specific files from the generator
docker_files = self.docker_gen.generate(plan, prompt)
files = {}
# README.md (always generate our own)
files["README.md"] = self.readme_gen.generate(plan, "docker")
# Merge docker-generated files
for name, content in docker_files.items():
files[name] = content
# .gitignore
files[".gitignore"] = self._gitignore()
return files
def _generate_static_repo(self, plan: dict, prompt: str) -> dict:
"""Generate a complete Static Space repo."""
title = plan.get("title", "My Site")
description = plan.get("description", "A static website")
files = {}
# README.md
files["README.md"] = self.readme_gen.generate(plan, "static")
# index.html
files["index.html"] = STATIC_TEMPLATE.format(
title=title,
description=description,
)
# style.css (additional styles)
files["style.css"] = """/* Additional custom styles */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
body {
font-family: 'Inter', system-ui, sans-serif;
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Animated gradient background */
@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.hero {
background-size: 200% 200%;
animation: gradient-shift 8s ease infinite;
}
/* Fade-in animation */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeInUp 0.5s ease forwards;
}
"""
files[".gitignore"] = self._gitignore()
return files
def _gradio_requirements(self, plan: dict) -> str:
"""Generate requirements.txt for Gradio apps."""
deps = [
"gradio>=5.9.1",
"huggingface-hub>=0.27.1",
]
# Add task-specific dependencies
task = plan.get("model_task", "")
if task in ("text-to-image", "image-classification", "object-detection"):
deps.append("Pillow>=10.0.0")
if "chart_output" in plan.get("components", []):
deps.append("matplotlib>=3.8.0")
deps.append("numpy>=1.26.0")
return "\n".join(deps) + "\n"
def _gitignore(self) -> str:
return """__pycache__/
*.py[cod]
*$py.class
*.egg-info/
dist/
build/
.env
.venv/
venv/
.DS_Store
*.log
"""
def edit(self, plan: dict, current_files: dict, edit_prompt: str) -> dict:
"""Edit an existing repo based on user instructions."""
sdk = plan.get("sdk", "gradio")
updated = dict(current_files)
if sdk == "gradio" and "app.py" in current_files:
updated["app.py"] = self.gradio_gen.edit(plan, current_files["app.py"], edit_prompt)
elif sdk == "docker":
docker_updated = self.docker_gen.edit(plan, current_files, edit_prompt)
updated.update(docker_updated)
# Static sites: for now return unchanged (could add LLM-based HTML editing)
return updated