Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168// flux, 26.04.23// Proxy to NVIDIA NIM FLUX.1 schnell image generation.// Hides the NVIDIA_API_KEY, applies one of two AC style presets,// returns the raw JPEG as a data URL the piece can decode directly.//// Usage from a piece:// const res = await fetch("/api/flux", {// method: "POST",// headers: { "Content-Type": "application/json" },// body: JSON.stringify({ prompt: "a happy frog", preset: "kidlisp", seed: 7 }),// });// const { ok, png, reason, elapsed_ms, seed } = await res.json();//// On safety-filter rejection: { ok: false, reason: "filtered" } (200, so the// piece can react gracefully). On NVIDIA upstream error: 502.//// Env: NVIDIA_API_KEY (required). Lives in lith/.env in production.
import { respond } from "../../backend/http.mjs";
const FLUX_URL = "https://ai.api.nvidia.com/v1/genai/black-forest-labs/flux.1-schnell";
// Two filter-safe AC style suffixes. The bisect that pinned these down lives// in ~/Desktop/nvidia-flux-log/README.md — short version: NVIDIA's safety// classifier filters on clusters of proper nouns + dense modifiers, so the// suffixes deliberately avoid naming the platform / maker / language.const PRESETS = { // Soft pastel mascot energy — animals, food, friendly subjects warm: "chunky pixel-art bitmap, crisp 1-pixel edges, no anti-aliasing, " + "saturated palette of black, navy, hot pink, lime, cyan, yellow, magenta, white, " + "centered subject on flat solid color background, " + "soft 1-pixel offset pastel shadow beneath subject, " + "square mobile composition, 90s indie computing aesthetic, " + "handmade lo-fi warmth, no text, no UI, no watermarks",
// High-contrast CRT energy — devices, abstract objects, default kidlisp: "high-contrast pixel-art bitmap, crisp 1-pixel edges, no anti-aliasing, " + "strict palette of black, hot pink, lime, cyan, yellow, white, " + "solid black background, " + "hard cyan 1-pixel shadow beneath subject, " + "square composition, no text",
// No styling — pass the user's prompt through verbatim raw: "",};
const ALLOWED_WIDTHS = [768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344];
export async function handler(event) { if (event.httpMethod === "OPTIONS") { return respond(200, ""); } if (event.httpMethod !== "POST") { return respond(405, { ok: false, reason: "method" }); }
if (!process.env.NVIDIA_API_KEY) { console.error("flux: NVIDIA_API_KEY not configured"); return respond(500, { ok: false, reason: "no_key" }); }
let body; try { body = JSON.parse(event.body || "{}"); } catch { return respond(400, { ok: false, reason: "bad_json" }); }
const prompt = (body.prompt || "").toString().trim(); if (!prompt) return respond(400, { ok: false, reason: "no_prompt" }); if (prompt.length > 1000) return respond(400, { ok: false, reason: "prompt_too_long" });
const presetName = body.preset || "kidlisp"; const styleSuffix = PRESETS[presetName] ?? PRESETS.kidlisp; const fullPrompt = styleSuffix ? `${prompt} — ${styleSuffix}` : prompt;
// Width/height clamp to FLUX's literal allowed set. Default 768 (smallest // → fastest, most reliable). Pieces that want bigger pay the latency tail. const width = ALLOWED_WIDTHS.includes(+body.width) ? +body.width : 768; const height = ALLOWED_WIDTHS.includes(+body.height) ? +body.height : width;
const seed = Number.isInteger(body.seed) ? body.seed : Math.floor(Math.random() * 1e9);
// 30s timeout — FLUX schnell normally returns in 1-4s. NVIDIA has been // observed hanging for minutes before 504'ing during outages; fail fast // so the piece can show an error and let the user retry. const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000);
const t0 = Date.now(); let upstream; try { upstream = await fetch(FLUX_URL, { method: "POST", headers: { Authorization: `Bearer ${process.env.NVIDIA_API_KEY}`, "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify({ prompt: fullPrompt, cfg_scale: 0, steps: 4, seed, width, height, mode: "base", }), signal: controller.signal, }); } catch (err) { if (err.name === "AbortError") { return respond(504, { ok: false, reason: "timeout" }); } console.error("flux: upstream fetch failed", err); return respond(502, { ok: false, reason: "network", detail: err.message }); } finally { clearTimeout(timeoutId); }
if (!upstream.ok) { const detail = await upstream.text().catch(() => ""); console.error("flux: upstream", upstream.status, detail.slice(0, 300)); return respond(502, { ok: false, reason: "upstream", status: upstream.status, detail: detail.slice(0, 300), }); }
let data; try { data = await upstream.json(); } catch { return respond(502, { ok: false, reason: "bad_upstream_json" }); }
const art = data?.artifacts?.[0]; if (!art) return respond(502, { ok: false, reason: "no_artifact" });
if (art.finishReason !== "SUCCESS") { // Safety filter — return 200 so the piece can react. return respond(200, { ok: false, reason: "filtered", finish: art.finishReason, }); }
const elapsed_ms = Date.now() - t0; return respond(200, { ok: true, png: `data:image/jpeg;base64,${art.base64}`, width, height, seed: art.seed, preset: presetName, elapsed_ms, });}