Spaces:
Sleeping
Sleeping
| import os, re, io, zipfile, tempfile, time | |
| import gradio as gr | |
| from anthropic import Anthropic | |
| # -------------------- CONFIG -------------------- | |
| MODEL_ID = "claude-sonnet-4-5-20250929" | |
| SYSTEM_PROMPT = ( | |
| "You are an expert full-stack developer. When the user asks for an app or site, " | |
| "return complete production-ready code artifacts in Markdown code fences labeled " | |
| "```html```, ```css```, and ```js```. Always include index.html, optionally styles.css and app.js. " | |
| "Generate beautiful, functional, responsive designs using pure HTML, CSS, and JS." | |
| ) | |
| # -------------------- HELPERS -------------------- | |
| def parse_artifacts(text: str): | |
| files = {} | |
| blocks = re.findall(r"```(html|css|js)\s+([\s\S]*?)```", text, re.I) | |
| for lang, code in blocks: | |
| name = {"html": "index.html", "css": "styles.css", "js": "app.js"}[lang.lower()] | |
| if name in files: | |
| base, ext = name.split(".") | |
| n = 2 | |
| while f"{base}{n}.{ext}" in files: | |
| n += 1 | |
| name = f"{base}{n}.{ext}" | |
| files[name] = code.strip() | |
| if not files: | |
| esc = gr.utils.escape_html(text) | |
| files["index.html"] = f"<!doctype html><meta charset='utf-8'><title>Artifact</title><pre>{esc}</pre>" | |
| if "index.html" not in files: | |
| files["index.html"] = "<!doctype html><meta charset='utf-8'><title>Artifact</title><h1>Artifact</h1>" | |
| return files | |
| def render_srcdoc(files: dict): | |
| html = files.get("index.html", "") | |
| css = files.get("styles.css", "") | |
| js = files.get("app.js", "") | |
| # Inline CSS + JS for sandbox preview | |
| if "</head>" in html: | |
| html = html.replace("</head>", f"<style>\n{css}\n</style></head>") | |
| else: | |
| html = f"<!doctype html><head><meta charset='utf-8'><style>{css}</style></head>{html}" | |
| if "</body>" in html: | |
| html = html.replace("</body>", f"<script>\n{js}\n</script></body>") | |
| else: | |
| html = f"{html}<script>{js}</script>" | |
| return html | |
| def make_zip_path(files: dict): | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".zip") | |
| with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as z: | |
| for name, code in files.items(): | |
| z.writestr(name, code) | |
| tmp.flush() | |
| return tmp.name | |
| def call_claude(api_key: str, prompt: str): | |
| client = Anthropic(api_key=api_key) | |
| t0 = time.time() | |
| resp = client.messages.create( | |
| model=MODEL_ID, | |
| max_tokens=4000, | |
| temperature=0.45, | |
| system=SYSTEM_PROMPT, | |
| messages=[{"role": "user", "content": prompt}], | |
| timeout=120, | |
| ) | |
| latency = int((time.time() - t0) * 1000) | |
| content = "".join(getattr(c, "text", "") for c in resp.content) | |
| files = parse_artifacts(content) | |
| return files, content, latency | |
| # -------------------- UI -------------------- | |
| with gr.Blocks(fill_height=True, theme=gr.themes.Soft()) as demo: | |
| gr.Markdown( | |
| "# 🌐 ZEN Artifact Builder — Claude Sonnet 4.5\n" | |
| "Describe any app or website and see it appear live below." | |
| ) | |
| with gr.Row(): | |
| api_key = gr.Textbox( | |
| label="ANTHROPIC_API_KEY", type="password", placeholder="sk-ant-…" | |
| ) | |
| prompt = gr.Textbox( | |
| label="Describe your app/site", | |
| lines=6, | |
| placeholder="Example: responsive dark-mode portfolio with glass panels and smooth animations.", | |
| ) | |
| generate_btn = gr.Button("✨ Generate", variant="primary") | |
| with gr.Row(): | |
| with gr.Tab("Live Preview"): | |
| preview = gr.HTML( | |
| elem_id="preview-pane", | |
| value="<div style='display:flex;align-items:center;justify-content:center;height:100%;color:#aaa;'>Awaiting generation…</div>", | |
| ) | |
| with gr.Tab("Artifacts"): | |
| file_select = gr.Dropdown(label="Select file", choices=[], interactive=True) | |
| editor = gr.Code(language="html", label="Code Editor", value="") | |
| save_btn = gr.Button("💾 Save") | |
| with gr.Tab("Raw Output & Export"): | |
| raw_output = gr.Textbox(label="Model Output (raw)", lines=12) | |
| latency_box = gr.Number(label="Latency (ms)") | |
| zip_file = gr.File(label="Download ZIP", interactive=False) | |
| files_state = gr.State({}) | |
| demo.css = """ | |
| #preview-pane { | |
| height: 85vh !important; | |
| min-height: 550px; | |
| border: 1px solid #ccc; | |
| border-radius: 10px; | |
| overflow: auto; | |
| background: #fff; | |
| } | |
| """ | |
| # -------------------- FUNCTIONS -------------------- | |
| def generate(api_key, prompt): | |
| if not api_key: | |
| raise gr.Error("Please enter your Anthropic API key.") | |
| files, raw, latency = call_claude(api_key, prompt) | |
| srcdoc = render_srcdoc(files) | |
| zip_path = make_zip_path(files) | |
| names = list(files.keys()) | |
| first = names[0] if names else "" | |
| return ( | |
| files, | |
| gr.update(value=srcdoc), | |
| gr.update(choices=names, value=first), | |
| gr.update(value=files.get(first, "")), | |
| raw, | |
| latency, | |
| zip_path, | |
| ) | |
| generate_btn.click( | |
| generate, | |
| inputs=[api_key, prompt], | |
| outputs=[files_state, preview, file_select, editor, raw_output, latency_box, zip_file], | |
| ) | |
| def load_editor(files, name): | |
| return files.get(name, "") | |
| file_select.change(load_editor, inputs=[files_state, file_select], outputs=editor) | |
| def save_file(files, name, code): | |
| files = dict(files) | |
| if name: | |
| files[name] = code | |
| return files, gr.update(value=render_srcdoc(files)) | |
| save_btn.click(save_file, inputs=[files_state, file_select, editor], outputs=[files_state, preview]) | |
| demo.launch() |