Spaces:
Build error
Build error
| # ========================================== | |
| # Stage 1: Download & cache the model weights | |
| # ========================================== | |
| FROM python:3.9-slim AS model-builder | |
| RUN pip install --no-cache-dir transformers torch | |
| # Pre-download the gpt2-mini model weights so the Space boots instantly | |
| RUN python -c " \ | |
| from transformers import AutoTokenizer, AutoModelForCausalLM; \ | |
| AutoTokenizer.from_pretrained('erwanf/gpt2-mini'); \ | |
| AutoModelForCausalLM.from_pretrained('erwanf/gpt2-mini') \ | |
| " | |
| # ========================================== | |
| # Stage 2: Final Runtime Environment | |
| # ========================================== | |
| FROM python:3.9-slim | |
| WORKDIR /app | |
| # Install system dependencies and Python packages | |
| RUN apt-get update && apt-get install -y --no-install-recommends \ | |
| curl \ | |
| && rm -rf /var/lib/apt/lists/* \ | |
| && pip install --no-cache-dir fastapi uvicorn transformers torch | |
| # Copy pre-downloaded model weights from the builder stage | |
| COPY --from=model-builder /root/.cache/huggingface /root/.cache/huggingface | |
| # Use Docker Heredoc to write main.py cleanly without shell escaping issues | |
| COPY <<EOF /app/main.py | |
| import os | |
| import torch | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.responses import HTMLResponse | |
| from pydantic import BaseModel | |
| from transformers import AutoTokenizer, AutoModelForCausalLM | |
| app = FastAPI() | |
| # Initialize tokenizer and model from local cache | |
| print("Loading gpt2-mini model into memory...") | |
| tokenizer = AutoTokenizer.from_pretrained("erwanf/gpt2-mini") | |
| model = AutoModelForCausalLM.from_pretrained("erwanf/gpt2-mini") | |
| model.eval() | |
| print("Model loaded successfully.") | |
| class GenerationRequest(BaseModel): | |
| prompt: str | |
| max_tokens: int = 128 | |
| temperature: float = 0.7 | |
| @app.post("/api/generate") | |
| async def generate_code(req: GenerationRequest): | |
| try: | |
| # Structure the prompt slightly to guide the raw text-generation model | |
| structured_prompt = f"# Python Script\n# Description: {req.prompt}\n\n" | |
| inputs = tokenizer.encode(structured_prompt, return_tensors="pt") | |
| with torch.no_grad(): | |
| outputs = model.generate( | |
| inputs, | |
| max_length=inputs.shape[1] + req.max_tokens, | |
| temperature=req.temperature, | |
| do_sample=True if req.temperature > 0.0 else False, | |
| pad_token_id=tokenizer.eos_token_id, | |
| no_repeat_ngram_size=3 | |
| ) | |
| generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True) | |
| # Clean up output to isolate code | |
| if generated_text.startswith(structured_prompt): | |
| generated_text = generated_text[len(structured_prompt):] | |
| return {"code": generated_text.strip()} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| @app.get("/", response_class=HTMLResponse) | |
| async def serve_ui(): | |
| return """<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>GPT2-Mini Python Workspace</title> | |
| <style> | |
| :root { | |
| --bg-main: #05060a; | |
| --bg-surface: #0d1117; | |
| --accent-cyan: #00f0ff; | |
| --accent-orange: #ff6b00; | |
| --text-muted: #8b949e; | |
| --text-light: #c9d1d9; | |
| --border-color: #1f242c; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| background-color: var(--bg-main); | |
| color: var(--text-light); | |
| font-family: "Courier New", Courier, monospace; | |
| display: flex; | |
| flex-direction: column; | |
| height: 100vh; | |
| overflow: hidden; | |
| } | |
| header { | |
| background-color: var(--bg-surface); | |
| border-bottom: 2px solid var(--accent-cyan); | |
| padding: 15px 20px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| box-shadow: 0 0 15px rgba(0, 240, 255, 0.15); | |
| } | |
| header h1 { | |
| font-size: 1.2rem; | |
| color: var(--accent-cyan); | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| text-shadow: 0 0 8px rgba(0, 240, 255, 0.5); | |
| } | |
| .status-badge { | |
| color: var(--accent-orange); | |
| font-size: 0.85rem; | |
| border: 1px solid var(--accent-orange); | |
| padding: 3px 8px; | |
| border-radius: 3px; | |
| text-shadow: 0 0 5px rgba(255, 107, 0, 0.4); | |
| } | |
| main { | |
| display: flex; | |
| flex: 1; | |
| overflow: hidden; | |
| } | |
| .control-panel { | |
| width: 350px; | |
| background-color: var(--bg-surface); | |
| border-right: 1px solid var(--border-color); | |
| display: flex; | |
| flex-direction: column; | |
| padding: 20px; | |
| gap: 20px; | |
| } | |
| .workspace-panel { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| background-color: var(--bg-main); | |
| } | |
| label { | |
| color: var(--accent-cyan); | |
| font-size: 0.85rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| textarea, input, select { | |
| background-color: var(--bg-main); | |
| border: 1px solid var(--border-color); | |
| color: var(--text-light); | |
| padding: 10px; | |
| font-family: inherit; | |
| font-size: 0.9rem; | |
| border-radius: 4px; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| } | |
| textarea:focus, input:focus, select:focus { | |
| border-color: var(--accent-cyan); | |
| } | |
| textarea#prompt-input { | |
| height: 120px; | |
| resize: none; | |
| } | |
| .param-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| button { | |
| background-color: transparent; | |
| border: 1px solid var(--accent-orange); | |
| color: var(--accent-orange); | |
| padding: 12px; | |
| font-family: inherit; | |
| font-weight: bold; | |
| text-transform: uppercase; | |
| cursor: pointer; | |
| letter-spacing: 1px; | |
| transition: all 0.2s; | |
| margin-top: auto; | |
| } | |
| button:hover { | |
| background-color: var(--accent-orange); | |
| color: var(--bg-main); | |
| box-shadow: 0 0 12px rgba(255, 107, 0, 0.6); | |
| } | |
| button:disabled { | |
| border-color: var(--text-muted); | |
| color: var(--text-muted); | |
| cursor: not-allowed; | |
| box-shadow: none; | |
| } | |
| .tab-bar { | |
| background-color: var(--bg-surface); | |
| display: flex; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .tab { | |
| padding: 12px 24px; | |
| cursor: pointer; | |
| border-right: 1px solid var(--border-color); | |
| font-size: 0.85rem; | |
| text-transform: uppercase; | |
| color: var(--text-muted); | |
| } | |
| .tab.active { | |
| background-color: var(--bg-main); | |
| color: var(--accent-cyan); | |
| border-bottom: 2px solid var(--accent-cyan); | |
| } | |
| .content-area { | |
| flex: 1; | |
| position: relative; | |
| overflow: auto; | |
| } | |
| .view-pane { | |
| display: none; | |
| width: 100%; | |
| height: 100%; | |
| padding: 20px; | |
| font-family: "Courier New", Courier, monospace; | |
| font-size: 0.95rem; | |
| white-space: pre-wrap; | |
| outline: none; | |
| border: none; | |
| background-color: transparent; | |
| color: var(--text-light); | |
| } | |
| .view-pane.active { display: block; } | |
| #preview-pane { | |
| background-color: #0b0d13; | |
| color: #a5d6ff; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>GPT2-CODE // WORKSPACE</h1> | |
| <div class="status-badge" id="app-status">ENGINE READY</div> | |
| </header> | |
| <main> | |
| <div class="control-panel"> | |
| <div class="param-group"> | |
| <label for="prompt-input">Target Objective</label> | |
| <textarea id="prompt-input" placeholder="e.g., function to parse json strings and extract keys..."></textarea> | |
| </div> | |
| <div class="param-group"> | |
| <label for="token-count">Max Generation Length</label> | |
| <input type="number" id="token-count" value="128" min="16" max="384"> | |
| </div> | |
| <div class="param-group"> | |
| <label for="temp-select">Inference Creativity</label> | |
| <select id="temp-select"> | |
| <option value="0.2">0.2 - Strict/Deterministic</option> | |
| <option value="0.6" selected>0.6 - Balanced Code</option> | |
| <option value="0.9">0.9 - Wild Speculation</option> | |
| </select> | |
| </div> | |
| <button id="generate-btn" onclick="triggerInference()">Execute Synthesis</button> | |
| </div> | |
| <div class="workspace-panel"> | |
| <div class="tab-bar"> | |
| <div class="tab active" id="tab-editor" onclick="switchTab('editor')">Source Output</div> | |
| <div class="tab" id="tab-preview" onclick="switchTab('preview')">Structure Preview</div> | |
| </div> | |
| <div class="content-area"> | |
| <textarea class="view-pane active" id="editor-pane" readonly placeholder="# Generated script architecture will compile here..."></textarea> | |
| <div class="view-pane" id="preview-pane"></div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| function switchTab(type) { | |
| document.querySelectorAll(".tab").forEach(t => t.classList.remove("active")); | |
| document.querySelectorAll(".view-pane").forEach(p => p.classList.remove("active")); | |
| if (type === "editor") { | |
| document.getElementById("tab-editor").classList.add("active"); | |
| document.getElementById("editor-pane").classList.add("active"); | |
| } else { | |
| document.getElementById("tab-preview").classList.add("active"); | |
| document.getElementById("preview-pane").classList.add("active"); | |
| renderPreview(); | |
| } | |
| } | |
| function renderPreview() { | |
| const rawCode = document.getElementById("editor-pane").value; | |
| const previewElement = document.getElementById("preview-pane"); | |
| if (!rawCode.trim()) { | |
| previewElement.innerHTML = `<span style="color:var(--text-muted)">[No raw structures to analyze]</span>`; | |
| return; | |
| } | |
| const lines = rawCode.split("\\n"); | |
| let structuralMap = lines.map((line) => { | |
| const clean = line.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); | |
| if (line.trim().startsWith("def ") || line.trim().startsWith("class ")) { | |
| return `<span style="color: var(--accent-cyan); font-weight: bold;">\${clean}</span>`; | |
| } else if (line.trim().startsWith("#")) { | |
| return `<span style="color: var(--text-muted); font-style: italic;">\${clean}</span>`; | |
| } else if (line.trim().startsWith("import ") || line.trim().startsWith("from ")) { | |
| return `<span style="color: var(--accent-orange);">\${clean}</span>`; | |
| } | |
| return clean; | |
| }).join("\\n"); | |
| previewElement.innerHTML = `<h4>[PARSED AST BLUEPRINT]</h4><br>\${structuralMap}`; | |
| } | |
| async function triggerInference() { | |
| const promptInput = document.getElementById("prompt-input").value; | |
| const tokenCount = document.getElementById("token-count").value; | |
| const temperature = document.getElementById("temp-select").value; | |
| const btn = document.getElementById("generate-btn"); | |
| const status = document.getElementById("app-status"); | |
| if (!promptInput.trim()) return; | |
| btn.disabled = true; | |
| btn.innerText = "SYNTHESIZING..."; | |
| status.innerText = "COMPUTING ENGINE PATHS"; | |
| status.style.color = "var(--accent-cyan)"; | |
| status.style.borderColor = "var(--accent-cyan)"; | |
| try { | |
| const res = await fetch("/api/generate", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| prompt: promptInput, | |
| max_tokens: parseInt(tokenCount, 10), | |
| temperature: parseFloat(temperature) | |
| }) | |
| }); | |
| if (!res.ok) throw new Error("Inference pipeline failure."); | |
| const data = await res.json(); | |
| document.getElementById("editor-pane").value = data.code; | |
| renderPreview(); | |
| } catch(err) { | |
| document.getElementById("editor-pane").value = `# Pipeline Exception:\\n\${err.message}`; | |
| } finally { | |
| btn.disabled = false; | |
| btn.innerText = "Execute Synthesis"; | |
| status.innerText = "ENGINE READY"; | |
| status.style.color = "var(--accent-orange)"; | |
| status.style.borderColor = "var(--accent-orange)"; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| EOF | |
| # Expose default Hugging Face space port 7860 | |
| EXPOSE 7860 | |
| # Run with Uvicorn, bound to all network interfaces on port 7860 | |
| CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"] |