Skip to content

Commit f7b4564

Browse files
committed
add webapp (now this is cheating)
1 parent 85d542c commit f7b4564

File tree

3 files changed

+353
-1
lines changed

3 files changed

+353
-1
lines changed

app.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""Minimal FastAPI app for GitHub URL to Dockerfile generator."""
2+
3+
import asyncio
4+
from fastapi import FastAPI, Request, Form
5+
from fastapi.responses import HTMLResponse
6+
from fastapi.staticfiles import StaticFiles
7+
from fastapi.templating import Jinja2Templates
8+
from pathlib import Path
9+
from tools import gitingest_tool, clone_repo_tool, create_container_tool
10+
11+
# Initialize FastAPI app
12+
app = FastAPI(title="GitHub to Dockerfile Generator")
13+
14+
# Setup templates
15+
templates = Jinja2Templates(directory="templates")
16+
17+
# Mount static files (we'll create this directory)
18+
static_dir = Path("static")
19+
static_dir.mkdir(exist_ok=True)
20+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
21+
22+
23+
@app.get("/", response_class=HTMLResponse)
24+
async def home(request: Request):
25+
"""Home page with the input form."""
26+
return templates.TemplateResponse("index.html", {
27+
"request": request,
28+
"repo_url": "",
29+
"loading": False,
30+
"result": None,
31+
"error": None
32+
})
33+
34+
35+
@app.post("/", response_class=HTMLResponse)
36+
async def generate_dockerfile(request: Request, repo_url: str = Form(...)):
37+
"""Process GitHub URL and generate Dockerfile."""
38+
39+
# Initial response with loading state
40+
context = {
41+
"request": request,
42+
"repo_url": repo_url,
43+
"loading": True,
44+
"result": None,
45+
"error": None
46+
}
47+
48+
try:
49+
# Step 1: Clone repository
50+
print(f"Cloning repository: {repo_url}")
51+
clone_result = await clone_repo_tool(repo_url)
52+
53+
if not clone_result["success"]:
54+
context["loading"] = False
55+
context["error"] = f"Failed to clone repository: {clone_result['error']}"
56+
return templates.TemplateResponse("index.html", context)
57+
58+
# Step 2: Analyze with gitingest
59+
print(f"Analyzing repository...")
60+
ingest_result = await gitingest_tool(clone_result['local_path'])
61+
62+
if not ingest_result["success"]:
63+
context["loading"] = False
64+
context["error"] = f"Failed to analyze repository: {ingest_result['error']}"
65+
return templates.TemplateResponse("index.html", context)
66+
67+
# Step 3: Generate Dockerfile
68+
print(f"Generating Dockerfile...")
69+
container_result = await create_container_tool(
70+
gitingest_summary=ingest_result['summary'],
71+
gitingest_tree=ingest_result['tree'],
72+
gitingest_content=ingest_result['content'],
73+
project_name=clone_result['repo_name']
74+
)
75+
76+
if not container_result["success"]:
77+
context["loading"] = False
78+
context["error"] = f"Failed to generate Dockerfile: {container_result['error']}"
79+
return templates.TemplateResponse("index.html", context)
80+
81+
# Success! Prepare result
82+
context["loading"] = False
83+
context["result"] = {
84+
"project_name": container_result['project_name'],
85+
"technology_stack": container_result['technology_stack'],
86+
"dockerfile": container_result['dockerfile'],
87+
"docker_compose": container_result.get('docker_compose_suggestion', ''),
88+
"reasoning": container_result.get('base_image_reasoning', ''),
89+
"additional_notes": container_result.get('additional_notes', ''),
90+
"repo_info": {
91+
"name": clone_result['repo_name'],
92+
"size_mb": clone_result['repo_size_mb'],
93+
"file_count": clone_result['file_count']
94+
}
95+
}
96+
97+
except Exception as e:
98+
context["loading"] = False
99+
context["error"] = f"Unexpected error: {str(e)}"
100+
101+
return templates.TemplateResponse("index.html", context)
102+
103+
104+
@app.get("/health")
105+
async def health_check():
106+
"""Health check endpoint."""
107+
return {"status": "healthy"}
108+
109+
110+
if __name__ == "__main__":
111+
import uvicorn
112+
uvicorn.run(app, host="0.0.0.0", port=8000)

requirements.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
openai-agents
22
python-dotenv
3-
gitingest
3+
gitingest
4+
fastapi
5+
uvicorn[standard]
6+
jinja2
7+
python-multipart

templates/index.html

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>GitHub to Dockerfile Generator</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
8+
<style>
9+
.loader {
10+
border: 8px solid #fff4da;
11+
border-top: 8px solid #ffc480;
12+
border-radius: 50%;
13+
width: 64px;
14+
height: 64px;
15+
animation: spin 1s linear infinite;
16+
}
17+
@keyframes spin {
18+
0% { transform: rotate(0deg); }
19+
100% { transform: rotate(360deg); }
20+
}
21+
</style>
22+
</head>
23+
<body class="bg-[#FFFDF8] min-h-screen">
24+
<!-- Header -->
25+
<header class="border-b-[3px] border-gray-900">
26+
<div class="max-w-4xl mx-auto px-4 py-4">
27+
<h1 class="text-3xl font-bold tracking-tight">
28+
<span class="text-gray-900">GitHub</span>
29+
<span class="text-[#FE4A60]">2</span>
30+
<span class="text-[#5CF1A4]">Dockerfile</span>
31+
</h1>
32+
</div>
33+
</header>
34+
35+
<!-- Main Content -->
36+
<main class="max-w-4xl mx-auto px-4 py-8">
37+
<!-- Hero Section -->
38+
<div class="mb-8 text-center">
39+
<h2 class="text-4xl md:text-6xl font-bold tracking-tighter mb-4">
40+
Generate Dockerfiles
41+
<br>
42+
from GitHub repos
43+
</h2>
44+
<p class="text-gray-600 text-lg max-w-2xl mx-auto">
45+
Paste any GitHub repository URL and get an AI-generated Dockerfile
46+
tailored to your project's technology stack.
47+
</p>
48+
</div>
49+
50+
<!-- Error Message -->
51+
{% if error %}
52+
<div class="mb-6 p-4 bg-red-50 border-[3px] border-red-500 rounded-lg text-red-700">
53+
{{ error }}
54+
</div>
55+
{% endif %}
56+
57+
<!-- Input Form -->
58+
<div class="relative mb-8">
59+
<div class="w-full h-full absolute inset-0 bg-gray-900 rounded-xl translate-y-2 translate-x-2"></div>
60+
<div class="rounded-xl relative z-20 p-8 border-[3px] border-gray-900 bg-[#fff4da]">
61+
<form method="post" class="flex flex-col md:flex-row gap-4">
62+
<div class="relative flex-1">
63+
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0 z-10"></div>
64+
<input
65+
type="url"
66+
name="repo_url"
67+
value="{{ repo_url }}"
68+
placeholder="https://github.com/username/repository"
69+
required
70+
class="border-[3px] w-full relative z-20 border-gray-900 placeholder-gray-600 text-lg font-medium focus:outline-none py-3.5 px-6 rounded"
71+
>
72+
</div>
73+
<div class="relative group">
74+
<div class="w-full h-full rounded bg-gray-800 translate-y-1 translate-x-1 absolute inset-0 z-10"></div>
75+
<button
76+
type="submit"
77+
class="py-3.5 rounded px-8 group-hover:-translate-y-px group-hover:-translate-x-px ease-out duration-300 z-20 relative w-full md:w-auto border-[3px] border-gray-900 font-medium bg-[#ffc480] tracking-wide text-lg text-gray-900"
78+
>
79+
Generate Dockerfile
80+
</button>
81+
</div>
82+
</form>
83+
</div>
84+
</div>
85+
86+
<!-- Loading State -->
87+
{% if loading %}
88+
<div class="relative">
89+
<div class="w-full h-full absolute inset-0 bg-black rounded-xl translate-y-2 translate-x-2"></div>
90+
<div class="bg-[#fafafa] rounded-xl border-[3px] border-gray-900 p-8 relative z-20 flex flex-col items-center space-y-4">
91+
<div class="loader"></div>
92+
<p class="text-lg font-bold text-gray-900">Analyzing repository and generating Dockerfile...</p>
93+
<p class="text-sm text-gray-600">This may take a few moments</p>
94+
</div>
95+
</div>
96+
{% endif %}
97+
98+
<!-- Results -->
99+
{% if result %}
100+
<div class="relative">
101+
<div class="w-full h-full absolute inset-0 bg-gray-900 rounded-xl translate-y-2 translate-x-2"></div>
102+
<div class="bg-[#fafafa] rounded-xl border-[3px] border-gray-900 p-6 relative z-20 space-y-6">
103+
104+
<!-- Project Info -->
105+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
106+
<div class="bg-[#fff4da] border-[3px] border-gray-900 rounded p-4">
107+
<h3 class="font-bold text-gray-900">Project</h3>
108+
<p class="text-sm">{{ result.project_name }}</p>
109+
</div>
110+
<div class="bg-[#fff4da] border-[3px] border-gray-900 rounded p-4">
111+
<h3 class="font-bold text-gray-900">Technology Stack</h3>
112+
<p class="text-sm">{{ result.technology_stack }}</p>
113+
</div>
114+
<div class="bg-[#fff4da] border-[3px] border-gray-900 rounded p-4">
115+
<h3 class="font-bold text-gray-900">Repository Size</h3>
116+
<p class="text-sm">{{ result.repo_info.file_count }} files ({{ result.repo_info.size_mb }} MB)</p>
117+
</div>
118+
</div>
119+
120+
<!-- Dockerfile -->
121+
<div>
122+
<div class="flex justify-between items-center mb-4">
123+
<h3 class="text-xl font-bold text-gray-900">Generated Dockerfile</h3>
124+
<div class="relative group">
125+
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0"></div>
126+
<button
127+
onclick="copyToClipboard('dockerfile-content')"
128+
class="px-4 py-2 bg-[#ffc480] border-[3px] border-gray-900 text-gray-900 rounded group-hover:-translate-y-px group-hover:-translate-x-px transition-transform relative z-10 flex items-center gap-2"
129+
>
130+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
131+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
132+
</svg>
133+
Copy Dockerfile
134+
</button>
135+
</div>
136+
</div>
137+
<div class="relative">
138+
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0"></div>
139+
<textarea
140+
id="dockerfile-content"
141+
class="w-full p-4 bg-[#fff4da] border-[3px] border-gray-900 rounded font-mono text-sm focus:outline-none relative z-10 h-96"
142+
readonly
143+
>{{ result.dockerfile }}</textarea>
144+
</div>
145+
</div>
146+
147+
<!-- Docker Compose (if available) -->
148+
{% if result.docker_compose %}
149+
<div>
150+
<div class="flex justify-between items-center mb-4">
151+
<h3 class="text-xl font-bold text-gray-900">Suggested docker-compose.yml</h3>
152+
<div class="relative group">
153+
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0"></div>
154+
<button
155+
onclick="copyToClipboard('docker-compose-content')"
156+
class="px-4 py-2 bg-[#ffc480] border-[3px] border-gray-900 text-gray-900 rounded group-hover:-translate-y-px group-hover:-translate-x-px transition-transform relative z-10 flex items-center gap-2"
157+
>
158+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
159+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
160+
</svg>
161+
Copy Compose
162+
</button>
163+
</div>
164+
</div>
165+
<div class="relative">
166+
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0"></div>
167+
<textarea
168+
id="docker-compose-content"
169+
class="w-full p-4 bg-[#fff4da] border-[3px] border-gray-900 rounded font-mono text-sm focus:outline-none relative z-10 h-48"
170+
readonly
171+
>{{ result.docker_compose }}</textarea>
172+
</div>
173+
</div>
174+
{% endif %}
175+
176+
<!-- Reasoning & Notes -->
177+
{% if result.reasoning or result.additional_notes %}
178+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
179+
{% if result.reasoning %}
180+
<div>
181+
<h3 class="text-lg font-bold text-gray-900 mb-2">Base Image Reasoning</h3>
182+
<div class="bg-[#fff4da] border-[3px] border-gray-900 rounded p-4">
183+
<p class="text-sm">{{ result.reasoning }}</p>
184+
</div>
185+
</div>
186+
{% endif %}
187+
188+
{% if result.additional_notes %}
189+
<div>
190+
<h3 class="text-lg font-bold text-gray-900 mb-2">Additional Notes</h3>
191+
<div class="bg-[#fff4da] border-[3px] border-gray-900 rounded p-4">
192+
<p class="text-sm whitespace-pre-wrap">{{ result.additional_notes }}</p>
193+
</div>
194+
</div>
195+
{% endif %}
196+
</div>
197+
{% endif %}
198+
199+
</div>
200+
</div>
201+
{% endif %}
202+
</main>
203+
204+
<!-- Footer -->
205+
<footer class="border-t-[3px] border-gray-900 mt-auto">
206+
<div class="max-w-4xl mx-auto px-4 py-4 text-center">
207+
<p class="text-gray-600 text-sm">
208+
Generate Dockerfiles from GitHub repositories using AI
209+
</p>
210+
</div>
211+
</footer>
212+
213+
<script>
214+
async function copyToClipboard(elementId) {
215+
const element = document.getElementById(elementId);
216+
const text = element.value || element.textContent;
217+
218+
try {
219+
await navigator.clipboard.writeText(text);
220+
// Brief visual feedback
221+
const button = event.target.closest('button');
222+
const originalText = button.innerHTML;
223+
button.innerHTML = '✓ Copied!';
224+
setTimeout(() => {
225+
button.innerHTML = originalText;
226+
}, 2000);
227+
} catch (err) {
228+
console.error('Failed to copy: ', err);
229+
// Fallback for older browsers
230+
element.select();
231+
document.execCommand('copy');
232+
}
233+
}
234+
</script>
235+
</body>
236+
</html>

0 commit comments

Comments
 (0)