diff --git a/README.md b/README.md index 3278b1a..0468b96 100644 --- a/README.md +++ b/README.md @@ -11,19 +11,13 @@ To run this app locally or deploy your own copy, you'll need: - Node.js (20 or later) - A [Replicate API token](https://replicate.com/account/api-tokens) -Set your Replicate API token in your environment: - -```sh -export REPLICATE_API_TOKEN=r8_... -``` - -Then install dependencies and run the development server: +Install dependencies and run the development server: ``` npm install && npm run dev ``` -Then open [localhost:3000](http://localhost:3000) in your browser to see the app. +Then open [localhost:3000](http://localhost:3000) in your browser. You'll be prompted to enter your Replicate API token, which will be stored in your browser's local storage for future use. ## Continuous Deployment diff --git a/app/api/generate-image/route.ts b/app/api/generate-image/route.ts index 1862ae6..2488d3c 100644 --- a/app/api/generate-image/route.ts +++ b/app/api/generate-image/route.ts @@ -2,9 +2,20 @@ import { NextResponse } from "next/server"; import Replicate from "replicate"; export async function GET(request: Request) { - const replicate = new Replicate(); const { searchParams } = new URL(request.url); const prompt = searchParams.get("text"); + const token = searchParams.get("token"); + + if (!token) { + return new NextResponse("API token is required", { + status: 401, + statusText: "Unauthorized", + }); + } + + const replicate = new Replicate({ + auth: token, + }); const model = "black-forest-labs/flux-schnell"; const input = { @@ -18,18 +29,26 @@ export async function GET(request: Request) { num_inference_steps: 2, }; - const output = await replicate.run(model, { input }) as string[]; - const headers = new Headers(); - headers.set("Content-Type", "image/*"); - headers.set( - "Cache-Control", - "no-store, no-cache, must-revalidate, proxy-revalidate" - ); - headers.set("Pragma", "no-cache"); - headers.set("Expires", "0"); - return new NextResponse(output[0], { - status: 200, - statusText: "OK", - headers, - }); + try { + const output = await replicate.run(model, { input }) as string[]; + const headers = new Headers(); + headers.set("Content-Type", "image/*"); + headers.set( + "Cache-Control", + "no-store, no-cache, must-revalidate, proxy-revalidate" + ); + headers.set("Pragma", "no-cache"); + headers.set("Expires", "0"); + return new NextResponse(output[0], { + status: 200, + statusText: "OK", + headers, + }); + } catch (error) { + console.error("Error generating image:", error); + return new NextResponse("Failed to generate image. Please check your API token.", { + status: 500, + statusText: "Internal Server Error", + }); + } } diff --git a/app/page.tsx b/app/page.tsx index c6ca04f..da6a6b2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -4,22 +4,119 @@ import { useEffect, useRef, useState } from "react"; import Image from "next/image"; import Link from "next/link"; - const DEFAULT_INPUT = "super fast pink and gold jet is flying against a beautiful nebula, trail behind it" +const DEFAULT_INPUT = "super fast pink and gold jet is flying against a beautiful nebula, trail behind it" +const STORAGE_KEY = "replicate_api_token"; export default function Home() { const [texts, setTexts] = useState([DEFAULT_INPUT]); + const [apiToken, setApiToken] = useState(null); + const [tokenInput, setTokenInput] = useState(""); + const [isLoading, setIsLoading] = useState(true); + + // Load token from localStorage on mount + useEffect(() => { + const storedToken = localStorage.getItem(STORAGE_KEY); + if (storedToken) { + setApiToken(storedToken); + } + setIsLoading(false); + }, []); const onChange = (e: React.ChangeEvent) => { setTexts([...texts, e.target.value]); }; - // Focus the input element when the page loads) + const handleTokenSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (tokenInput.trim()) { + localStorage.setItem(STORAGE_KEY, tokenInput.trim()); + setApiToken(tokenInput.trim()); + setTokenInput(""); + } + }; + + const handleClearToken = () => { + localStorage.removeItem(STORAGE_KEY); + setApiToken(null); + setTexts([DEFAULT_INPUT]); + }; + + // Focus the input element when the page loads const inputElement = useRef(null); useEffect(() => { - if (inputElement.current) { + if (inputElement.current && apiToken) { inputElement.current.focus(); } - }, []); + }, [apiToken]); + + if (isLoading) { + return ( +
+
Loading...
+
+ ); + } + + if (!apiToken) { + return ( +
+
+

Welcome to Fast Flux Demo

+

+ Create a{" "} + + Replicate API token + + {" "}then paste it here: +

+ +
+
+ + setTokenInput(e.target.value)} + placeholder="r8_..." + className="w-full p-3 bg-stone-700 text-stone-100 placeholder-stone-400 rounded border border-stone-600 focus:outline-none focus:ring-2 focus:ring-blue-500" + autoFocus + /> +
+ + +
+ +
+

+ Check out the GitHub repo at{" "} + + replicate/fast-flux-demo + +

+
+
+
+ ); + } return (
@@ -33,17 +130,26 @@ export default function Home() { ref={inputElement} onFocus={(e) => e.target.setSelectionRange(0, e.target.value.length)} /> - - - +
+ + + + +
{texts.map((text, index) => (