Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
drochetti committed Feb 22, 2024
0 parents commit e99137d
Show file tree
Hide file tree
Showing 34 changed files with 9,036 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Rename this file to .env.local and add your fal credentials
# Visit https://fal.ai to get started
FAL_KEY="FAL_KEY_ID:FAL_KEY_SECRET"
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Lightning Fast SDXL API demo by fal.ai

This application is a sample of the SDXL Lightning API [https://fal.ai/models/stable-diffusion-xl-lightning]. It also shows the `fal.realtime` client in action.

### Prerequisites

1. [Fal AI](https://fal.ai/) API key (for model access)

### Getting started

1. Add the `FAL_KEY` to your `.env.local` file.
2. Run
```sh
npm run dev
```
26 changes: 26 additions & 0 deletions app/api/proxy/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { route } from "@fal-ai/serverless-proxy/nextjs";
import { type NextRequest } from "next/server";

const URL_ALLOW_LIST = ["https://rest.alpha.fal.ai/tokens/"];

export const POST = (req: NextRequest) => {
const url = req.headers.get("x-fal-target-url");
if (!url) {
return new Response("Not found", { status: 404 });
}

if (!URL_ALLOW_LIST.includes(url)) {
return new Response("Not allowed", { status: 403 });
}

const appCheckCookie = req.cookies.get("fal-app");
if (!appCheckCookie || !appCheckCookie.value) {
return new Response("Not allowed", { status: 403 });
}

return route.POST(req);
};

export const GET = (req: NextRequest) => {
return route.GET(req);
};
Binary file added app/favicon.ico
Binary file not shown.
64 changes: 64 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

::-webkit-scrollbar {
width: 0;
background-color: transparent;
}

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5.9% 10%;
--radius: 0.3rem;
}

.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
49 changes: 49 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { Nav } from "@/components/nav";
import { ThemeProvider } from "@/components/theme-provider";
import { Analytics } from "@vercel/analytics/react";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "SDXL Lightning - by fal.ai",
description: "Lightning fast SDXL API demo by fal.ai",
authors: [{ name: "fal.ai", url: "https://fal.ai" }],
metadataBase: new URL("https://fastsdxl.ai"),
openGraph: {
images: "/og_thumbnail.jpeg",
},
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<body
className={inter.className}
style={{
display: "flex",
flex: 1,
flexDirection: "column",
height: "100vh",
}}
>
<Analytics />
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<Nav />
{children}
</ThemeProvider>
</body>
</html>
);
}
148 changes: 148 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"use client";

/* eslint-disable @next/next/no-img-element */
/* eslint-disable @next/next/no-html-link-for-pages */
import * as fal from "@fal-ai/serverless-client";
import { useEffect, useRef, useState } from "react";
import { Input } from "@/components/ui/input";
import { ModelIcon } from "@/components/icons/model-icon";
import Link from "next/link";

const DEFAULT_PROMPT =
"A cinematic shot of a baby raccoon wearing an intricate italian priest robe";

function randomSeed() {
return Math.floor(Math.random() * 10000000).toFixed(0);
}

fal.config({
proxyUrl: "/api/proxy",
});

const INPUT_DEFAULTS = {
_force_msgpack: new Uint8Array([]),
enable_safety_checker: true,
image_size: "square_hd",
sync_mode: true,
num_images: 1,
num_inference_steps: "2",
};

export default function Lightning() {
const [image, setImage] = useState<null | string>(null);
const [prompt, setPrompt] = useState<string>(DEFAULT_PROMPT);
const [seed, setSeed] = useState<string>(randomSeed());
const [inferenceTime, setInferenceTime] = useState<number>(NaN);

const connection = fal.realtime.connect("fal-ai/fast-lightning-sdxl", {
connectionKey: "lightning-sdxl",
throttleInterval: 64,
onResult: (result) => {
const blob = new Blob([result.images[0].content], { type: "image/jpeg" });
setImage(URL.createObjectURL(blob));
setInferenceTime(result.timings.inference);
},
});

const timer = useRef<any | undefined>(undefined);

const handleOnChange = async (prompt: string) => {
if (timer.current) {
clearTimeout(timer.current);
}
setPrompt(prompt);
const input = {
...INPUT_DEFAULTS,
prompt: prompt,
seed: seed ? Number(seed) : Number(randomSeed()),
};
connection.send(input);
timer.current = setTimeout(() => {
connection.send({ ...input, num_inference_steps: "4" });
}, 500);
};

useEffect(() => {
if (typeof window !== "undefined") {
window.document.cookie = "fal-app=true; path=/; samesite=strict; secure;";
}
// initial image
connection.send({
...INPUT_DEFAULTS,
num_inference_steps: "4",
prompt: prompt,
seed: seed ? Number(seed) : Number(randomSeed()),
});
}, []);

return (
<main>
<div className="container py-4 px-1.5 space-y-4 lg:space-y-8 mx-auto">
<div className="flex flex-col space-y-2">
<div className="flex flex-col max-md:space-y-4 md:flex-row md:space-x-4">
<div className="flex-1 space-y-1">
<label>Prompt</label>
<Input
onChange={(e) => {
handleOnChange(e.target.value);
}}
className="font-light w-full"
placeholder="Type something..."
value={prompt}
/>
</div>
<div className="space-y-1">
<label>Seed</label>
<Input
onChange={(e) => {
setSeed(e.target.value);
handleOnChange(prompt);
}}
className="font-light w-full"
placeholder="random"
type="number"
value={seed}
/>
</div>
</div>
</div>
<div className="flex flex-col space-y-6 lg:flex-row lg:space-y-0">
<div className="flex-1 flex-col flex items-center justify-center">
{image && inferenceTime && (
<div className="flex flex-row space-x-1">
<span>inference time</span>
<strong>
{inferenceTime
? `${(inferenceTime * 1000).toFixed(0)}ms`
: `n/a`}
</strong>
</div>
)}
<div className="min-h-[512px] max-w-fit">
{image && (
<img id="imageDisplay" src={image} alt="Dynamic Image" />
)}
</div>
</div>
</div>
</div>
<div className="container flex flex-col items-center justify-center my-4">
<p className="text-sm text-base-content/70 py-4 text-center">
This playground is hosted on{" "}
<strong>
<a href="https://fal.ai" className="underline" target="_blank">
fal.ai
</a>
</strong>{" "}
and is for demonstration purposes only.
</p>
<div className="flex flex-row items-center space-x-2">
<span className="text-xs font-mono">powered by</span>
<Link href="https://fal.ai" target="_blank">
<ModelIcon />
</Link>
</div>
</div>
</main>
);
}
16 changes: 16 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
Loading

0 comments on commit e99137d

Please sign in to comment.