Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

Sign in to fal.ai, generate an API key, and set it as FAL_KEY for your account.

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
// This app uses the fal.ai API to generate images based on user prompts.
// It features a clean UI with an input field for the prompt and a button to generate the image.
// The generated image is displayed below the input field.
// React is used for the UI and the fal.ai serverless client for image generation.
// The app measures and displays the latency for each image generation.
// The background features randomly placed pixelart lightning bolts in neon yellow.
/** @jsxImportSource https://esm.sh/react */
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";
// Constants for background
const GRID_SIZE = 100; // Size of each grid cell
const BOLT_CHANCE = 0.2; // 20% chance to place a bolt in each cell
const BOLT_SIZE = 40; // Width and height of each bolt
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; // requests per hour
const RATE_LIMIT_WINDOW = 5 * 60 * 1000; // 5 minutes in milliseconds
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(""); // Clear any previous errors
} catch (err) {
setError(err.message);
console.error(err);
} finally {
setLoading(false);
}
};
isidentical-fluximagegenerator.web.val.run
August 28, 2024