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:;base64,} Saves files into /tmp/llm_attachments/ 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""" Fallback App

Hello (fallback)

This app was generated as a fallback because OpenAI failed. Brief: {brief}

---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}