Spaces:
Sleeping
Sleeping
File size: 5,775 Bytes
306086a |
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 |
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() |