import * as fal from "https://esm.sh/@fal-ai/serverless-client";
import React, { useEffect, useState } from "https://esm.sh/react";
import { createRoot } from "https://esm.sh/react-dom/client";
import { blob } from "https://esm.town/v/std/blob";
const GRID_SIZE = 100;
const BOLT_CHANCE = 0.2;
const BOLT_SIZE = 40;
const DEFAULT_PROMPT = "green grass landscape with text \"val.town\" is engraved, a townhouse in the background";
const PLACEHOLDER_IMAGE = "https://fal.media/files/penguin/PysYf1-_ddhM7JKiLrkcF.png";
const RATE_LIMIT = 15;
const RATE_LIMIT_WINDOW = 5 * 60 * 1000;
interface RateLimitData {
count: number;
firstRequest: number;
}
function LightningBolt({ x, y }) {
return (
<svg x={x} y={y} width="40" height="40" viewBox="0 0 10 10">
<path d="M5,0 L3,5 L6,5 L4,10 L7,5 L4,5 Z" fill="#FFFF00" stroke="#FFFF00" strokeWidth="0.5" />
</svg>
);
}
function Background() {
const [bolts, setBolts] = useState([]);
useEffect(() => {
const newBolts = [];
const numColumns = Math.ceil(window.innerWidth / GRID_SIZE);
const numRows = Math.ceil(window.innerHeight / GRID_SIZE);
for (let i = 0; i < numColumns; i++) {
for (let j = 0; j < numRows; j++) {
if (Math.random() < BOLT_CHANCE) {
newBolts.push({
x: i * GRID_SIZE + Math.random() * (GRID_SIZE - BOLT_SIZE),
y: j * GRID_SIZE + Math.random() * (GRID_SIZE - BOLT_SIZE),
});
}
}
}
setBolts(newBolts);
}, []);
return (
<svg className="background" width="100%" height="100%">
{bolts.map((bolt, index) => <LightningBolt key={index} x={bolt.x} y={bolt.y} />)}
</svg>
);
}
function LoadingSpinner() {
return (
<div className="loading-spinner">
<div className="spinner"></div>
</div>
);
}
function App() {
const [prompt, setPrompt] = useState(DEFAULT_PROMPT);
const [imageUrl, setImageUrl] = useState(PLACEHOLDER_IMAGE);
const [latency, setLatency] = useState<number | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const generateImage = async () => {
setLoading(true);
setError("");
try {
const response = await fetch("/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || "Failed to generate image");
setLatency(data.latency);
setImageUrl(data.imageUrl);
setError("");
} catch (err) {
setError(err.message);
console.error(err);
} finally {
setLoading(false);
}
};