diff --git a/README.md b/README.md index 7b1ec4c..31e27f0 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,16 @@ This app is powered by: 🍃 [Tailwind CSS](https://tailwindcss.com/), for styles. +## Usage + +1. Open the app in your browser. +1. When prompted, enter your [Replicate API token](https://replicate.com/account/api-tokens?new-token-name=paint-by-text-kontext). +1. You can generate a free token at the link above (requires a Replicate account). +1. Your token is stored securely in your browser and used only for your requests. + ## Development 1. Install a recent version of [Node.js](https://nodejs.org/) -1. Copy your [Replicate API token](https://replicate.com/account?utm_source=project&utm_campaign=paintbytext) and set it in your environment: - ``` - echo "REPLICATE_API_TOKEN=" > .env.local - ```` 1. Install dependencies and run the server: ``` npm install diff --git a/pages/api/predictions/[id].js b/pages/api/predictions/[id].js index 7029fbb..bba1aa4 100644 --- a/pages/api/predictions/[id].js +++ b/pages/api/predictions/[id].js @@ -1,9 +1,15 @@ const API_HOST = process.env.REPLICATE_API_HOST || "https://api.replicate.com"; export default async function handler(req, res) { + const token = req.headers["x-replicate-api-token"]; + if (!token) { + res.statusCode = 401; + res.end(JSON.stringify({ detail: "Missing Replicate API token. Please provide your token in the x-replicate-api-token header." })); + return; + } const response = await fetch(`${API_HOST}/v1/predictions/${req.query.id}`, { headers: { - Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`, + Authorization: `Token ${token}`, "Content-Type": "application/json", }, }); diff --git a/pages/api/predictions/index.js b/pages/api/predictions/index.js index 7f8447e..27d8ac4 100644 --- a/pages/api/predictions/index.js +++ b/pages/api/predictions/index.js @@ -1,38 +1,30 @@ import Replicate from "replicate"; - -const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN, - userAgent: `${packageData.name}/${packageData.version}` -}); - - +import packageData from "../../../package.json"; const API_HOST = process.env.REPLICATE_API_HOST || "https://api.replicate.com"; -import packageData from "../../../package.json"; - export default async function handler(req, res) { - if (!process.env.REPLICATE_API_TOKEN) { - throw new Error("The REPLICATE_API_TOKEN environment variable is not set. See README.md for instructions on how to set it."); + const token = req.headers["x-replicate-api-token"]; + if (!token) { + res.statusCode = 401; + res.end(JSON.stringify({ detail: "Missing Replicate API token. Please provide your token in the x-replicate-api-token header." })); + return; } - // remove null and undefined values req.body = Object.entries(req.body).reduce( (a, [k, v]) => (v == null ? a : ((a[k] = v), a)), {} ); - - let prediction - - const model = "black-forest-labs/flux-kontext-pro" + let prediction; + const model = "black-forest-labs/flux-kontext-pro"; + const replicate = new Replicate({ + auth: token, + userAgent: `${packageData.name}/${packageData.version}` + }); prediction = await replicate.predictions.create({ model, input: req.body }); - - - console.log({prediction}); - res.statusCode = 201; res.end(JSON.stringify(prediction)); } diff --git a/pages/index.js b/pages/index.js index 422f2b5..ae114ee 100644 --- a/pages/index.js +++ b/pages/index.js @@ -14,6 +14,51 @@ export const appName = "Paint by Text"; export const appSubtitle = "Edit your photos using written instructions, with the help of an AI."; export const appMetaDescription = "Edit your photos using written instructions, with the help of an AI."; +function TokenModal({ onTokenSet }) { + const [token, setToken] = useState(""); + return ( +
+
{ + e.preventDefault(); + if (token) { + localStorage.setItem("replicateApiToken", token); + onTokenSet(token); + } + }} + aria-modal="true" + role="dialog" + > +

Enter your Replicate API token

+

+ Get a free token at {" "} + + replicate.com/account/api-tokens + +

+ setToken(e.target.value)} + placeholder="r8_..." + required + autoFocus + /> + +
+
+ ); +} + export default function Home() { const [events, setEvents] = useState([]); const [predictions, setPredictions] = useState([]); @@ -21,10 +66,15 @@ export default function Home() { const [isProcessing, setIsProcessing] = useState(false); const [seed] = useState(getRandomSeed()); const [initialPrompt, setInitialPrompt] = useState(seed.prompt); + const [apiToken, setApiToken] = useState(null); +// Removed showTokenForm state // set the initial image from a random seed useEffect(() => { setEvents([{ image: seed.image }]); + const storedToken = localStorage.getItem("replicateApiToken"); + if (storedToken) setApiToken(storedToken); + // Removed setShowTokenForm }, [seed.image]); const handleImageDropped = async (image) => { @@ -39,6 +89,7 @@ export default function Home() { const handleSubmit = async (e) => { e.preventDefault(); + if (!apiToken) return; const prompt = e.target.prompt.value; const lastImage = events.findLast((ev) => ev.image)?.image; @@ -60,6 +111,7 @@ export default function Home() { method: "POST", headers: { "Content-Type": "application/json", + "x-replicate-api-token": apiToken, }, body: JSON.stringify(body), }); @@ -75,7 +127,9 @@ export default function Home() { prediction.status !== "failed" ) { await sleep(500); - const response = await fetch("/api/predictions/" + prediction.id); + const response = await fetch("/api/predictions/" + prediction.id, { + headers: { "x-replicate-api-token": apiToken }, + }); prediction = await response.json(); if (response.status !== 200) { setError(prediction.detail); @@ -105,8 +159,19 @@ export default function Home() { setInitialPrompt(seed.prompt); }; + const handleTokenSet = (token) => { + setApiToken(token); + // Removed setShowTokenForm + }; + + const handleLogout = () => { + localStorage.removeItem("replicateApiToken"); + setApiToken(null); + // Removed setShowTokenForm + }; + return ( -
+
{appName} @@ -115,42 +180,52 @@ export default function Home() { -
-
-

{appName}

-

- {appSubtitle} -

-
- - { - setInitialPrompt(events[index - 1].prompt); - setEvents( - events.slice(0, index - 1).concat(events.slice(index + 1)) - ); - }} - /> - - - -
- {error &&

{error}

} -
- -
); }