Spaces:
Sleeping
Sleeping
File size: 5,810 Bytes
f1080fa 069a92b f1080fa 069a92b f1080fa d597cd8 f1080fa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | import os
import base64
import mimetypes
from pathlib import Path
from datetime import datetime
# from dotenv import load_dotenv
from openai import OpenAI
# load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_BASE_URL)
TMP_DIR = Path("/tmp/llm_attachments")
TMP_DIR.mkdir(parents=True, exist_ok=True)
def decode_attachments(attachments):
"""
attachments: list of {name, url: data:<mime>;base64,<b64>}
Saves files into /tmp/llm_attachments/<name>
Returns list of dicts: {"name": name, "path": "/tmp/..", "mime": mime, "size": n}
"""
saved = []
for att in attachments or []:
name = att.get("name") or "attachment"
url = att.get("url", "")
if not url.startswith("data:"):
continue
try:
header, b64data = url.split(",", 1)
mime = header.split(";")[0].replace("data:", "")
data = base64.b64decode(b64data)
path = TMP_DIR / name
with open(path, "wb") as f:
f.write(data)
saved.append({
"name": name,
"path": str(path),
"mime": mime,
"size": len(data)
})
except Exception as e:
print("Failed to decode attachment", name, e)
return saved
def summarize_attachment_meta(saved):
"""
saved is list from decode_attachments.
Returns a short human-readable summary string for the prompt.
"""
summaries = []
for s in saved:
nm = s["name"]
p = s["path"]
mime = s.get("mime", "")
try:
if mime.startswith("text") or nm.endswith((".md", ".txt", ".json", ".csv")):
with open(p, "r", encoding="utf-8", errors="ignore") as f:
if nm.endswith(".csv"):
lines = [next(f).strip() for _ in range(3)]
preview = "\\n".join(lines)
else:
data = f.read(1000)
preview = data.replace("\n", "\\n")[:1000]
summaries.append(f"- {nm} ({mime}): preview: {preview}")
else:
summaries.append(f"- {nm} ({mime}): {s['size']} bytes")
except Exception as e:
summaries.append(f"- {nm} ({mime}): (could not read preview: {e})")
return "\\n".join(summaries)
def _strip_code_block(text: str) -> str:
"""
If text is inside triple-backticks, return inner contents. Otherwise return text as-is.
"""
if "```" in text:
parts = text.split("```")
if len(parts) >= 2:
return parts[1].strip()
return text.strip()
def generate_readme_fallback(brief: str, checks=None, attachments_meta=None, round_num=1):
checks_text = "\\n".join(checks or [])
att_text = attachments_meta or ""
return f"""# Auto-generated README (Round {round_num})
**Project brief:** {brief}
**Attachments:**
{att_text}
**Checks to meet:**
{checks_text}
## Setup
1. Open `index.html` in a browser.
2. No build steps required.
## Notes
This README was generated as a fallback (OpenAI did not return an explicit README).
"""
def generate_app_code(brief: str, attachments=None, checks=None, round_num=1, prev_readme=None):
"""
Generate or revise an app using the OpenAI Responses API.
- round_num=1: build from scratch
- round_num=2: refactor based on new brief and previous README/code
"""
saved = decode_attachments(attachments or [])
attachments_meta = summarize_attachment_meta(saved)
context_note = ""
if round_num == 2 and prev_readme:
context_note = f"\n### Previous README.md:\n{prev_readme}\n\nRevise and enhance this project according to the new brief below.\n"
user_prompt = f"""
You are a professional web developer assistant.
### Round
{round_num}
### Task
{brief}
{context_note}
### Attachments (if any)
{attachments_meta}
### Evaluation checks
{checks or []}
### Output format rules:
1. Produce a complete web app (HTML/JS/CSS inline if needed) satisfying the brief.
2. Output must contain **two parts only**:
- index.html (main code)
- README.md (starts after a line containing exactly: ---README.md---)
3. README.md must include:
- Overview
- Setup
- Usage
- If Round 2, describe improvements made from previous version.
4. Do not include any commentary outside code or README.
"""
try:
response = client.responses.create(
model="gpt-5",
input=[
{"role": "system", "content": "You are a helpful coding assistant that outputs runnable web apps."},
{"role": "user", "content": user_prompt}
]
)
text = response.output_text or ""
print("✅ Generated code using new OpenAI Responses API.")
except Exception as e:
print("⚠ OpenAI API failed, using fallback HTML instead:", e)
text = f"""
<html>
<head><title>Fallback App</title></head>
<body>
<h1>Hello (fallback)</h1>
<p>This app was generated as a fallback because OpenAI failed. Brief: {brief}</p>
</body>
</html>
---README.md---
{generate_readme_fallback(brief, checks, attachments_meta, round_num)}
"""
if "---README.md---" in text:
code_part, readme_part = text.split("---README.md---", 1)
code_part = _strip_code_block(code_part)
readme_part = _strip_code_block(readme_part)
else:
code_part = _strip_code_block(text)
readme_part = generate_readme_fallback(brief, checks, attachments_meta, round_num)
files = {"index.html": code_part, "README.md": readme_part}
return {"files": files, "attachments": saved} |